Oracle 中的数字精度

numeric precision in Oracle

我有一个 table 和 FLOAT 列,如 Oracle docs 中所定义。 我插入一个有 52 个二进制数字的数字(不包括隐含的初始 1),但我看到 oracle 只需要 50 个数字就可以正确存储它。怎么可能?

create table numeric_types2(
    f1 float(60)
);
insert into numeric_types2 SELECT BIN_TO_NUM(
    1,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,1
                                      ) FROM DUAL;

然后:

select to_char(cast(f1 as float(49)), 'XXXXXXXXXXXXXXXXX'),
       to_char(cast(f1 as float(50)), 'XXXXXXXXXXXXXXXXX'),
       to_char(cast(f1 as float(51)), 'XXXXXXXXXXXXXXXXX')
from root.numeric_types2;

return:

10000000000004,    10000000000001,    10000000000001

这是为什么?我错过了一些基本的浮点数学吗?

链接的文档页面说

A FLOAT value is represented internally as NUMBER

莫非二进制位数四舍五入到十进制位数?

另外,换算成十进制,十六进制数为:

10000000000001 -> 4503599627370497
10000000000004 -> 4503599627370500

所以在我看来,数字在内部被转换为十进制表示形式,四舍五入为 14 或 15 位十进制数字 - 497 将四舍五入为 500。

请注意,在拉丁语中,数字以 10 为底(digitus = finger)。

docs also state:

The FLOAT data type is a subtype of NUMBER

所以这是一个 number 幕后和

To convert from binary to decimal precision, multiply n by 0.30103

插入数字:

49 * 0.30103 = 14.75047
50 * 0.30103 = 15.05150
51 * 0.30103 = 15.65356

所以float(50)float(51)对应number(16),而float(49)number(15)

您可以通过取 dump 个值来验证这一点:

create table numeric_types2 (
    f1 float(60), n1 number
);
insert into numeric_types2 
with rws as (
  select BIN_TO_NUM(
    1,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,1) n from dual
)
  select n, n from rws;

select dump ( f1 ), dump ( n1 ), 
       dump ( cast ( n1 as float(50) ) ) df50, 
       dump ( cast ( n1 as float(49) ) ) df49
from   numeric_types2;

DUMP(F1)                                 DUMP(N1)                                 DF50                                     DF49                                  
Typ=2 Len=9: 200,46,4,60,97,28,38,5,98   Typ=2 Len=9: 200,46,4,60,97,28,38,5,98   Typ=2 Len=9: 200,46,4,60,97,28,38,5,98   Typ=2 Len=8: 200,46,4,60,97,28,38,6    

注意

dump ( f1 ) = dump ( n1 ) = dump ( cast ( n1 as float(50) ) )

仅将数字转换为 float(49) 会给出不同的值。

最后请注意,文档还包含此建议:

Oracle FLOAT is available for you to use, but Oracle recommends that you use the BINARY_FLOAT and BINARY_DOUBLE data types instead, as they are more robust

如前所述,这是因为此类型下有一个 number 实现。正如您可以使用以下查询检查的那样,二进制精度被完全忽略:

  1. 二进制精度四舍五入向上到小数精度N.
  2. 该数字在数学上四舍五入到 N 个重要位置(例如,对于 N = 2:101 变为 100,106 变为 110)。
  3. 数字以 number 格式存储,具有第 2 步中的前 N ​​个有效数字,并按原始数字的小数位数缩放(例如,结果具有相同的长度,但 N 个有效数字)。
with a as (
  select level as l,
    cast(level as float(1)) as c,
    cast(level as float(2)) as c2,
    cast(level as float(3)) as c3,
    cast(level as float(4)) as c4,
    cast(level as float(6)) as c6,
    cast(level as float(7)) as c7
  from dual
  connect by level < 121
)
select a.*,
  dump(c, 16) as cd,
  dump(c4, 16) as c4d,
  dump(power(10, l), 16) as scale
from a

db<>fiddle 这里

如您所见,对于精度 1-3,您只能存储一位数,对于 4-6 - 两位数,等等。您还可以观察到,该比例每两位小数变化一次([ 中的前两个字节) =13=],列 scale)。

所以存储是:两个字节用于比例,最多 20 个字节用于精度,其中每个字节包含从 0 到 99 的数字。但我无法弄清楚他们在哪里丢失了精度为 38 (19) 的两位数字*2, 应该是 20).