Что плохого в этом простом «двойном» расчете?

Что не так с этим простым двойным вычислением в java?

Я знаю, что некоторые десятичные числа не могут быть правильно представлены в двоичных форматах float / double, но с переменной d3 java может без проблем хранить и отображать 2,64.

double d1 = 4.64;
double d2 = 2.0;
double d3 = 2.64;
double d4 = d1 - d2;

System.out.println("d1      : " + d1);
System.out.println("d2      : " + d2);
System.out.println("d3      : " + d3);
System.out.println("d4      : " + d4);
System.out.println("d1 - d2 : " + (d1 - d2));

Отвечать,

d1      : 4.64
d2      : 2.0
d3      : 2.64
d4      : 2.6399999999999997
d1 - d2 : 2.6399999999999997

person vi.su.    schedule 23.05.2013    source источник
comment
Абсолютно nothing wrong с этим.   -  person The Dark Knight    schedule 23.05.2013
comment
Я думаю, люди обижаются на термин «что не так?». Я просто имею в виду, что здесь происходит? :)   -  person vi.su.    schedule 23.05.2013
comment
@ vi.su. Да я тоже жду ответа :) :)   -  person Grijesh Chauhan    schedule 23.05.2013
comment
@TheDarkKnight На самом деле, что-то не так. Компьютер дает вам неправильный ответ даже с точностью машины. (Мы знаем это, поскольку машина имеет более точное представление правильного ответа.) Да, мы все знаем об этом поведении и остерегаемся от него, но утверждение «Компьютер не прав» является точным утверждением.   -  person jpmc26    schedule 23.05.2013
comment
может быть что-то с '64', потому что это 2 ^ 6 (1000000). для того же расчета, приведенного выше, если я использую «4.63», то ответ будет таким, как ожидалось. опять же, для «4.32» ответ будет «2.3200000000000003» .. :)   -  person vi.su.    schedule 23.05.2013
comment
Не думаю, что это связано с 64 или 32. См. Мой ответ об ошибках.   -  person jpmc26    schedule 23.05.2013
comment
@Raedwald Я не думаю, что это дубликат. Акцент вопроса в другом. В вопросе, который вы ссылаетесь, OP спрашивает, как это исправить. Этот вопрос спрашивает о причине. Хотя однозначно близкородственный.   -  person jpmc26    schedule 23.05.2013
comment
Ребят, это не дубликат. Я спрашиваю конкретный случай, когда java может правильно хранить и печатать число, но не может использовать его в расчетах. @raedwald   -  person vi.su.    schedule 30.05.2013
comment
@ vi.su java правильно использует номер, помните, что если ваша программа полагается на точность двойных чисел, вам нужно переосмыслить свою программу. 0,64 просто не является числом в двоичном формате, 64/100 не может быть выражено точно в двоичном формате, так же как 2/3 не могут быть выражены точно в десятичном виде.   -  person Richard Tingle    schedule 03.08.2013


Ответы (5)


Проблема

В двоичном формате 2.64 повторяется 10.10100011110101110000101000111101, другими словами, не может быть точно представлено в двоичном формате, отсюда возникает маленькая ошибка. Java проявляет к вам доброту с d3, но как только начнутся фактические вычисления, она должна вернуться к реальному представлению.

Двоичный калькулятор

Дальше больше:

2.64= 10.10100011110101110000101000111101
4.64=100.1010001111010111000010100011110 

Теперь, даже несмотря на то, что .64 одинаково в обоих случаях, он поддерживается с разной точностью, потому что 4 = 100 использует больше двойных значащих цифр, чем 2 = 10, поэтому < / em> когда вы говорите 4.64-2.0 и 2.64, 64-битное число представлено с другой ошибкой округления в обоих случаях, эта потерянная информация не может быть восстановлена ​​для окончательного ответа.

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

Никогда не предполагайте, что двойные значения являются точными (хотя их неточности микроскопические и вызваны только тем, что некоторые числа не могут быть точно выражены в двоичном формате).


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

