Tочность числовых значений типа данных FLOAT

Есть таблица с типом столбца FLOAT, как определено в документации.

Вставляю число с 52 двоичными знаками (не считая начальной 1, которая является неявной), но вижу, что для хранения без округления достаточно только 50 знаков.

Как такое может быть?

create table numeric_types2 (fl 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(fl as float(49)), 'XXXXXXXXXXXXXXXXX') fl49,
    to_char (cast(fl as float(50)), 'XXXXXXXXXXXXXXXXX') fl50,
    to_char (cast(fl as float(51)), 'XXXXXXXXXXXXXXXXX') fl51
from numeric_types2;

Вывод:

FL49               FL50               FL51              
------------------ ------------------ ------------------
    10000000000004     10000000000001     10000000000001

Почему так происходит? Что-то не понимаю в математике с плавающей точкой?


Свободный перевод вопроса numeric precision in Oracle от участника @ihadanny


Ответы (1 шт):

Автор решения: 0xdb

В документации также указано:

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).

Это можно проверить, сделав дамп значений:

create table numeric_types2 (f1 float(60), n1 number)
/
insert into numeric_types2 
    with t 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 t;
    
select 'dump float' what, dump (f) dump from t union all 
select 'dump number',     dump (n)      from t union all 
select 'dump float50',    dump (cast (n as float(50))) from t union all
select 'dump float49',    dump (cast (n as float(49))) from t;

WHAT         DUMP                                            
------------ ------------------------------------------------
dump float   Typ=2 Len=9: 200,46,4,60,97,28,38,5,98          
dump number  Typ=2 Len=9: 200,46,4,60,97,28,38,5,98          
dump float50 Typ=2 Len=9: 200,46,4,60,97,28,38,5,98          
dump float49 Typ=2 Len=8: 200,46,4,60,97,28,38,6             

Заметьте, что:

dump (f) = dump (n) = dump (cast (n as float(50)))

Только приведение числа к float(49) дает другое значение.

В заключение следует упомянуть, что документация содержит рекомендацию:

Oracle Database uses the Oracle FLOAT data type internally when converting ANSI FLOAT data. 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.


Свободный перевод ответа от участника @Chris Saxon

→ Ссылка