Плохой результат от оператора Java по модулю? - PullRequest
4 голосов
/ 16 августа 2011

Я подозреваю, что об этом уже спрашивали, но не могу найти вопрос, который соответствует ...

Я использую Scala, но я уверен, что это просто проблема с Java ... входные значения удваиваются

println(28.0 / 5.6) 
println(28.0 % 5.6)

Результат этих строк

5.0 
1.7763568394002505E-15 

Это означает, что Java правильно выполняет деление, но по какой-то причине неправильно использует модуль по модулю, поскольку модуль должен быть равен 0 для любой проблемы деления, которая разрешается до целого числа ...

Есть ли обходной путь для этого?

Спасибо!

Ответы [ 4 ]

13 голосов
/ 16 августа 2011

5.0 просто показывает, что результат точный , поскольку Java понимает, что он ближе к 5.0, чем к любому другому двойнику. Это не означает, что точный результат операции равен точно 5.

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

Это не очень хорошее объяснение, но представьте, что у вас был десятичный тип с плавающей точкой с точностью до 4 цифр. Каков результат 1000 / 99,99 и 1000% 99,99?

Ну, реальный результат начинается с 10.001001 - так что вы должны округлить его до 10.00. Тем не менее, остаток 0,10, который вы можете выразить. Итак, еще раз, это выглядит как разделение дает вам целое число, но это не вполне .

Имея это в виду, имейте в виду, что ваш литерал 5,6 на самом деле 5.5999999999999996447286321199499070644378662109375. Теперь ясно, что 28.0 (который * может) быть представлен точно разделенным на это число, не совсем 5.

РЕДАКТИРОВАТЬ: Теперь, если вы выполните результат с десятичной арифметикой с плавающей запятой с использованием BigDecimal, значение действительно равно точно 5,6, и проблем нет:

import java.math.BigDecimal;

public class Test {
    public static void main(String[] args) {
        BigDecimal x = new BigDecimal("28.0");
        BigDecimal y = new BigDecimal("5.6");

        BigDecimal div = x.divide(y);
        BigDecimal rem = x.remainder(y);

        System.out.println(div); // Prints 5
        System.out.println(rem); // Prints 0.0
    }
}
3 голосов
/ 16 августа 2011

Результат на самом деле не равен 0, но он довольно близок (0.00000000000000177635 ...).Проблема в том, что некоторые десятичные числа не могут быть представлены точно в двоичном виде, так что вот в чем проблема;Я подозреваю, что тот же результат будет напечатан в C / C ++.

2 голосов
/ 16 августа 2011

арифметика

Прежде всего, десятичное число 5.6 не может быть точно представлено в двоичной форме с плавающей точкой. Округлено до точной двоичной дроби 3152519739159347/2 49 .

Тот факт, что 28,0 / 5,6 = 5,0, объясняется тем, что 5,0 является ближайшим double числом к ​​истинному результату, где истинный результат равен 5,0000000000000003172065784643 ....

Что касается 28,0% 5.6, истинный результат равен точно 1/2 49 , что приблизительно равно 1.776 × 10 & минус 15 , поэтому расчет правильно округлен.

Обходные

Зачем вам обходной путь? Для большинства приложений хорошо держать слегка ошибочный результат. Вы обеспокоены отображением "симпатичных" результатов?

Если вам нужна абсолютно точная арифметика, вам нужно будет использовать некоторую реализацию BigFraction.

Дальнейшее чтение

Тема предостережений с плавающей точкой освещалась в различных статьях:

(в порядке убывания читаемости.)

0 голосов
/ 27 ноября 2012

Это мое решение для проверки, если двойное значение делится на другое с использованием оператора по модулю:

public class DoubleOperation
{
    public static final double EPSILON = 0.000001d;

    public static boolean equals(double val1, double val2)
    {
        return (Math.abs(val1 - val2) < EPSILON);
    }

    public static boolean divisible(double dividend, double divisor)
    {
        double divisionRemainder = dividend % divisor;
        return (equals(divisionRemainder, 0.0d) || equals(divisionRemainder, divisor));
    }
}
...