Хотя вы всегда должны ожидать, что двойные числа будут иметь небольшие ошибки для последних нескольких десятичных знаков, было бы неправильно думать о двоичных представлениях как о плохих или хуже десятичных.

Мы все привыкли к тому, что определенные числа (например, 1/3) не могут быть точно представлены в десятичном виде, и мы принимаем, что такое число будет иметь вид 0,333333333333, а не истинное значение (которое я не могу записать без бесконечного пробела); именно в этом контексте нельзя точно выразить двоичные числа. 1/10 - это такое число, которое не может быть точно выражено в двоичном формате; это нас удивляет только потому, что мы привыкли к десятичным

person Richard Tingle    schedule 23.05.2013
comment
Потрясающий актуальный ответ. - person jpmc26; 23.05.2013
comment
100 спасибо Ричард !! :) - person Grijesh Chauhan; 23.05.2013
comment
@RicharTingle Может также сказать мне, почему мой код C ведет себя иначе, чем Java. - person Grijesh Chauhan; 23.05.2013
comment
@GrijeshChauhan Я думаю, что оператор printf использовал округление по умолчанию из 6 знаков после запятой. Округленное до этой точности 2.6399999999999997 равно 2.640000. По умолчанию Java показывает все десятичное представление. Обычно проблема возникает только в последних 1 или 2 десятичных разрядах, поэтому, если есть какое-либо округление, ошибка скрыта - person Richard Tingle; 23.05.2013
comment
@RichardTingle Отличный Ричард !, Еще раз спасибо за полезный комментарий !! - person Grijesh Chauhan; 23.05.2013
comment
+1, однако, никогда не предполагать, кажется слишком экстремальным :) Двойные значения точны тогда и только тогда, когда они точно могут быть представлены как двойные. Это гарантируется спецификацией IEEE 754 и Java. 2,0, 2,25, 2,125 - точное значение, 2,25 + 2,125 - точно 4,375 и т. Д. - person user396672; 23.05.2013
comment
@ user396672 Верно, но расчет, который должен дать 2,25 в качестве конечного результата, но содержит любой неточный шаг, сам по себе может быть неточным (например, 2,26–0,01 приблизительно равно 2,25). По общему признанию, если бы вы могли каким-то образом гарантировать, что используете только двоичные десятичные дроби на протяжении всего пути, вы могли бы это предположить. Но я не могу придумать ни одного разумного приложения, в котором это было бы правдой. - person Richard Tingle; 23.05.2013

d1 - d2 возвращает точный результат двоичной арифметики с плавающей запятой, он равен 2,6399999999999997, и поэтому он печатается. Если хотите округлить, можете сделать это во время печати.

System.out.printf("d1 - d2 : %.2f",  d4);

или с Commons-Math

d4 = Precision.round(d4, 2);
person Evgeniy Dorofeev    schedule 23.05.2013
comment
округление не проблема. пожалуйста, проверьте переменную d3. - person vi.su.; 23.05.2013
comment
Спасибо, что очень помогло. Это то, что у меня 11,6 + 76,8 + 11,6 и продолжает возвращать 99,999 .... все время. вместо 100, как у моего калькулятора. - person theo231022; 20.07.2017

В основном из-за того, что double является 64-битной плавающей точкой IEEE 754 с двойной точностью. Он не предназначен для хранения точных десятичных значений. Поэтому для точных расчетов двойники не рекомендуются. Вместо этого используйте конструктор String для BigDecimal, например :

new BigDecimal("2.64")
person Erik Pragt    schedule 23.05.2013
comment
Пожалуйста, проверьте переменную 'd3'. - person vi.su.; 23.05.2013
comment
Это правильный ответ. - person Visruth; 29.08.2020

Это потому, что ошибки во внутренних представлениях 4.64 и 2.0 конструктивно сочетаются (что означает, что они дают большую ошибку).

