Ответ 1
Я вижу кучу вопросов, которые рассказывают вам, как обойти эту проблему, но не тот, который действительно объясняет, что происходит, кроме "ошибки округления с плавающей точкой - это плохо, m'kay?" Поэтому позвольте мне сделать снимок. Позвольте мне прежде всего отметить, что ничто в этом ответе не является специфическим для Java. Ошибка округления - это проблема, присущая любому фиксированному представлению чисел, поэтому вы получаете те же проблемы, например, в C.
Ошибка округления в десятичном типе данных
В качестве упрощенного примера предположим, что у нас есть какой-то компьютер, который изначально использует беззнаковый десятичный тип данных, пусть назовет его float6d
. Длина типа данных - 6 цифр: 4, посвященная мантиссе, и 2, посвященная экспоненте. Например, число 3.142 может быть выражено как
3.142 x 10^0
который будет храниться в 6 цифрах как
503142
Первые две цифры - показатель плюс 50, а последние четыре - мантисса. Этот тип данных может представлять любое число от 0.001 x 10^-50
до 9.999 x 10^+49
.
Собственно, это не так. Он не может хранить номер. Что делать, если вы хотите представить 3.141592? Или 3.1412034? Или 3.141488906? Жесткая удача, тип данных не может хранить более четырех цифр точности, поэтому компилятор должен округлить все с большим количеством цифр, чтобы вписаться в ограничения типа данных. Если вы пишете
float6d x = 3.141592;
float6d y = 3.1412034;
float6d z = 3.141488906;
тогда компилятор преобразует каждое из этих трех значений в одно и то же внутреннее представление, 3.142 x 10^0
(которое, помните, сохраняется как 503142
), так что x == y == z
будет иметь значение true.
Дело в том, что существует целый ряд действительных чисел, которые все сопоставляются с одной и той же базовой последовательностью цифр (или бит, на реальном компьютере). В частности, любой x
, удовлетворяющий 3.1415 <= x <= 3.1425
(предполагающий получетное округление), преобразуется в представление 503142
для хранения в памяти.
Это округление происходит каждый раз, когда ваша программа сохраняет значение с плавающей запятой в памяти. В первый раз это происходит, когда вы пишете константу в своем исходном коде, как я сделал выше, с x
, y
и z
. Это происходит снова, когда вы выполняете арифметическую операцию, которая увеличивает количество цифр точности, превышающих то, что может представлять тип данных. Любой из этих эффектов называется ошибка округления. Это может произойти несколькими способами:
-
Сложение и вычитание: если одно из значений, которые вы добавляете, имеет другой показатель от другого, вы получите дополнительные цифры точности, и если их будет достаточно, наименее значимые будут необходимо отбросить. Например, 2.718 и 121.0 - оба значения, которые могут быть точно представлены в типе данных
float6d
. Но если вы попытаетесь добавить их вместе:1.210 x 10^2 + 0.02718 x 10^2 ------------------- 1.23718 x 10^2
который округляется до
1.237 x 10^2
или 123.7, отбрасывая две цифры точности. -
Умножение: количество цифр в результате приблизительно равно количеству цифр в двух операндах. Это приведет к некоторой ошибке округления, если ваши операнды уже имеют много значащих цифр. Например, 121 x 2.718 дает вам
1.210 x 10^2 x 0.02718 x 10^2 ------------------- 3.28878 x 10^2
который округляется до
3.289 x 10^2
или 328.9, снова опустив две цифры точности.Однако полезно помнить, что если ваши операнды являются "хорошими" числами без значительных цифр, формат с плавающей запятой может, вероятно, точно представлять результат, поэтому вам не нужно иметь дело с ошибкой округления, Например, 2.3 x 140 дает
1.40 x 10^2 x 0.23 x 10^2 ------------------- 3.22 x 10^2
который не имеет проблем округления.
-
Раздел: здесь все становится беспорядочным. Подразделение в значительной степени всегда приводит к некоторой сумме ошибок округления, если только количество, которое вы делите, не является базой (в этом случае деление представляет собой просто сдвиг цифр или сдвиг бит в двоичном формате). В качестве примера возьмите два очень простых числа, 3 и 7, разделите их, и вы получите
3. x 10^0 / 7. x 10^0 ---------------------------- 0.428571428571... x 10^0
Ближайшим значением для этого числа, которое может быть представлено как
float6d
, является4.286 x 10^-1
или 0.4286, что явно отличается от точного результата.
Как мы увидим в следующем разделе, ошибка, возникающая при округлении, растет с каждой выполняемой вами операцией. Итак, , если вы работаете с "хорошими" номерами, как в вашем примере, как правило, лучше всего выполнять операции деления как можно позже, поскольку это операции, которые, скорее всего, вводят ошибку округления в вашу программу где раньше не существовало.
Анализ ошибки округления
В целом, если вы не можете предположить, что ваши цифры "хороши", ошибка округления может быть положительной или отрицательной, и очень сложно предсказать, в каком направлении она будет идти только на основе операции. Это зависит от конкретных значений. Посмотрите на этот график ошибки округления для 2.718 z
как функцию z
(все еще используя тип данных float6d
):
На практике, когда вы работаете со значениями, использующими полную точность вашего типа данных, часто проще обрабатывать ошибку округления как случайную ошибку. Рассматривая сюжет, вы можете угадать, что величина ошибки зависит от порядка величины результата операции. В этом частном случае, когда z
имеет порядок 10 -12.718 z
также имеет порядок 10 -1 поэтому это будет число формы 0.XXXX
. Максимальная ошибка округления - это половина последней цифры точности; в этом случае "последней цифрой точности" я имею в виду 0.0001, поэтому ошибка округления колеблется между -0.00005 и +0.00005. В точке, где 2.718 z
переходит в следующий порядок величины, который равен 1/2.718 = 0.3679, вы можете видеть, что ошибка округления также скачет на порядок.
Возьмите 0,0001 в качестве грубой средней относительной погрешности в значениях x
и y
. Относительная ошибка в x * y
или x / y
затем задается
sqrt(0.0001^2 + 0.0001^2) = 0.0001414
что является фактором sqrt(2)
больше относительной ошибки в каждом из отдельных значений.
Когда дело доходит до объединения операций, вы можете применить эту формулу несколько раз, один раз для каждой операции с плавающей запятой. Так, например, для z / (x * y)
относительная ошибка в x * y
составляет в среднем 0,0001414 (в этом десятичном примере), а затем относительная ошибка в z / (x * y)
равна
sqrt(0.0001^2 + 0.0001414^2) = 0.0001732
Обратите внимание, что средняя относительная ошибка растет с каждой операцией, в частности, как квадратный корень из числа умножений и делений, которые вы делаете.
Аналогично, для z / x * y
средняя относительная ошибка в z / x
равна 0,0001414, а относительная ошибка в z / x * y
равна
sqrt(0.0001414^2 + 0.0001^2) = 0.0001732
Итак, то же самое, в этом случае. Это означает, что для произвольных значений, в среднем, два выражения вводят примерно ту же ошибку. (В теории, то есть, я видел, что эти операции ведут себя по-другому на практике, но это другая история.)
Сведения о горах
Вам может быть интересно узнать, какой конкретный расчет вы представили в вопросе, а не только в среднем. Для этого анализа перейдите в реальный мир двоичной арифметики. Номера плавающей запятой в большинстве систем и языков представлены с помощью стандарта IEEE 754. Для 64-битных чисел format указывает 52 бита, посвященных мантиссе, 11 - экспоненте, а один - знаку. Другими словами, при записи в базе 2 число с плавающей запятой является значением формы
1.1100000000000000000000000000000000000000000000000000 x 2^00000000010
52 bits 11 bits
Ведущий 1
явно не хранится и составляет 53-й бит. Кроме того, вы должны отметить, что 11 бит, хранящихся для представления экспоненты, фактически являются реальным показателем плюс 1023. Например, это конкретное значение равно 7, что равно 1.75 x 2 2. Мантисса равна 1,75 в двоичном выражении или 1.11
, а показатель составляет 1023 + 2 = 1025 в двоичном выражении или 10000000001
, поэтому содержимое, хранящееся в памяти, составляет
01000000000111100000000000000000000000000000000000000000000000000
^ ^
exponent mantissa
но это не имеет большого значения.
В вашем примере также 450,
1.1100001000000000000000000000000000000000000000000000 x 2^00000001000
и 60,
1.1110000000000000000000000000000000000000000000000000 x 2^00000000101
Вы можете играть с этими значениями, используя этот конвертер или любой из многих других пользователей в Интернете.
Когда вы вычисляете первое выражение, 450/(7*60)
, процессор сначала выполняет умножение, получение 420 или
1.1010010000000000000000000000000000000000000000000000 x 2^00000001000
Затем он делит 450 на 420. Это дает 15/14, что составляет
1.0001001001001001001001001001001001001001001001001001001001001001001001...
в двоичном формате. Теперь спецификация языка Java говорит, что
Неточные результаты должны округляться до представляемого значения, ближайшего к бесконечно точному результату; если два ближайших представимых значения одинаково близки, выбирается единица с наименьшим значащим битом нуль. Это стандартный режим округления по стандарту IEEE 754, известный как от округленного до ближайшего.
а ближайшее представимое значение до 15/14 в 64-битном формате IEEE 754 -
1.0001001001001001001001001001001001001001001001001001 x 2^00000000000
что приблизительно равно 1.0714285714285714
в десятичной форме. (Точнее, это наименьшее точное десятичное значение, которое однозначно определяет это конкретное двоичное представление.)
С другой стороны, если вы сначала вычислите 450/7, результат будет 64.2857142857... или в двоичном формате,
1000000.01001001001001001001001001001001001001001001001001001001001001001...
для которого ближайшее представимое значение
1.0000000100100100100100100100100100100100100100100101 x 2^00000000110
который равен 64.28571428571429180465... Обратите внимание на изменение последней цифры бинарной мантиссы (по сравнению с точным значением) из-за ошибки округления. Разделение этого на 60 дает вам
1.000100100100100100100100100100100100100100100100100110011001100110011...
Посмотрите на конец: картина отличается! Это 0011
, которое повторяется вместо 001
, как в другом случае. Ближайшим представимым значением является
1.0001001001001001001001001001001001001001001001001010 x 2^00000000000
который отличается от другого порядка операций в последних двух битах: они 10
вместо 01
. Десятичный эквивалент равен 1.0714285714285716.
Конкретное округление, вызывающее эту разницу, должно быть ясным, если вы посмотрите на точные двоичные значения:
1.0001001001001001001001001001001001001001001001001001001001001001001001...
1.0001001001001001001001001001001001001001001001001001100110011001100110...
^ last bit of mantissa
В этом случае получается, что первый результат, численно 15/14, оказывается наиболее точным представлением точного значения. Это пример того, как уход из отдела до конца вам выгоден. Но опять же, это правило выполняется только до тех пор, пока значения, с которыми вы работаете, не используют полную точность типа данных. После того, как вы начнете работать с неточными (округленными) значениями, вы больше не защитите себя от дальнейших ошибок округления, выполнив сначала умножения.