Двойная точность путаницы? - PullRequest
1 голос
/ 29 марта 2012

Отвечая на этот вопрос, я получил эти запутанные результаты:

double d = 0.49999999999999990d; //output 0.4999999999999999 as expected
d = 0.49999999999999991d; //output 0.4999999999999999
d = 0.49999999999999992d; //output 0.49999999999999994
d = 0.49999999999999993d; //output 0.49999999999999994
d = 0.49999999999999994d; //output 0.49999999999999994 as expected
d = 0.49999999999999995d; //output 0.49999999999999994
d = 0.49999999999999996d; //output 0.49999999999999994
d = 0.49999999999999997d; //output 0.49999999999999994
d = 0.49999999999999998d; //output 0.5

Почему это поведение показывает?

ПРИМЕЧАНИЕ: я получил эти выходные данные, просто напечатав d; Я имею в виду, я использовал:

System.out.println(d);

Ответы [ 3 ]

2 голосов
/ 29 марта 2012

Типы с плавающей точкой не могут точно представлять все действительные числа. Фактически, double - это 64-битный тип с плавающей запятой, и поэтому он может представлять только 2 64 различных значений ... и существует бесконечное количество вещественных чисел. (Действительно, существует бесконечное число действительных чисел от 0.49999999999999990d до 0.49999999999999999d.)

Вы выбрали несколько чисел, которые попадают между последовательными значениями в совокупности значений double. Другими словами, вы превысили пределы точности для типа double.

Что вы можете с этим поделать? Один из способов получить больше точности - использовать класс BigDecimal, который (теоретически) может дать вам область точности около 2 МИЛЛИАРДОВ. Недостатком является то, что ваш код будет более сложным ... и значительно медленнее, в зависимости от того, какую точность вы используете.

Другой подход заключается в признании того, что вам, вероятно, не нужна такая большая точность.

1 голос
/ 29 марта 2012

System.out.println(d) будет проходить через Double.toString, что является довольно сложным методом (как видно из документации) и не всегда будет вести себя так, как вы ожидаете.Это в основном дает самую короткую строку, которая однозначно определяет d.

Возможно, результат этой программы проясняет это:

double[] tests = {
        0.49999999999999990d, //output 0.4999999999999999 as expected
        0.49999999999999991d, //output 0.4999999999999999
        0.49999999999999992d, //output 0.49999999999999994
        0.49999999999999993d, //output 0.49999999999999994
        0.49999999999999994d, //output 0.49999999999999994 as expected
        0.49999999999999995d, //output 0.49999999999999994
        0.49999999999999996d, //output 0.49999999999999994
        0.49999999999999997d, //output 0.49999999999999994
        0.49999999999999998d, //output 0.5
    };

String[] literals = {
        "0.49999999999999990d",
        "0.49999999999999991d",
        "0.49999999999999992d",
        "0.49999999999999993d",
        "0.49999999999999994d",
        "0.49999999999999995d",
        "0.49999999999999996d",
        "0.49999999999999997d",
        "0.49999999999999998d",
    };

String f = "%-25s%-65s%-25s%n";
System.out.printf(f, "Literal", "Actually represents", "Printed as");

for (int i = 0; i < tests.length; i++)
    System.out.printf(f, literals[i],
                         new BigDecimal(tests[i]).toString(), 
                         Double.valueOf(tests[i]));

Выход:

Literal                  Actually represents                                              Printed as               
0.49999999999999990d     0.49999999999999988897769753748434595763683319091796875          0.4999999999999999       
0.49999999999999991d     0.49999999999999988897769753748434595763683319091796875          0.4999999999999999       
0.49999999999999992d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999993d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999994d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999995d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999996d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999997d     0.499999999999999944488848768742172978818416595458984375         0.49999999999999994      
0.49999999999999998d     0.5                                                              0.5                      

Как видно, литерал иногда далек от значения, которое он на самом деле представляет, что означает, что Double.toString печатает что-то, что может выглядеть удивительно.

0 голосов
/ 29 марта 2012

Только определенные числа могут быть представлены в точности как doubles.В рассматриваемом диапазоне имеется три таких числа:

  • 0.49999999999999990
  • 0.49999999999999994
  • 0.5

Все между этими числами округляется до ближайшего из трех.

Если вы посмотрите, как эти числа представлены в шестнадцатеричном виде, вы увидите, что три числа имеют последовательные мантиссы (часть перед p):

In [20]: float.hex(0.49999999999999990)
Out[20]: '0x1.ffffffffffffep-2'

In [21]: float.hex(0.49999999999999994)
Out[21]: '0x1.fffffffffffffp-2'

In [22]: float.hex(0.5)
Out[22]: '0x1.0000000000000p-1'

Для представления чисел, таких как 0.49999999999999992, потребуется больше битов мантиссы, чем может предложить double.

...