Вопрос

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

Это было полезно?

Решение

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

  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. Тогда возникает проблема бинарного vs.десятичное представление.Число вроде 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 в конце десятичной дроби.

    Точно так же двоичные дроби хороши для обозначения половин, четвертей, восьмых и т. д.но такие вещи, как десятая часть, дадут бесконечно повторяющийся поток двоичных цифр.

  3. Есть еще одна проблема, хотя большинство людей с ней не сталкиваются, если только они не выполняют огромное количество числовых операций.Но ведь те уже знают о проблеме.Поскольку многие числа с плавающей запятой являются всего лишь приближениями точного значения, это означает, что для данного приближения ж действительного числа р действительных чисел может быть бесконечно много больше р1, р2, ...которые соответствуют точно такому же приближению.Эти числа лежат в определенном интервале.Скажем так рмин – минимально возможное значение р что приводит к ж и рМакс максимально возможное значение р для которого это справедливо, то вы получите интервал [рмин, рМакс] где любое число в этом интервале может быть вашим фактическим номером р.

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

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

Другие советы

Покажите им, что десятичная система страдает от точно та же проблема.

Попробуйте представить 1/3 в десятичной системе счисления по основанию 10.Точно у вас это не получится.

Поэтому, если вы напишете «0,3333», вы получите достаточно точное представление для многих случаев использования.

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

Другие дроби, такие как 1/2, можно легко представить в виде конечного десятичного представления в десятичной системе счисления:"0,5"

Теперь основание 2 и основание 10 страдают, по сути, от одной и той же проблемы:у обоих есть числа, которые они не могут точно представить.

Хотя в базе 10 нет проблем с представлением 1/10 как «0,1» в базе 2, вам понадобится бесконечное представление, начинающееся с «0,000110011..».

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

Вздремнуть...

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

В питоне:

>>> 1.0 / 10
0.10000000000000001

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

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

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

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

3.60000000000000008882

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

Проблема:Значение 0,45 невозможно точно представить с помощью числа с плавающей запятой, поэтому оно округляется до 0,450000018.Почему это?

Отвечать:Целочисленное значение 45 представлено двоичным значением 101101.Для того, чтобы сделать значение 0.45 было бы точным, если бы вы могли взять 45 x 10^-2 (= 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 ограничено.Для плавающего числа у вас есть общее число 32.Double имеет 64, а Decimal — 128.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top