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보다 높은 모든 값은 0으로 반올림됩니다.

반면에 리터럴 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) Windows Server의 Microsoft Corporation <br> Express Edition (64 비트) 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) Windows NT의 Microsoft Corporation <br> Express Edition (64 비트) 6.3 <X64> (빌드 9600 :) (하이퍼 바이저) <br> |
SELECT POWER(2.,64.);
GO
| (열 이름 없음) |
| : ------------------- |
| 18446744073709552000 |

여기 dbfiddle


1
따라서 기본적으로 POWER 기능은 17 자리 이상의 정밀도가 필요한 모든 작업에는 쓸모가 없습니다. 그렇기 때문에 올바른 결과를 얻을 수 POWER(2.,56.) = 72057594037927936있지만 더 높은 결과는 없습니다. 루프에 곱하는 자체 POWER 함수를 작성해야한다고 생각합니다.
Triynko

14

2 64 의 결과는 float(그리고 real그 문제에서) 정확하게 표현할 수 있습니다.

이 정확한 결과가 numeric(첫 번째 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로 작성하는 한 가지 방법 :

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입니다.

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.