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) 前に、関数が実行されます。しかし、これは(常に?)そうではありません

その場合、精度の低下を説明できます。

科学表記法を使用する浮動小数点値の10進数または数値への変換は、精度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> 2016年10月28日18:17:30 <br>著作権(c)Microsoft Corporation <br> Express Edition(64ビット)on Windows Server 2012 R2 Standard 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> 2016年6月17日19:14:09 <br>著作権(c)Microsoft Corporation <br> Express Edition(64ビット)on Windows NT 6.3 <X64>(ビルド9600:)(ハイパーバイザー)<br> |
SELECT POWER(2.,64.);
GO
| (列名なし)|
| :------------------- |
| 18446744073709552000 |

ここに dbfiddle


1
したがって、基本的に、POWER関数は17桁を超える精度を必要とするものには役に立たない。だからこそ、正しい結果POWER(2.,56.) = 72057594037927936が得られますが、それ以上はありません。ループ内で乗算するだけのPOWER関数を作成する必要があると思います(笑)。
トリインコ

14

2 64の結果はfloatrealで言えば)正確に表現できます。

この正確な結果がnumeric(第1 POWERオペランドの型)に変換されるときに問題が発生します。

データベース互換性レベル130が導入される前、SQL Server floatnumeric暗黙的な変換に最大17桁に丸めました。

互換性レベル130では、変換中に可能な限り高い精度が維持されます。これは、サポート技術情報の記事に記載されています。

一部のデータ型と一般的でない操作の処理におけるSQL Server 2016の改善

Azure SQL Databaseでこれを利用するにはCOMPATIBILITY_LEVEL、130 に設定する必要があります。

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でそれを記述する1つの方法:

DECLARE @exponent INTEGER = 57;

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

SQL Server 2008でテストした結果は、144115188075855870ではなく144115188075855872です。

これは、指数が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 ;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.