Модуль с удвоением в 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