programing

SQL에서 199.96 - 0 = 200인 이유는 무엇입니까?

itmemos 2023. 7. 6. 21:51
반응형

SQL에서 199.96 - 0 = 200인 이유는 무엇입니까?

고객들이 이상한 청구서를 받고 있어요.핵심 문제를 분리할 수 있었습니다.

SELECT 199.96 - (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))) -- 200 what the?
SELECT 199.96 - (0.0 * FLOOR(1.0 * CAST(199.96 AS DECIMAL(19, 4)))) -- 199.96
SELECT 199.96 - (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * 199.96)) -- 199.96

SELECT 199.96 - (CAST(0.0 AS DECIMAL(19, 4)) * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))) -- 199.96
SELECT 199.96 - (CAST(0.0 AS DECIMAL(19, 4)) * FLOOR(1.0 * CAST(199.96 AS DECIMAL(19, 4))))                         -- 199.96
SELECT 199.96 - (CAST(0.0 AS DECIMAL(19, 4)) * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * 199.96))                         -- 199.96

-- It gets weirder...
SELECT (0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))) -- 0
SELECT (0 * FLOOR(1.0 * CAST(199.96 AS DECIMAL(19, 4))))                         -- 0
SELECT (0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * 199.96))                         -- 0

-- so... ... 199.06 - 0 equals 200... ... right???
SELECT 199.96 - 0 -- 199.96 ...NO....

여기서 무슨 일이 일어나고 있는지 아는 사람이 있나요?제 말은, 확실히 십진법 데이터 유형과 관련이 있지만, 제가 머리를 싸매고 있을 수는 없습니다.


리터럴 숫자가 어떤 데이터 유형인지에 대해 많은 혼란이 있었습니다. 그래서 저는 실제 선을 보여주기로 결정했습니다.

PS.SharePrice - (CAST((@InstallmentCount - 1) AS DECIMAL(19, 4)) * CAST(FLOOR(@InstallmentPercent * PS.SharePrice) AS DECIMAL(19, 4))))

PS.SharePrice DECIMAL(19, 4)

@InstallmentCount INT

@InstallmentPercent DECIMAL(19, 4)

각 작업의 결과가 다음과 다른 유형의 피연산자를 갖도록 했습니다.DECIMAL(19, 4)외부 컨텍스트에 적용하기 전에 명시적으로 캐스팅됩니다.

그럼에도 불구하고, 그 결과는 남아있습니다.200.00.


이제 컴퓨터에서 실행할 수 있는 졸인 샘플을 만들었습니다.

DECLARE @InstallmentIndex INT = 1
DECLARE @InstallmentCount INT = 1
DECLARE @InstallmentPercent DECIMAL(19, 4) = 1.0
DECLARE @PS TABLE (SharePrice DECIMAL(19, 4))
INSERT INTO @PS (SharePrice) VALUES (599.96)

-- 2000
SELECT
  IIF(@InstallmentIndex < @InstallmentCount,
  FLOOR(@InstallmentPercent * PS.SharePrice),
  1999.96)
FROM @PS PS

-- 2000
SELECT
  IIF(@InstallmentIndex < @InstallmentCount,
  FLOOR(@InstallmentPercent * CAST(599.96 AS DECIMAL(19, 4))),
  1999.96)
FROM @PS PS

-- 1996.96
SELECT
  IIF(@InstallmentIndex < @InstallmentCount,
  FLOOR(@InstallmentPercent * 599.96),
  1999.96)
FROM @PS PS

-- Funny enough - with this sample explicitly converting EVERYTHING to DECIMAL(19, 4) - it still doesn't work...
-- 2000
SELECT
  IIF(@InstallmentIndex < @InstallmentCount,
  FLOOR(@InstallmentPercent * CAST(199.96 AS DECIMAL(19, 4))),
  CAST(1999.96 AS DECIMAL(19, 4)))
FROM @PS PS

이제 내게 뭔가...

-- 2000
SELECT
  IIF(1 = 2,
  FLOOR(CAST(1.0 AS decimal(19, 4)) * CAST(199.96 AS DECIMAL(19, 4))),
  CAST(1999.96 AS DECIMAL(19, 4)))

-- 1999.9600
SELECT
  IIF(1 = 2,
  CAST(FLOOR(CAST(1.0 AS decimal(19, 4)) * CAST(199.96 AS DECIMAL(19, 4))) AS INT),
  CAST(1999.96 AS DECIMAL(19, 4)))

도대체 바닥이 정수를 반환해야 하는 거지.이게 무슨 일입니까? :-D


저는 이제 그것을 본질적으로 요약할 수 있었다고 생각합니다 :-D.

-- 1.96
SELECT IIF(1 = 2,
  CAST(1.0 AS DECIMAL (36, 0)),
  CAST(1.96 AS DECIMAL(19, 4))
)

-- 2.0
SELECT IIF(1 = 2,
  CAST(1.0 AS DECIMAL (37, 0)),
  CAST(1.96 AS DECIMAL(19, 4))
)

