Почему изменение порядка сумм возвращает другой результат?
23.53 + 5.88 + 17.64
знак равно 47.05
23.53 + 17.64 + 5.88
знак равно 47.050000000000004
И Java, и JavaScript возвращают одинаковые результаты.
Я понимаю, что из-за того, что числа с плавающей запятой представлены в двоичном виде, некоторые рациональные числа ( например, 1/3 - 0,333333 ... ) не могут быть представлены точно.
Почему простое изменение порядка элементов влияет на результат?
java
javascript
floating-point
Марлон Бернардес
источник
источник
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
). Следовательно, да: будьте осторожны при выборе порядка сумм и других операций. Некоторые языки предоставляют встроенные средства для выполнения «высокоточных» сумм (например, pythonmath.fsum
), поэтому вы можете рассмотреть возможность использования этих функций вместо алгоритма наивной суммы.Ответы:
Это изменит точки, в которых значения округлены, в зависимости от их величины. В качестве примера рода вещей , которые мы видят, давайте делать вид , что вместо бинарной с плавающей точкой, мы использовали десятичный тип с плавающей точкой с 4 значащими цифрами, где выполняются каждое добавление в «бесконечной» точности , а затем с округлением до ближайшее представимое число. Вот две суммы:
Нам даже не нужны нецелые числа, чтобы это было проблемой:
Возможно, это более четко демонстрирует, что важной частью является то, что у нас ограниченное количество значащих цифр, а не ограниченное количество десятичных знаков . Если бы мы всегда могли сохранить одинаковое количество десятичных разрядов, то, по крайней мере, с добавлением и вычитанием мы были бы в порядке (до тех пор, пока значения не переполняются). Проблема в том, что когда вы получаете большее число, меньшая информация теряется - 10001 округляется до 10000 в этом случае. (Это пример проблемы, которую Эрик Липперт отметил в своем ответе .)
Важно отметить, что значения в первой строке с правой стороны одинаковы во всех случаях - поэтому, хотя важно понимать, что ваши десятичные числа (23.53, 5.88, 17.64) не будут представлены точно как
double
значения, это только проблема из-за проблем, показанных выше.источник
May extend this later - out of time right now!
с нетерпением жду этого @Jondouble
иfloat
, где для очень больших чисел, последовательные представимые числа более 1 друг от друга.Вот что происходит в двоичном формате. Как мы знаем, некоторые значения с плавающей точкой не могут быть представлены точно в двоичном виде, даже если они могут быть представлены точно в десятичном виде. Эти 3 числа являются лишь примерами этого факта.
С помощью этой программы я вывожу шестнадцатеричные представления каждого числа и результаты каждого сложения.
printValueAndInHex
Метод просто помощник шестигранного принтера.Вывод следующий:
Первые 4 цифры
x
,y
,z
, иs
«ы шестнадцатеричные представления. В представлении IEEE с плавающей запятой биты 2-12 представляют двоичный показатель , то есть масштаб числа. (Первый бит является знаковым битом, а остальные биты для мантиссы .) Представленная экспонента фактически является двоичным числом минус 1023.Экспоненты для первых 4 чисел извлекаются:
Первый набор дополнений
Второе число (
y
) имеет меньшую величину. При сложении этих двух чиселx + y
последние 2 бита второго числа (01
) сдвигаются за пределы диапазона и не учитываются при расчете.Второе дополнение добавляет
x + y
иz
добавляет два числа одного масштаба.Второй набор дополнений
Здесь
x + z
происходит первое. Они имеют одинаковый масштаб, но они дают число, которое выше в масштабе:Второе дополнение добавляет
x + z
иy
, и теперь 3 бита отбрасываютсяy
для добавления чисел (101
). Здесь должен быть округлен вверх, потому что результатом является следующее число с плавающей запятой вверх:4047866666666666
для первого набора дополнений по сравнению4047866666666667
со вторым набором дополнений. Эта ошибка достаточно значительна, чтобы показать ее в распечатке.В заключение, будьте осторожны при выполнении математических операций над числами IEEE. Некоторые представления неточны, и они становятся еще более неточными, когда масштабы различны. Сложите и вычтите числа аналогичного масштаба, если можете.
источник
=)
+1 для вашего помощника с шестигранным принтером ... это действительно здорово!Ответ Джона, конечно, правильный. В вашем случае ошибка не превышает ошибку, которую вы накапливаете, выполняя любую простую операцию с плавающей запятой. У вас есть сценарий, в котором в одном случае вы получаете нулевую ошибку, а в другом - крошечную ошибку; это не очень интересный сценарий. Хороший вопрос: существуют ли сценарии, в которых изменение порядка вычислений превращается из крошечной ошибки в (относительно) огромную ошибку? Ответ однозначно да.
Рассмотрим для примера:
против
против
Очевидно, в точной арифметике они будут одинаковыми. Интересно попытаться найти значения для a, b, c, d, e, f, g, h так, чтобы значения x1 и x2 и x3 отличались в большом количестве. Посмотри, сможешь ли ты сделать это!
источник
double d = double.MaxValue; Console.WriteLine(d + d - d - d); Console.WriteLine(d - d + d - d);
- результат равен бесконечности, а затем 0.На самом деле это охватывает гораздо больше, чем просто Java и Javascript, и, вероятно, повлияет на любой язык программирования с использованием чисел с плавающей запятой или двойных чисел.
В памяти плавающие точки используют специальный формат в соответствии со стандартом IEEE 754 (конвертер дает гораздо лучшее объяснение, чем я).
В любом случае, вот конвертер поплавков.
http://www.h-schmidt.net/FloatConverter/
Дело в порядке операций - «тонкость» операции.
Ваша первая строка дает 29,41 из первых двух значений, что дает нам 2 ^ 4 в качестве показателя степени.
Ваша вторая строка дает 41,17, что дает нам 2 ^ 5 в качестве показателя степени.
Мы теряем значительную цифру, увеличивая показатель степени, что может изменить результат.
Попробуйте включить и выключить последний бит в дальнем правом углу для 41.17, и вы увидите, что чего-то «незначительного», такого как 1/2 ^ 23 от показателя степени, будет достаточно, чтобы вызвать эту разницу с плавающей запятой.
Изменить: Для тех из вас, кто помнит значимые цифры, это подпадает под эту категорию. 10 ^ 4 + 4999 со значащей цифрой 1 будет 10 ^ 4. В этом случае значимая цифра намного меньше, но мы можем видеть результаты с прикрепленным к ней .00000000004.
источник
Числа с плавающей запятой представлены в формате IEEE 754, который обеспечивает определенный размер битов для мантиссы. К сожалению, это дает вам определенное количество «дробных строительных блоков» для игры, и некоторые дробные значения не могут быть точно представлены.
В вашем случае происходит то, что во втором случае сложение, вероятно, приводит к некоторой проблеме точности из-за порядка, в котором оцениваются дополнения. Я не вычислял значения, но это может быть, например, то, что 23,53 + 17,64 нельзя точно представить, а 23,53 + 5,88 можно.
К сожалению, это известная проблема, с которой вам просто нужно иметь дело.
источник
Я считаю, что это связано с порядком эвакуации. Хотя в математическом мире сумма, естественно, одинакова, в бинарном мире вместо A + B + C = D она
Так что есть второй шаг, где числа с плавающей запятой могут сойти
Когда вы меняете заказ,
источник