Oracle 中的双精度值与 Java (IEEE 754) 之间的区别

Difference between double value in Oracle and Java (IEEE 754)

Oracle 中的

BINARY_DOUBLE 和 Java 中的 Double 使用 IEEE 754 标准。

但是他们的准确度是有区别的

例如值:

456.67d

甲骨文:

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

Result: 4.56670000000000020000E+02

Java:

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

Result: 4.566700000000000000000E002

Java 中的值不如 Oracle 中的值准确。

在线转换器表示 456.67d 的最准确表示是:

4.56670000000000015916157281026E2

那么为什么 Java 中的准确性与 Oracle 中的不一样? 以及如何获得 Java 更准确的值?

使用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都没有关系,还是一样的浮点数。