Модуль с удвоением в Java
Как вы относитесь к явному поведению Java при работе с модулем при использовании удвоений?
Например, вы ожидаете, что результатом 3.9 - (3.9 % 0.1)
будет 3.9
(и действительно, Google говорит, что я не сходит с ума), но когда я запускаю его на Java, я получаю 3.8000000000000003
.
Я понимаю, что это результат того, как хранилища Java и процессы удваиваются, но есть ли способ обойти его?
Ответы
Ответ 1
Используйте точный тип, если вам нужен точный результат:
double val = 3.9 - (3.9 % 0.1);
System.out.println(val); // 3.8000000000000003
BigDecimal x = new BigDecimal( "3.9" );
BigDecimal bdVal = x.subtract( x.remainder( new BigDecimal( "0.1" ) ) );
System.out.println(bdVal); // 3.9
Почему 3.8000... 003? Поскольку Java использует FPU для вычисления результата. 3.9 невозможно точно сохранить в нотации с двойной точностью IEEE, поэтому оно хранит 3,89999... вместо этого. И 3.8999% 0.01 дает 0.09999... следовательно, результат немного больше 3.8.
Ответ 2
Вы можете использовать java.math.BigDecimal и его метод divideAndRemainder().
Ответ 3
От Спецификация языка Java:
Результат с плавающей запятой остаток, рассчитанный % - это не то же самое, что произведенной в результате операции остатка определенных IEEE 754. IEEE 754 операция остатка вычисляет остаток от округления, не усекающее деление, и поэтому его поведение не аналогично обычный целочисленный оператор остатка. Вместо этого язык программирования Java определяет% на операции с плавающей запятой вести себя аналогично то целого остатка оператор; это можно сравнить с библиотечная функция C fmod. IEEE 754 может быть вычисленный библиотечной программой Math.IEEEremainder.
Другими словами, это связано с тем, что Java округляет результат деления, участвующего в вычислении остатка, тогда как IEEE754 указывает на усечение ответа деления. Этот конкретный случай, по-видимому, очень четко показывает эту разницу.
Вы можете получить ответ, который вы ожидаете, используя Math.IEEEremainder:
System.out.println(3.9 - (3.9 % 0.1));
System.out.println(3.9 - Math.IEEEremainder(3.9, 0.1));
Ответ 4
Если вы знаете количество десятичных знаков, с которыми имеете дело, вы можете попробовать сначала преобразовать в целые числа. Это просто классический случай сглаживания с плавающей запятой. Вместо выполнения 3.9 % 0.1
вы делаете что-то вроде 3.899 % 0.0999