2010-01-20 5 views
29

Как вы объясните неточность с плавающей запятой новым программистам и мирянам, которые все еще думают, что компьютеры бесконечно мудры и точны?
У вас есть любимый пример или анекдот, который, кажется, дает идею гораздо лучше, чем точное, но сухое объяснение?
Как это учится в классах компьютерных наук?Примеры неточности с плавающей запятой

+1

Вы можете составить это с помощью этого простого javascript: alert (0.1 * 0.1 * 10); – 2010-04-24 23:07:01

+0

Взгляните на эту статью: [Что должен знать каждый компьютерный ученый о арифметике с плавающей точкой] (http://docs.sun.com/source/806-3568/ncg_goldberg.html) –

ответ

26

В основном есть две основные проблемы, с которыми сталкиваются люди с числами с плавающей запятой.

  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 или около того.)

  2. Тогда есть проблема двоичных против десятичного представления. Число, подобное 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 
    

    Я не могу думать о хорошей аналогии, чтобы правильно объяснить , хоть. Это в основном та же проблема, почему вы можете представлять/только приблизительно в десятичной форме, потому что для получения точного значения вам необходимо повторить 3 бесконечно в конце десятичной дроби.

    Аналогично, двоичные фракции хороши для представления половин, четвертей, восьмых и т. Д., Но вещи, подобные десятой, дают бесконечно повторяющийся поток двоичных цифр.

  3. Тогда есть еще одна проблема, хотя большинство людей не спотыкаются на это, если только они не делают огромное количество численного материала. Но тогда они уже знают о проблеме. Так как много чисел с плавающей точкой являются лишь приближением точного значения это означает, что при заданном приближении ф действительного числа г может быть бесконечно много больше действительных чисел г , г-, ... которые соответствуют точно такому же приближению. Эти числа лежат в определенном интервале.Допустим, что г мин является минимально возможное значение г, что приводит к е и г макс максимальное возможное значение г, для которых это имеет место, то вы получили отрезок [ г мин, г макс], где любое число в этом интервале может быть ваш фактический номер г.

    Теперь, если вы выполняете вычисления по этому числу, добавляя, вычитая, умножая и т. Д., Вы теряете точность. Каждое число является приблизительным, поэтому вы выполняете вычисления с интервалами. Результатом является также интервал, и ошибка аппроксимации только возрастает, тем самым расширяя интервал. Вы можете вернуть один номер из этого расчета. Но это просто один номер с интервалом возможных результатов с учетом точности ваших исходных операндов и точности потерь из-за расчета.

    Такие вещи называются Interval arithmetic и, по крайней мере, для меня это было частью нашего математического курса в университете.

+1

Привет, Йоханнес, это определенно хороший пример, но на самом деле он не говорит людям * почему * он не работает. Я ищу, чтобы кто-то понял причину неудачи, а не только тот факт, что он терпит неудачу все время от времени. –

+1

Хм, кроме объяснения проблемы масштаба и проблемы двоичного и десятичного представления, я думаю, что я не нашел лучшего способа рассказать об этом людям: /. Можно использовать похожие анекдоты, такие как добавление чайной ложки воды в бассейн не меняет наше восприятие того, сколько в нем. – Joey

+0

Чтобы разработать, многие люди, которых я получаю в мастерских, даже не очень удобны с научной нотацией, поэтому они уже требуют достаточного количества умственных усилий, чтобы обернуть головы вокруг разницы между -4e200, -4e-200, 4e- 200 и 4e200. –

2

В питона:

>>> 1.0/10 
0.10000000000000001 

Объясните, как некоторые фракции не могут быть представлены точно в двоичном коде. Точно так же, как некоторые фракции (например, 1/3) не могут быть представлены точно в основании 10.

+0

codeape, я ищу что-то немного глубже, чем просто парадные примеры ошибок округления. Я хотел бы сказать людям, почему эти ошибки ползут, и имейте в виду причину этого, без необходимости понимать спецификацию IEEE 754. –

+1

@David: дайте им пример, где числа с плавающей запятой точны, например, добавив 0.25 несколько раз. Результат будет точным, пока вы не переполните мантиссу, потому что 0.25 - '1/(2^2)'. Затем попробуйте одно и то же с 0.2, и вы получите проблемы, потому что 0.2 не является представимым в конечном номере base-2. –

6

Как это для объяснения неспециалисту. Односторонние компьютеры представляют числа, считая дискретные единицы. Это цифровые компьютеры. Для целых чисел те, у кого нет дробной части, современные цифровые компьютеры считаются степенями двух: 1, 2, 4, 8. ,,, Значение места, двоичные цифры, бла, бла, бла. Для фракций цифровые компьютеры рассчитывают обратные степени в два: 1/2, 1/4, 1/8, ... Проблема в том, что многие числа не могут быть представлены суммой конечного числа этих обратных степеней. Использование большего количества значений места (больше бит) повысит точность представления этих «проблемных» чисел, но никогда не получит его именно потому, что имеет только ограниченное количество бит. Некоторые числа не могут быть представлены бесконечным количеством бит.

Snooze ...

ОК, вы хотите измерить объем воды в контейнере, и у вас есть только 3 измерения чашки: полная чаша, половина чашки, и четверть стакана. После подсчета последней полной чашки, скажем, остается одна треть оставшейся чашки. Но вы не можете измерить это, потому что он точно не заполняет любую комбинацию доступных чашек. Он не заполняет половину чашки, и переполнение из четвертной чашки слишком мало, чтобы заполнить что-либо. Таким образом, у вас есть ошибка - разница между 1/3 и 1/4. Эта ошибка усугубляется, когда вы совмещаете ее с ошибками других измерений.

8

Покажите им, что система base-10 страдает от ровно та же проблема.

Попробуйте представить 1/3 как десятичное представление в базе 10. Вы не сможете это сделать точно.

Итак, если вы напишете «0.3333», у вас будет достаточно точное представление для многих случаев использования.

Но если вы переместите это на долю, вы получите «3333/10000», что равно не так же, как «1/3».

Другие фракции, такие как 1/2 легко могут быть представлены конечным десятичным представлением в базовом-10: «0,5»

В настоящее время базовый-2 и основание 10 страдает от по существу одной и те же задач: оба имеет некоторые цифры, которые они не могут точно представлять.

В то время как у base-10 нет проблем, представляя 1/10 как «0,1» в base-2, вам понадобится бесконечное представление, начинающееся с «0.000110011 ..».

2

Другой пример, в С

printf (" %.20f \n", 3.6); 

невероятно дает

3,60000000000000008882

0

мило кусок числовой странности может наблюдаться, если один преобразует 9999999.4999999999 к float и обратно к double. Результат сообщается как 10000000, хотя это значение, очевидно, ближе к 9999999, и хотя 9999999.499999999 правильно округляется до 9999999.

1

Вот мое простое понимание.

Проблема: Значение 0.45 не может быть точно представлено поплавком и округлено до 0.450000018. Почему это?

Ответ: INT значение 45 представлена ​​двоичным значением 101101. Для того, чтобы значение 0,45 было бы точным, если его можно взять 45 х^-2 10 (= 45/10^2 .) Но это невозможно, потому что вы должны использовать основание 2 вместо 10.

Таким образом, ближайший к 10^2 = 100 будет 128 = 2^7. Общее количество бит, которое вам нужно, равно 9: 6 для значения 45 (101101) + 3 бит для значения 7 (111). Затем значение 45 x 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.

Смежные вопросы