Технически говоря, 2.64 тоже точно не хранится. Однако есть конкретное представление, соответствующее 2,64. Однако подумайте о том, что 4.64 и 2.0 хранятся не совсем точно. Ошибки в 4.64 и 2.0 объединяются, чтобы произвести еще большую ошибку, достаточно большую, чтобы их вычитание не дало представления 2,64.

Ответ отклоняется на 3 * 10 ^ -16. Чтобы дать какой-то пример того, как это может происходить, давайте представим, что представление для 4.64 слишком мало на 2 * 10 ^ -16, а представление для 2.0 слишком велико на 1 * 10 ^ -16. Тогда вы получите

(4.64 - 2*10^-16) - (2.0 + 1*10^-16) = 2.64 - 3*10^-16

Таким образом, когда расчет завершен, две ошибки объединились, чтобы создать еще большую ошибку. Но если представление для 2,64 отключено только на 1 * 10 ^ -16, то это не будет считаться компьютером равным 2,64.

Также возможно, что 4.64 просто имеет большую ошибку, чем 2.64, даже если 2.0 не имеет ошибки. Если представление 4.64 на 3 * 10 ^ -16 слишком мало, вы получите то же самое:

(4.64 - 3*10^-16) - 2.0 = 2.64 - 3*10^-16

Опять же, если представление 2,64 отключено только на 1 * 10 ^ -16, то этот результат не будет считаться равным 2,64.

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

person jpmc26    schedule 23.05.2013
comment
не уверен, но я думаю, что 4.64 -2 и 2.64 хранятся в памяти одинаково, тогда почему нечеткий вывод? + за ответ .. - person Grijesh Chauhan; 23.05.2013
comment
@GrijeshChauhan Я изложил свой ответ. Надеюсь, теперь в этом есть больше смысла. - person jpmc26; 23.05.2013
comment
@ jpmc26, спасибо, проголосовало "за". - person vi.su.; 23.05.2013

В этом нет ничего плохого. Но попробуйте использовать BigDecimal

http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html

Примечание: double и float внутренне представлены как двоичные дроби в соответствии со стандартом IEEE 754 и поэтому не могут точно представлять десятичные дроби.

person Scary Wombat    schedule 23.05.2013
comment
мое голосование ожидается, пока вы не объясните ответ :) - person Grijesh Chauhan; 23.05.2013
comment
BigDecimal для 2.64 ?? :) - person vi.su.; 23.05.2013
comment
Потому что double и float внутренне представлены как двоичные дроби в соответствии со стандартом IEEE 754 и поэтому не могут точно представлять десятичные дроби. mindprod.com/jgloss/floatingpoint.html math.byu.edu/~schow/work / ... atingPoint.htm - person Scary Wombat; 23.05.2013
comment
@ vi.su Да. Неужели вас перепутали с BigInteger? - person Erik Pragt; 23.05.2013
comment
@ErikPragt, нет. когда он может хранить и использовать переменную d3, почему он не работает для того же значения из других вычислений? - person vi.su.; 23.05.2013
comment
@ user2310289 + за красивые ссылки ... - person Grijesh Chauhan; 23.05.2013
comment
@ vi.su некоторые числа с плавающей запятой могут быть представлены точно, а то же самое - нет. - person Scary Wombat; 23.05.2013
comment
@ user2310289 Это правда, что некоторые числа с плавающей запятой могут быть представлены точно, а некоторые - нет, но, похоже, мы имеем дело не с этим. В этом вопросе результат вычисления менее точен, чем наиболее точное внутреннее представление правильного результата. Также я это vi.su. подразумевал, что использование BigDecimal для такого небольшого числа кажется странным. - person jpmc26; 23.05.2013
comment
@ jpmc26 Вы абсолютно правы, и ваше объяснение ниже великолепно. Но вопрос был в том, что не так с этим простым "двойным" вычислением в java? - person Scary Wombat; 23.05.2013