Функция Math.floor не работает должным образом с двойной переменной

public class Main {

public static void main(String[] args) {
    System.out.println(areEqualByThreeDecimalPlaces(-3.1759D, -3.175D));
    System.out.println(areEqualByThreeDecimalPlaces(3.174D, 3.175D));
    System.out.println(areEqualByThreeDecimalPlaces(-3.0D, -3.0D));
}

public static boolean areEqualByThreeDecimalPlaces(double num1, double num2) {
    float f = (float) Math.abs(num1 - num2)*1000f;
    double d = Math.abs(num1 - num2)*1000d;
    //double d = 1.000000d;
    System.out.println(String.format("double: %f  float: %f%nd: %f\t\t  f: %f", Math.floor(d), Math.floor(f), d, f));

    return Math.floor(f) < 1;
}}

В приведенном выше коде я пытаюсь проверить, равны ли два двойных значения, отправленные в качестве параметров в areEqualByThreeDecimalPlaces(), с точностью до 3 десятичных знаков. В рамках этого метода я попытался понять, почему Math.floor() с двойной переменной d не работает должным образом после присвоения вычисленного значения с помощью Math.abs() * 1000d. Похоже, это сработало для переменной floatf после того, как я использовал приведение типа float на Math.abs() - это меня немного смутило. Почему функция Math.floor() не работает должным образом для переменной doubled, поскольку, похоже, она работает для переменной floatf?

Вывод:


double: 0.000000  float: 0.000000
d: 0.900000       f: 0.900000
true
double: 0.000000  float: 1.000000
d: 1.000000       f: 1.000000
false
double: 0.000000  float: 0.000000
d: 0.000000       f: 0.000000
true

Process finished with exit code 0

Это результат, который я получаю с помощью приведенного выше кода. Здесь секции double: и float: являются выходом Math.floor(d) и Math.floor(f). Неожиданное поведение происходит со вторым набором значений, отправленных main(), значениями, заданными этой командой System.out.println(areEqualByThreeDecimalPlaces(3.174D, 3.175D));. Для переменной doubled вывод должен выглядеть так же, как вывод для переменной floatf, однако Math.floor(d) возвращает 0.000000 там, где я ожидал, это будет 1.000000.

Пожалуйста, укажите в качестве результата то, что вы ожидали, и что вы получили.

Nikhil 05.01.2019 06:56

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

Hans-Martin Mosner 05.01.2019 10:35
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
192
1

Ответы 1

Замените свой последний оператор печати следующим:

System.out.println(String.format("double: %2.15f  float: %f%nd: %2.15f\t\t  f: %f", Math.floor(d), Math.floor(f), d, f));

Это печатает числа с большим количеством десятичных знаков. Тогда вы можете видеть, что d действительно меньше, чем 1. Это происходит потому, что числа с плавающей запятой (как double или float) обычно не могут точно представлять десятичные значения.

Это связано с тем, что числа с плавающей запятой внутри представлены как двоичные значения (основание 2). Таким образом, в десятичном представлении 1/5 равно 0,2, но это бесконечное число с плавающей запятой в представлении с основанием 2, например, 1/3 находится в десятичном представлении 0,333 ....

Было бы даже лучше напечатать new BigDecimal(d) вместо d, чтобы получить точное десятичное значение. %2.15f не может быть всей правдой.

Roland Illig 05.01.2019 14:20

Мне несколько неудобно последнее предложение здесь - числа с плавающей запятой являются точные значения, они просто не обязательно являются точным результатом предыдущей операции. Например, если у вас есть типы с плавающей запятой с двумя десятичными цифрами и вы попытаетесь представить 1/3, результатом будет точно 0,33, но это не совсем «одна треть». Это операции, которые являются приблизительными, а не сами значения.

Jon Skeet 05.01.2019 14:21

@Roland Illig, да, %2.15f не может быть всей правдой, но здесь этого достаточно, чтобы показать причину проблемы.

Donat 05.01.2019 14:38

%2.15f здесь хватает только по стечению обстоятельств. Это могло также привести к неправильному направлению из-за ошибок округления, что, в конце концов, и является той проблемой, которая приводит к этому вопросу. Поэтому это плохой совет, поскольку он не работает надежно в подобных ситуациях. Чтобы давать полезные ответы и получать положительные отзывы, вам лучше писать ответы, которые работают во всех ситуациях и улучшают знания того, кто задает вопрос.

Roland Illig 05.01.2019 22:44

Другие вопросы по теме