-- 2
SELECT IIF(1 = 2,
  CAST(1.0 AS DECIMAL (38, 0)),
  CAST(1.96 AS DECIMAL(19, 4))
)

먼저 포장을 풀고 상황을 확인해야 합니다.

SELECT 199.96 - 
    (
        0.0 * 
        FLOOR(
            CAST(1.0 AS DECIMAL(19, 4)) * 
            CAST(199.96 AS DECIMAL(19, 4))
        )
    ) 

이제 감산 작업의 각 측면에 대해 SQL Server가 사용하는 유형을 정확하게 살펴보겠습니다.

SELECT  SQL_VARIANT_PROPERTY (199.96     ,'BaseType'),
    SQL_VARIANT_PROPERTY (199.96     ,'Precision'),
    SQL_VARIANT_PROPERTY (199.96     ,'Scale')

SELECT  SQL_VARIANT_PROPERTY (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))  ,'BaseType'),
    SQL_VARIANT_PROPERTY (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))  ,'Precision'),
    SQL_VARIANT_PROPERTY (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4)))  ,'Scale')

결과:

숫자 52숫자 381

그렇게199.96이라numeric(5,2) 더 긴 고리더오래그.Floor(Cast(etc))이라numeric(38,1).

감산 작업의 결과 정밀도 및 척도에 대한 규칙(즉,e1 - e2) 다음과 같이 표시됩니다.

정밀도: max(s1, s2) + max(p1-s1, p2-s2) + 1
척도: max(s1, s2)

이는 다음과 같이 평가됩니다.

정밀도 : max(1,2) + max(38-1,5-2) + 1 => 2 + 37 + 1 => 40
척도: max(1,2) => 2

또한 규칙 링크를 사용하여 위치를 확인할 수 있습니다.numeric(38,1)즉, 두 정밀도 19 값을 곱한 값입니다.

그러나:

  • 결과 정밀도와 척도의 절대 최대값은 38입니다.결과 정밀도가 38보다 크면 38로 감소하고, 결과의 적분 부분이 잘리는 것을 방지하기 위해 해당 스케일이 감소합니다.곱셈 또는 나눗셈과 같은 경우에는 오버플로 오류가 발생할 수 있지만 소수점 정밀도를 유지하기 위해 스케일 팩터가 감소하지 않습니다.

아, 정확도가 40입니다.우리는 그것을 줄여야 합니다. 그리고 정밀도를 낮추면 항상 최소 유효 자릿수를 잘라내야 하기 때문에 규모를 줄이는 것을 의미합니다.식의 최종 결과 유형은 로 반올림됩니다.

이동 및 통합을 통해 이 문제를 해결할 수 있습니다.CAST()큰 표현의 안쪽에서 하나의 표현으로의 조작 CAST()전체 표현 결과를 중심으로 합니다.그래서 이것은:

SELECT 199.96 - 
    (
        0.0 * 
        FLOOR(
            CAST(1.0 AS DECIMAL(19, 4)) * 
            CAST(199.96 AS DECIMAL(19, 4))
        )
    ) 

다음이 됩니다.

SELECT CAST( 199.96 - ( 0.0 * FLOOR(1.0 * 199.96) ) AS decimial(19,4))

바깥쪽 깁스도 제거할 수 있을 것 같아요.

우리는 여기서 예상되는 결과보다는 실제 현재 우리가 가지고 있는 정확성과 규모에 맞는 유형을 선택해야 한다는 것을 배웁니다.SQL Server는 오버플로를 방지하기 위해 산술 연산 중에 이러한 유형을 변형시키기 때문에 큰 정밀도 수치만 계산하는 것은 의미가 없습니다.


추가 정보:

다음 문장에 관련된 데이터 유형을 주의 깊게 살펴봅니다.

SELECT 199.96 - (0.0 * FLOOR(CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4))))
  1. NUMERIC(19, 4) * NUMERIC(19, 4)이라NUMERIC(38, 7)(아래 참조)
    • FLOOR(NUMERIC(38, 7))이라NUMERIC(38, 0)(아래 참조)
  2. 0.0이라NUMERIC(1, 1)
    • NUMERIC(1, 1) * NUMERIC(38, 0)이라NUMERIC(38, 1)
  3. 199.96이라NUMERIC(5, 2)
    • NUMERIC(5, 2) - NUMERIC(38, 1)이라NUMERIC(38, 1)(아래 참조)

이것이 당신이 왜 그렇게 되었는지 설명합니다.200.0(0이 아닌 10진수 에 한 자리) 대신199.96.

주의:

FLOOR 지정한 숫자 식보다 작거나 같은 최대 정수를 반환하고 결과가 입력과 동일한 유형을 가집니다.INT의 경우 INT, FLOAT의 경우 FLOAT, NUMERICAL(x, y)의 경우 NUMERICAL(x, 0)을 반환합니다.

알고리즘에 따라:

