Как вы объясните неточность с плавающей запятой новым программистам и мирянам, которые все еще считают компьютеры бесконечно мудрыми и точными?
У вас есть любимый пример или анекдот, который, кажется, дает эту идею гораздо лучше, чем точное, но сухое объяснение?
Как это учится в классах компьютерных наук?
Примеры неточности с плавающей запятой
Ответ 1
В основном существуют две основные проблемы, с которыми люди сталкиваются с числами с плавающей запятой.
-
Проблема масштаба. Каждый номер FP имеет показатель, который определяет общий "масштаб" номера, поэтому вы можете представлять либо очень маленькие значения, либо действительно большие, хотя количество цифр, которое вы можете выделить для этого, ограничено. Добавление двух чисел разного масштаба иногда приводит к тому, что меньший из них "съеден", так как нет возможности поместить его в более крупный масштаб.
PS> $a = 1; $b = 0.0000000000000000000000001 PS> Write-Host a=$a b=$b a=1 b=1E-25 PS> $a + $b 1
В качестве аналогии для этого случая вы можете представить большой бассейн и чайную ложку воды. Оба имеют очень разные размеры, но индивидуально вы можете легко понять, насколько они грубо. Однако, выливая чайную ложку в бассейн, вы все равно останетесь с бассейном, полным воды.
(Если люди, изучающие это, имеют проблемы с экспоненциальной нотацией, можно также использовать значения
1
и100000000000000000000
или так.) -
Тогда возникает проблема двоичного и десятичного представлений. Число, подобное
0.1
, не может быть представлено точно с ограниченным количеством двоичных цифр. Некоторые языки маскируют это, хотя:PS> "{0:N50}" -f 0.1 0.10000000000000000000000000000000000000000000000000
Но вы можете "усилить" ошибку представления, повторно добавляя числа вместе:
PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum 9,99999999999998
Я не могу придумать хорошую аналогию, чтобы правильно объяснить это. Это в основном та же проблема, почему вы можете представить 1/ 3 только приблизительно в десятичной форме, потому что для получения точного значения вам нужно повторить 3 бесконечно в конце десятичной дроби.
Аналогично, двоичные фракции хороши для представления половин, четвертей, восьмых и т.д., но такие вещи, как десятая, будут давать бесконечно повторяющийся поток двоичных цифр.
-
Тогда есть еще одна проблема, хотя большинство людей не спотыкаются на это, если только они не производят огромное количество цифровых данных. Но тогда они уже знают о проблеме. Так как многие числа с плавающей запятой являются просто аппроксимациями точного значения, это означает, что для данного приближения f действительного числа r может быть бесконечно много вещественных чисел r 1, r 2,... которые соответствуют точно такому же приближению. Эти числа лежат в определенном интервале. Скажем, что r min - минимально возможное значение r, которое приводит к f и r max максимально возможное значение r, для которого это выполняется, тогда вы получили интервал [ r min, r max], где любое число в этом интервале может быть вашим фактическим числом r.
Теперь, если вы выполняете вычисления по этому количеству-добавлению, вычитанию, умножению и т.д., вы теряете точность. Каждое число является приблизительным, поэтому вы выполняете вычисления с интервалами. Результатом является также интервал, и ошибка аппроксимации только возрастает, тем самым расширяя интервал. Вы можете вернуть один номер из этого расчета. Но это всего лишь одно число из интервала возможных результатов, с учетом точности ваших исходных операндов и точности потерь из-за расчета.
Подобная вещь называется Интервальная арифметика и, по крайней мере, для меня это было частью нашего математического курса в университете.
Ответ 2
Покажите им, что система base-10 страдает от одной и той же проблемы.
Попробуйте представить 1/3 как десятичное представление в базе 10. Вы не сможете сделать это точно.
Итак, если вы пишете "0.3333", у вас будет достаточно точное представление для многих случаев использования.
Но если вы переместите это обратно на долю, вы получите "3333/10000", что не совпадает с "1/3".
Другие фракции, такие как 1/2, могут быть легко представлены конечным десятичным представлением в базе-10: "0,5"
Теперь base-2 и base-10 страдают, по существу, одной и той же проблемой: оба имеют некоторые числа, которые они не могут точно представлять.
В то время как у base-10 нет проблем, представляя 1/10 как "0.1" в base-2, вам понадобится бесконечное представление, начинающееся с "0.000110011..".
Ответ 3
Как это для объяснения неспециалисту. Односторонние компьютеры представляют числа, считая дискретные единицы. Это цифровые компьютеры. Для целых чисел те, у кого нет дробной части, современные цифровые компьютеры считаются степенями двух: 1, 2, 4, 8.,, Значение места, двоичные цифры, бла, бла, бла. Для фракций цифровые компьютеры рассчитывают обратные степени в два: 1/2, 1/4, 1/8,... Проблема в том, что многие числа не могут быть представлены суммой конечного числа этих обратных степеней. Использование большего количества значений места (больше бит) повысит точность представления этих "проблемных" чисел, но никогда не получит его именно потому, что имеет только ограниченное количество бит. Некоторые числа не могут быть представлены бесконечным числом бит.
Snooze...
ОК, вы хотите измерить объем воды в контейнере, и у вас есть только 3 мерных чашки: полная чашка, половина чашки и четверть чашки. После подсчета последней полной чашки, скажем, есть одна треть оставшейся чашки. Но вы не можете измерить это, потому что он точно не заполняет любую комбинацию доступных чашек. Он не заполняет половину чашки, и переполнение из четвертной чашки слишком мало, чтобы заполнить что-либо. Таким образом, у вас есть ошибка - разница между 1/3 и 1/4. Эта ошибка усугубляется, когда вы совмещаете ее с ошибками других измерений.
Ответ 4
В python:
>>> 1.0 / 10
0.10000000000000001
Объясните, как некоторые фракции не могут быть представлены точно в двоичном формате. Точно так же, как некоторые фракции (например, 1/3) не могут быть представлены точно в базе 10.
Ответ 5
Другой пример, в C
printf (" %.20f \n", 3.6);
невероятно дает
+3,60000000000000008882
Ответ 6
Вот мое простое понимание.
Проблема: Значение 0,45 не может быть точно представлено плавающей точкой и округлено до 0,450000018. Почему это?
Ответ: Значение int 45 равно двоичному значению 101101. Чтобы сделать значение 0,45, было бы точно, если бы вы могли взять 45 х 10 ^ -2 (= 45/10 ^ 2.) Но это невозможно, потому что вы должны использовать базу 2 вместо 10.
Таким образом, ближайший к 10 ^ 2 = 100 будет 128 = 2 ^ 7. Общее количество бит, которое вам нужно, равно 9: 6 для значения 45 (101101) + 3 бит для значения 7 (111). Тогда значение 45 х 2 ^ -7 = 0,3515625. Теперь у вас серьезная проблема неточности. 0,3515625 не близко к 0,45.
Как мы можем улучшить эту неточность? Ну, мы могли бы изменить значение 45 и 7 на что-то еще.
Как насчет 460 x 2 ^ -10 = 0.44921875. Теперь вы используете 9 бит для 460 и 4 бит для 10. Затем его немного ближе, но все же не так близко. Однако, если ваше начальное желаемое значение было 0,44921875, вы получите точное совпадение без аппроксимации.
Таким образом, формула для вашего значения будет X = A x 2 ^ B. Где A и B - целые значения положительные или отрицательные. Очевидно, чем выше числа, тем выше будет ваша точность, поскольку вы знаете, что количество бит для представления значений A и B ограничено. Для float у вас есть общее количество 32. Двойной имеет 64, а Decimal имеет 128.
Ответ 7
Симпатичная часть численной странности может наблюдаться, если одна из них преобразует 9999999.4999999999 в float
и обратно в double
. Результат сообщается как 10000000, хотя это значение, очевидно, ближе к 9999999, и хотя 9999999.499999999 правильно округляется до 9999999.