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 шт):
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