Разница между двойным значением в Oracle и Java (IEEE 754)

BINARY_DOUBLE в Oracle и Double в Java используют стандарт IEEE 754.

Но есть разница в их точности.

Например значение:

456.67d

Oracle:

declare
  a BINARY_DOUBLE := 456.67d;
begin
  SYS.DBMS_OUTPUT.PUT_Line(TO_CHAR(a,'9.99999999999999999999EEEE'));
end;

Результат: 4.56670000000000020000E+02

Джава:

Double d = 456.67d;
DecimalFormat formatter = new DecimalFormat("0.000000000000000000000E000");
System.out.println(formatter.format(d));

Результат: 4.566700000000000000000E002

Значение в Java не так точно, как в Oracle.

Онлайн-конвертеры сообщили, что наиболее точным представлением для 456.67d является:

4.56670000000000015916157281026E2

Так почему же в Java точность не такая, как в Oracle? И как я могу получить в Java более точное значение?

2 ответа

Решение

Использовать BigDecimal:

Double d = 456.67d;
BigDecimal bd = new BigDecimal( d );
bd.setScale( 50 );
System.out.println( bd );
DecimalFormat formatter = new DecimalFormat("0.000000000000000000000E000");
System.out.println( formatter.format( bd ) );

Выход:

456.67000000000001591615728102624416351318359375
4.566700000000000159162E002

Оба точны. Они просто используют другую стратегию для печати.

Вот точные значения, напечатанные с помощью Squeak Smalltalk (но язык не имеет значения, это то же представление и та же арифметика):

d := 456.67.
{
    d predecessor asFraction printShowingMaxDecimalPlaces: 50.
    d asFraction printShowingMaxDecimalPlaces: 50.
    d successor asFraction printShowingMaxDecimalPlaces: 50}.

->

#(
    '456.6699999999999590727384202182292938232421875'
    '456.67000000000001591615728102624416351318359375'
    '456.670000000000072759576141834259033203125'
)    

Так что любое десятичное представление между (d predecessor asFraction + d asFraction) / 2 printShowingMaxDecimalPlaces: 50. а также (d successor asFraction + d asFraction) / 2 printShowingMaxDecimalPlaces: 50. будет округлено до того же значения с плавающей запятой (исключены границы, поскольку точная связь округляется до ближайшего четного, а это число имеет нечетное значениеи...)

Итак, границы:

#(
    '456.669999999999987494447850622236728668212890625'
    '456.670000000000044337866711430251598358154296875'
)

Oracle просто использует 17 цифр и не беспокоится о дальнейших цифрах, потому что известно, что 17 достаточно для различения любых двух значений с плавающей запятой двойной точности.

Java немного более умна: она использует только достаточно цифр, чтобы отличать их от следующих представимых значений с плавающей запятой. Таким образом, размещение 0, 1, 2, 3 или 4 в позиции 17 или даже 4.5666999999999999e2 не имеет значения, это то же самое значение с плавающей запятой.

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