Я знаю, что 0.1
десятичное число не может быть представлено точно конечным двоичным числом ( объяснение ), поэтому double n = 0.1
потеряет некоторую точность и не будет точно 0.1
. С другой стороны, 0.5
можно представить именно так, как оно есть 0.5 = 1/2 = 0.1b
.
Сказав, что понятно, что добавление 0.1
три раза не даст точно, 0.3
так печатается следующий код false
:
double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
sum += d;
System.out.println(sum == 0.3); // Prints false, OK
Но тогда как получается, что добавление 0.1
пять раз даст точно 0.5
? Следующий код печатает true
:
double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?
Если 0.1
невозможно точно представить, как получается, что 5-кратное добавление дает именно то, 0.5
что можно точно представить?
sum
имела такое же конечное значение, как если бы цикл действительно выполнялся. В стандарте C ++ это называется «как если бы правило» или «такое же наблюдаемое поведение».Ответы:
Ошибка округления не случайна, и, как она реализована, она пытается минимизировать ошибку. Это означает, что иногда ошибка не видна или ее нет.
Например
0.1
не совсем0.1
то есть,new BigDecimal("0.1") < new BigDecimal(0.1)
но0.5
это точно1.0/2
Эта программа показывает вам истинные ценности.
печать
Примечание: это
0.3
немного отключено, но когда вы доберетесь до0.4
битов, вам придется сдвинуться на единицу, чтобы вписаться в 53-битный предел, и ошибка будет сброшена. Опять же , закрадывается ошибка обратно в течение0.6
и0.7
но0.8
к1.0
отбрасывается ошибка.Причиной ошибки является ограниченная точность. т.е. 53 бита. Это означает, что, поскольку число использует больше битов, поскольку оно становится больше, биты должны быть сброшены с конца. Это вызывает округление, которое в этом случае в вашу пользу.
Вы можете получить противоположный эффект, когда получите меньшее число, например
0.1-0.0999
=>,1.0000000000000286E-4
и вы увидите больше ошибок, чем раньше.Примером этого является то, почему в Java 6 Почему Math.round (0.49999999999999994) возвращает 1 В этом случае потеря бита в вычислениях приводит к большой разнице с ответом.
источник
strictfp
Time для рассмотрения целых чисел с фиксированной точкой, я думаю. (или BigDecimal)Переполнение запрета в плавающей запятой
x + x + x
- это точно правильно округленное (т. Е. Ближайшее) число с плавающей запятой к действительному 3 *x
,x + x + x + x
равное 4 *x
, иx + x + x + x + x
опять-таки правильное округленное приближение с плавающей запятой для 5 *x
.Первый результат, для
x + x + x
, вытекает из того факта, чтоx + x
является точным.x + x + x
Таким образом, является результатом только одного округления.Второй результат более сложный, одна демонстрация его обсуждается здесь (а Стивен Канон ссылается на другое доказательство путем анализа кейса на последних 3 цифрах
x
). Подводя итог, можно сказать , что либо 3 *x
находится в той же бинаде, что и 2 *,x
либо в той же бинаде, что и 4 *x
, и в каждом случае можно сделать вывод, что ошибка при третьем добавлении отменяет ошибку при втором добавлении ( первое добавление будет точным, как мы уже говорили).Третий результат, «
x + x + x + x + x
правильно округленный», вытекает из второго так же, как первый получается из точностиx + x
.Второй результат объясняет, почему
0.1 + 0.1 + 0.1 + 0.1
именно число с плавающей запятой0.4
: рациональные числа 1/10 и 4/10 аппроксимируются одинаково, с одинаковой относительной погрешностью при преобразовании в число с плавающей запятой. Эти числа с плавающей точкой имеют соотношение ровно 4 между ними. Первый и третий результаты показывают, что0.1 + 0.1 + 0.1
и0.1 + 0.1 + 0.1 + 0.1 + 0.1
можно ожидать, что в них будет меньше ошибок, чем можно было бы сделать из наивного анализа ошибок, но сами по себе они соотносят результаты только с соответственно3 * 0.1
и5 * 0.1
, что можно ожидать, будут близки, но не обязательно идентичны0.3
и0.5
.Если вы продолжите добавление
0.1
после четвертого сложения, вы, наконец, увидите ошибки округления, из-за которых «0.1
добавлено к себе n раз» на некоторое время отклоняется от двоичного кода. После этого начнется поглощение, и кривая станет плоской.n * 0.1
и еще больше расходятся от n / 10. Если бы вы наносили на график значения «0.1, добавленные к себе n раз» как функцию от n, вы бы наблюдали линии с постоянным наклоном по бинадам (как только результат n-го сложения обречен на попадание в конкретный бинар, можно ожидать, что свойства добавления будут аналогичны предыдущим добавлениям, которые дали результат в той же самой бинаде). В пределах одного и того же бинада ошибка будет увеличиваться или уменьшаться. Если бы вы посмотрели на последовательность уклонов от бинада до бинада, вы бы узнали повторяющиеся цифры0.1
источник
x + x + x
это точно правильно округленное число с плавающей запятой до реального 3 *x
. «Правильно округленный» означает «ближайший» в этом контексте.Системы с плавающей запятой производят различную магию, включая несколько дополнительных битов точности для округления. Таким образом, очень маленькая ошибка из-за неточного представления 0,1 заканчивается округлением до 0,5.
Думайте о плавающей точке как о замечательном, но НЕПРАВИЛЬНОМ способе представлять числа Не все возможные числа легко представлены в компьютере. Иррациональные числа типа Пи. Или как SQRT (2). (Символические математические системы могут представлять их, но я сказал «легко».)
Значение с плавающей запятой может быть очень близким, но не точным. Это может быть так близко, что вы могли бы перейти к Плутону и быть на миллиметре. Но все же не точный в математическом смысле.
Не используйте с плавающей точкой, когда вам нужно быть точным, а не приблизительным. Например, бухгалтерские приложения хотят точно отслеживать определенное количество копеек в учетной записи. Целые числа хороши для этого, потому что они точны. Основной проблемой, за которой нужно следить с целыми числами, является переполнение.
Использование BigDecimal для валюты работает хорошо, потому что базовое представление является целым, хотя и большим.
Признавая, что числа с плавающей запятой неточны, они все еще имеют множество применений. Системы координат для навигации или координат в графических системах. Астрономические ценности. Научные ценности. (Вы, вероятно, не можете знать точную массу бейсбола с точностью до массы электрона, так что неточность на самом деле не имеет значения.)
Для подсчета заявок (в том числе бухгалтерских) используйте целое число. Для подсчета количества людей, проходящих через ворота, используйте int или long.
источник
strictfp
). То, что вы отказались понимать что-то, не означает, что это непостижимо, и что другие не должны отказываться, чтобы понять это. См. Stackoverflow.com/questions/18496560 в качестве примера длин, на которые пойдут реализации Java для реализации определения языка (которое не включает каких-либо положений для битов дополнительной точности, а такжеstrictfp
для любого дополнительного бита exp)