Чому “SELECT POWER (10.0, 38.0);” видає арифметичну помилку переповнення?


15

Я оновлюю свій IDENTITYскрипт перевірки на переповнення для обліку DECIMALта NUMERIC IDENTITYстовпців .

У рамках перевірки я обчислював розмір діапазону типу даних для кожного IDENTITYстовпця; Я використовую це для підрахунку, який відсоток цього діапазону вичерпаний. Бо DECIMALі NUMERIC розмір цього діапазону -2 * 10^p - 2 де pточність.

Я створив купу тестових таблиць із DECIMALта NUMERIC IDENTITYстовпцями і спробував обчислити їхні діапазони наступним чином:

SELECT POWER(10.0, precision)
FROM sys.columns
WHERE 
       is_identity = 1
   AND type_is_decimal_or_numeric
;

Це призвело до такої помилки:

Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric. 

Я звузив його до IDENTITYстовпців типу DECIMAL(38, 0)(тобто з максимальною точністю), тож я спробував POWER()розрахунок безпосередньо на цьому значенні.

Усі наступні запити

SELECT POWER(10.0, 38.0);
SELECT CONVERT(FLOAT, (POWER(10.0, 38.0)));
SELECT CAST(POWER(10.0, 38.0) AS FLOAT);

також призвели до тієї ж помилки.

  • Чому SQL Server намагається перетворити результат POWER(), який є типовим FLOAT, до NUMERIC(особливо, коли FLOATмає більший пріоритет )?
  • Як я можу динамічно обчислити діапазон а DECIMALчи NUMERICстовпця для всіх можливих точок (включаючи p = 38, звичайно)?

Відповіді:


18

З POWERдокументації :

Синтаксис

POWER ( float_expression , y )

Аргументи

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

y
Чи сила, до якої можна підняти float_expression . y може бути виразом точної числової або приблизної категорії числових типів даних, за винятком бітового типу даних.

Типи повернення

Повертає той самий тип, що і представлений у float_expression . Наприклад, якщо десятковий (2,0) подається як float_expression, результат, що повертається, є десятковим (2,0).


Перший вхід неявно подається на floatнеобхідність.

Внутрішній обчислення виконується за допомогою floatарифметики за допомогою стандартної функції C Runtime Library (CRT) pow.

floatВихід з powвідливають назад до типу лівого операнда (мається на увазі, що numeric(3,1)при використанні буквальне значення 10.0).

Використання явного floatвідмінно працює у вашому випадку:

SELECT POWER(1e1, 38);
SELECT POWER(CAST(10 as float), 38.0);

Точний результат для 10 38 не може бути збережений на SQL сервері, decimal/numericоскільки для нього знадобиться 39 цифр точності (1 з наступними 38 нулями). Максимальна точність - 38.


23

Замість того, щоб більше втручатися у відповідь Мартіна, я додам решту моїх висновків щодо POWER()цього.

Тримайтеся за ваші взутті.

Преамбула

По-перше, я представляю вам виставку A, документацію MSDN щодоPOWER() :

Синтаксис

POWER ( float_expression , y )

Аргументи

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

Типи повернення

Те саме, що float_expression.

Ви можете зробити висновок, прочитавши той останній рядок, який POWER()є типом повернення FLOAT, але прочитати його ще раз. float_expressionє "float типу або типу, який можна неявно перетворити на float". Отже, незважаючи на свою назву, float_expressionнасправді може бути а FLOAT, а DECIMALчи а INT. Оскільки вихідний результат POWER()такий самий, як і вихідний float_expression, він також може бути одним із таких типів.

Отже, у нас є скалярна функція із типом повернення, які залежать від введення. Це може бути?

Спостереження

Представляю вашій увазі виставку B - тест, який демонструє, що вона POWER()видає свої результати різним типам даних залежно від вкладених даних .

SELECT 
    POWER(10, 3)             AS int
  , POWER(1000000000000, 3)  AS numeric0     -- one trillion
  , POWER(10.0, 3)           AS numeric1
  , POWER(10.12305, 3)       AS numeric5
  , POWER(1e1, 3)            AS float
INTO power_test;

EXECUTE sp_help power_test;

DROP TABLE power_test;

Відповідні результати:

Column_name    Type      Length    Prec     Scale
-------------------------------------------------
int            int       4         10       0
numeric0       numeric   17        38       0
numeric1       numeric   17        38       1
numeric5       numeric   17        38       5
float          float     8         53       NULL

Що, здається, відбувається, це те, що POWER()кидається float_expressionв найменший тип, який йому підходить, не враховуючи BIGINT.

Таким чином, SELECT POWER(10.0, 38);виходить з помилкою переповнення, тому що 10.0отримується літ, до NUMERIC(38, 1)якого недостатньо великий, щоб утримати результат 10 38 . Це тому, що 10 38 розширюється, щоб прийняти 39 цифр перед десятковою, тоді як NUMERIC(38, 1)може зберігати 37 цифр перед десятковою плюс одну після неї. Тому максимальне значення NUMERIC(38, 1)може містити 10 37 - 0,1.

Озброївшись таким розумінням, я можу придумати ще одну невдачу переливу наступним чином.

SELECT POWER(1000000000, 3);    -- one billion

Один мільярд (на відміну від мільйона триліонів з першого прикладу, який подано NUMERIC(38, 0)) достатньо малий, щоб вмістити його INT. Однак один мільярд, збільшений до третьої потужності, занадто великий INT, тому помилка переповнення.

Кілька інших функцій проявляють подібну поведінку, коли тип їх виходу залежить від їх введення:

Висновок

У цьому конкретному випадку рішення - використовувати SELECT POWER(1e1, precision).... Це спрацює з усіма можливими заходами з моменту того, як 1e1він FLOATможе бути переданий , що може вміти смішно велику кількість .

Оскільки ці функції настільки звичні, важливо розуміти, що ваші результати можуть бути округленими або можуть спричинити помилки переповнення через їх поведінку. Якщо ви очікуєте або покладаєтесь на певний тип даних для свого результату, явно подайте відповідний вхід, якщо це необхідно.

Тож діти, тепер, коли ви це знаєте, ви можете піти і процвітати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.