Чому TSQL повертає неправильне значення для POWER (2., 64.)?


14

select POWER(2.,64.)повертається 18446744073709552000замість 18446744073709551616. Здається, має лише 16 цифр точності (округлення 17-ї).

Навіть зробивши точність явною, select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))вона все одно повертає округлий результат.

Це здається досить базовою операцією, щоб вона довільно луснула на 16-ти цифрах точності, як це. Найвищий, який він може правильно розрахувати, - лише POWER(2.,56.)невдалий POWER(2.,57.). Що тут відбувається?

Що насправді жахливо, це те, що select 2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.;насправді повертає потрібне значення. Стільки для лаконічності.


Відповіді:


17

З онлайнової документації :

POWER ( float_expression , y )  

Аргументи

float_expression - це вираз float типу або типу, який можна неявно перетворити на float

Це означає, що все, що ви передаєте в якості першого параметра, буде імпліцитно передано а float(53) перед виконанням функції. Однак це не завжди (завжди?) .

Якби це було так, це пояснювало б втрату точності:

Перетворення значень поплавця, які використовують наукові позначення, у десятковий чи числовий, обмежується значеннями точності лише 17 цифр. Будь-яке значення з точністю вище 17 раундів до нуля.

З іншого боку, буквальне 2.є типом numeric…:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (Без назви стовпців) |
| : --------------- |
| числовий |

dbfiddle тут

… І оператор множення повертає тип даних аргументу з більш високим пріоритетом .

Здається, що в 2016 році (SP1) зберігається вся точність:

SELECT @@version;
GO
| (Без назви стовпців) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 жовтня 2016 18:17:30 <br> Авторські права (c) Microsoft Corporation - Express Edition (64-розрядні) на Windows Server 2012 р2 стандарт 6.3 <X64> (збірка 9600:) (гіпервізор) <br> |
SELECT POWER(2.,64.);
GO
| (Без назви стовпців) |
| : ------------------- |
| 18446744073709551616 |

dbfiddle тут

… Але у 2014 році (SP2) вони не:

SELECT @@version;
GO
| (Без назви стовпців) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 червня 2016 19:14:09 <br> Авторські права (c) Microsoft Corporation - Express Edition (64-розрядна) для Windows NT 6.3 <X64> (збірка 9600:) (Гіпервізор) <br> |
SELECT POWER(2.,64.);
GO
| (Без назви стовпців) |
| : ------------------- |
| 18446744073709552000 |

dbfiddle тут


1
Таким чином, функція POWER марна для нічого, що вимагає більш ніж 17 цифр точності. Ось чому це дає правильний результат для, POWER(2.,56.) = 72057594037927936але не вище. Я думаю, мені доведеться написати власну функцію POWER, яка просто множиться в циклі, lol.
Трайнко

14

Результат 2 64 точно представлений в floatrealз цього приводу).

Проблема виникає при перетворенні цього точного результату numeric(тип першого POWERоперанда).

Перед тим, як було введено рівень сумісності баз даних 130, SQL Server округлювався floatдо numericнеявних перетворень до максимум 17 цифр.

Під рівнем 130 сумісності під час конверсії зберігається якомога більша точність. Це задокументовано у статті бази знань:

Вдосконалення SQL Server 2016 в обробці деяких типів даних і нечастотних операцій

Щоб скористатися цим у базі даних SQL Azure, вам потрібно встановити значення COMPATIBILITY_LEVEL130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

Тестування робочого навантаження потрібне, оскільки нова угода не є панацеєю. Наприклад:

SELECT POWER(10., 38);

... слід нанести помилку, оскільки 10 38 не можна зберігати numeric(максимальна точність 38). Помилка переповнення призводить до сумі 120 сумісності, але результат під 130 - це:

99999999999999997748809823456034029568 -- (38 digits)

2

Трохи математики ми можемо знайти вирішення. Для непарних n:

2 ^ n 
= 2 ^ (2k + 1)
= 2 * (2 ^ 2k)
= 2 * (2 ^ k) * (2 ^ k)

Для рівних n:

2 ^ n 
= 2 ^ (2k)
= 1 * (2 ^ 2k)
= 1 * (2 ^ k) * (2 ^ k)

Один із способів записати це в T-SQL:

DECLARE @exponent INTEGER = 57;

SELECT (1 + @exponent % 2) * POWER(2., FLOOR(0.5 * @exponent)) * POWER(2., FLOOR(0.5 * @exponent));

Випробувано на SQL Server 2008, результат - 144115188075855872 замість 144115188075855870.

Це працює аж до показника 113. Схоже, NUMERIC (38,0) може зберігати до 2 ^ 126, тому покриття не зовсім, але формула може бути розбита на більше частин, якщо потрібно .


0

Для розваги рекурсивне рішення CTE:

with 
  prm (p, e) as           -- parameters, to evaluate: p**e
    (select 2, 64),       -- (2 ** 64)  
  pow (power, exp) as 
    (select cast(p as numeric(30,0)), 
            e
     from prm 
     union all 
     select cast(power * power * (case when exp % 2 = 0 then 1 else p end) 
                 as numeric(30,0)), 
            exp / 2 
     from prm, pow 
     where exp > 1 
    ) 
select power 
from pow 
where exp = 1 ;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.