Откуда возникает неточность при работе с double в Java
Что проверяют
Понимаете ли вы, что неточность не баг Java, а свойство двоичного формата с плавающей точкой, и знаете ли, что с этим делать там, где считаются деньги. Вопрос дешёвый по формулировке и очень показательный по ответу.
Как отвечать
double это IEEE 754 двойной точности: знак, порядок и мантисса фиксированной ширины. Значение хранится как двоичная дробь, а десятичные дроби вроде 0.1 в двоичной системе бесконечны, поэтому в переменную попадает ближайшее представимое число, а не то, что написано в коде.
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // falseДальше скажите про накопление: каждая операция округляет результат к ближайшему представимому значению, и в длинном суммировании ошибки складываются. Вторая причина потери точности это сложение чисел очень разного порядка, когда маленькое слагаемое просто не помещается в мантиссу и пропадает целиком.
Что делать:
- деньги считать в минимальных единицах через
longлибо вBigDecimal, создавая его из строки:new BigDecimal("0.1"); - сравнивать не напрямую, а по допуску, причём для больших значений допуск берут относительный, а не фиксированный;
- округлять один раз на выводе, а не в середине расчёта.
Где спотыкаются
Создают BigDecimal из double. Конструктор new BigDecimal(0.1) честно сохранит всю двоичную погрешность, потому что на вход уже пришло неточное значение. Нужен конструктор от строки или BigDecimal.valueOf.
Сравнивают BigDecimal через equals. Этот метод учитывает масштаб, поэтому числа с разным количеством знаков после точки окажутся неравными. Для сравнения по величине есть compareTo.
Считают, что дело в печати или что спасёт float. Форматирование лишь прячет разницу, в сравнении и в сумме она никуда не денется, а у float мантисса короче, и ошибка проявляется раньше.