Зачем нужна математика.круглый(0.49999999999999994) возвращает 1?



в следующей программе вы можете видеть, что каждое значение немного меньше, чем .5 округляется вниз, за исключением 0.5.



for (int i = 10; i >= 0; i--) {
long l = Double.doubleToLongBits(i + 0.5);
double x;
do {
x = Double.longBitsToDouble(l);
System.out.println(x + " rounded is " + Math.round(x));
l--;
} while (Math.round(x) > i);
}


печать



10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0


Я использую Java 6 обновление 31.

657   5  

5 ответов:

резюме

в Java 6 (и, вероятно, раньше), round(x) реализуется как floor(x+0.5).1 это ошибка спецификации, для именно этого патологического случая.2 Java 7 больше не требует этой сломанной реализации.3

проблема

0.5+0.49999999999999994 точно 1 в двойной точности:

static void print(double d) {
    System.out.printf("%016x\n", Double.doubleToLongBits(d));
}

public static void main(String args[]) {
    double a = 0.5;
    double b = 0.49999999999999994;

    print(a);      // 3fe0000000000000
    print(b);      // 3fdfffffffffffff
    print(a+b);    // 3ff0000000000000
    print(1.0);    // 3ff0000000000000
}

это потому, что 0.49999999999999994 имеет меньший показатель степени, чем 0,5, поэтому, когда они добавляются, его мантисса смещается, и ULP становится больше.

решение

начиная с Java 7, OpenJDK (например) реализует его таким образом:4

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5
        return (long)floor(a + 0.5d);
    else
        return 0;
}

1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29

2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (кредиты @SimonNickerson для поиска этого)

3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29

4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29

Это, кажется, известная ошибка ( ошибка Java 6430675: математика.раунд имеет удивительное поведение для 0x1.ффффффффффффф-2), который был исправлен в Java 7.

исходный код в JDK 6:

public static long round(double a) {
    return (long)Math.floor(a + 0.5d);
}

исходный код в JDK 7:

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) {
        // a is not the greatest double value less than 0.5
        return (long)Math.floor(a + 0.5d);
    } else {
        return 0;
    }
}

когда значение 0.49999999999999994 Д в JDK 6, он будет вызывать пол и, следовательно, возвращает 1, но в JDK 7,if условие проверяет, является ли число наибольшим двойным значением меньше 0,5 или нет. Поскольку в этом случае число не является наибольшим двойным значением меньше 0,5, поэтому else блок возвращает 0.

вы можете попробовать 0.49999999999999999 d, который вернет 1, но не 0, потому что это наибольшее двойное значение меньше 0,5.

у меня есть то же самое на JDK 1.6 32-бит, но на Java 7 64-бит у меня есть 0 для 0.49999999999999994 который округляется 0 и последняя строка не печатается. Кажется, это проблема с виртуальной машиной, однако, используя плавающие точки, вы должны ожидать, что результаты будут немного отличаться в разных средах (CPU, 32 - или 64-разрядный режим).

и, при использовании round или инвертирование матриц и т. д. эти бита может сделать огромную разницу.

x64 вывод:

10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 0

ответ ниже-это отрывок из Oracleсообщение об ошибке 6430675 at. Посетите отчет для получения полного объяснения.

методы {математика, в отличие от strictmath.круглые операционно определяются как

(long)Math.floor(a + 0.5d)

для двойных аргументов. Хотя это определение обычно работает так, как ожидалось, оно дает удивительный результат 1, а не 0, для 0x1.fffffffffffffp-2 (0.49999999999999994).

значение 0.49999999999999994-это наибольшее значение с плавающей запятой менее 0,5. Как шестнадцатеричный литерал с плавающей запятой его значение равно 0x1.fffffffffffffp-2, которая равна (2 - 2^52) * 2^-2. == (0.5 - 2^54). Поэтому точное значение суммы

(0.5 - 2^54) + 0.5

это 1 - 2^54. Это на полпути между двумя соседними числами с плавающей запятой (1-2^53) и 1. В арифметическом раунде IEEE 754 до ближайшего четного режима округления, используемого Java, когда результаты с плавающей запятой неточны, чем ближе два представимых значения с плавающей запятой, которые заключают точный результат в скобки, должны быть возвращены; если оба значения одинаково близки, возвращается тот, который его последний бит равен нулю. В этом случае правильное возвращаемое значение из add равно 1, а не наибольшее значение меньше 1.

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

Comments

    Ничего не найдено.