Operation | Result precision                    | Result scale*
e1 * e2   | p1 + p2 + 1                         | s1 + s2
e1 - e2   | max(s1, s2) + max(p1-s1, p2-s2) + 1 | max(s1, s2)

결과 정밀도와 척도의 절대 최대값은 38입니다.결과 정밀도가 38보다 크면 38로 감소하고, 결과의 적분 부분이 잘리는 것을 방지하기 위해 해당 스케일이 감소합니다.

이 설명에는 덧셈 및 곱셈 연산 내부에서 축척이 정확히 어떻게 감소하는지에 대한 자세한 내용도 포함됩니다.이 설명에 근거하여:

  • NUMERIC(19, 4) * NUMERIC(19, 4)이라NUMERIC(39, 8)그리고 고정되었습니다.NUMERIC(38, 7)
  • NUMERIC(1, 1) * NUMERIC(38, 0)이라NUMERIC(40, 1)그리고 고정되었습니다.NUMERIC(38, 1)
  • NUMERIC(5, 2) - NUMERIC(38, 1)이라NUMERIC(40, 2)그리고 고정되었습니다.NUMERIC(38, 1)

자바스크립트로 알고리즘을 구현하려는 저의 시도입니다.SQL Server에 대한 결과를 교차 확인했습니다.그것은 당신의 질문의 본질적인 부분에 답을 줍니다.

// https://learn.microsoft.com/en-us/sql/t-sql/data-types/precision-scale-and-length-transact-sql?view=sql-server-2017

function numericTest_mul(p1, s1, p2, s2) {
  // e1 * e2
  var precision = p1 + p2 + 1;
  var scale = s1 + s2;

  // see notes in the linked article about multiplication operations
  var newscale;
  if (precision - scale < 32) {
    newscale = Math.min(scale, 38 - (precision - scale));
  } else if (scale < 6 && precision - scale > 32) {
    newscale = scale;
  } else if (scale > 6 && precision - scale > 32) {
    newscale = 6;
  }

  console.log("NUMERIC(%d, %d) * NUMERIC(%d, %d) yields NUMERIC(%d, %d) clamped to NUMERIC(%d, %d)", p1, s1, p2, s2, precision, scale, Math.min(precision, 38), newscale);
}

function numericTest_add(p1, s1, p2, s2) {
  // e1 + e2
  var precision = Math.max(s1, s2) + Math.max(p1 - s1, p2 - s2) + 1;
  var scale = Math.max(s1, s2);

  // see notes in the linked article about addition operations
  var newscale;
  if (Math.max(p1 - s1, p2 - s2) > Math.min(38, precision) - scale) {
    newscale = Math.min(precision, 38) - Math.max(p1 - s1, p2 - s2);
  } else {
    newscale = scale;
  }

  console.log("NUMERIC(%d, %d) + NUMERIC(%d, %d) yields NUMERIC(%d, %d) clamped to NUMERIC(%d, %d)", p1, s1, p2, s2, precision, scale, Math.min(precision, 38), newscale);
}

function numericTest_union(p1, s1, p2, s2) {
  // e1 UNION e2
  var precision = Math.max(s1, s2) + Math.max(p1 - s1, p2 - s2);
  var scale = Math.max(s1, s2);

  // my idea of how newscale should be calculated, not official
  var newscale;
  if (precision > 38) {
    newscale = scale - (precision - 38);
  } else {
    newscale = scale;
  }

  console.log("NUMERIC(%d, %d) + NUMERIC(%d, %d) yields NUMERIC(%d, %d) clamped to NUMERIC(%d, %d)", p1, s1, p2, s2, precision, scale, Math.min(precision, 38), newscale);
}

/*
 * first example in question
 */

// CAST(1.0 AS DECIMAL(19, 4)) * CAST(199.96 AS DECIMAL(19, 4))
numericTest_mul(19, 4, 19, 4);

// 0.0 * FLOOR(...)
numericTest_mul(1, 1, 38, 0);

// 199.96 * ...
numericTest_add(5, 2, 38, 1);

/*
 * IIF examples in question
 * the logic used to determine result data type of IIF / CASE statement
 * is same as the logic used inside UNION operations
 */

// FLOOR(DECIMAL(38, 7)) UNION CAST(1999.96 AS DECIMAL(19, 4)))
numericTest_union(38, 0, 19, 4);

// CAST(1.0 AS DECIMAL (36, 0)) UNION CAST(1.96 AS DECIMAL(19, 4))
numericTest_union(36, 0, 19, 4);

// CAST(1.0 AS DECIMAL (37, 0)) UNION CAST(1.96 AS DECIMAL(19, 4))
numericTest_union(37, 0, 19, 4);

// CAST(1.0 AS DECIMAL (38, 0)) UNION CAST(1.96 AS DECIMAL(19, 4))
numericTest_union(38, 0, 19, 4);

언급URL : https://stackoverflow.com/questions/51442639/why-is-199-96-0-200-in-sql

반응형