문제

우리는 VB.NET 및 SQL Server에서 레거시 회계 시스템을 다시 작성하고 있습니다.우리는 재작성을 위해 새로운 .NET/SQL 프로그래머 팀을 구성했습니다.대부분의 시스템은 이미 Floats를 사용하여 달러 금액으로 완료되었습니다.내가 프로그래밍한 레거시 시스템 언어에는 Float가 없었으므로 아마도 Decimal을 사용했을 것입니다.

당신의 추천은 무엇입니까?

달러 금액에 Float 또는 Decimal 데이터 유형을 사용해야 합니까?

둘 중 하나의 장단점은 무엇입니까?

일일 스크럼에서 언급한 한 가지 단점은 소수점 이하 두 자리를 넘는 결과를 반환하는 금액을 계산할 때 주의해야 한다는 것입니다.금액을 소수점 이하 두 자리까지 반올림해야 할 것 같습니다.

또 다른 단점은 모든 표시 및 인쇄된 금액에 소수점 두 자리를 표시하는 형식 설명이 있어야 한다는 것입니다.나는 이것이 완료되지 않았고 금액이 정확하지 않은 것을 몇 번 발견했습니다.(즉.10.2 또는 10.2546)

장점은 Float가 디스크에서 8바이트만 차지하고 Decimal은 9바이트(Decimal 12,2)를 차지한다는 것입니다.

도움이 되었습니까?

해결책

달러 금액에 Float 또는 Decimal 데이터 유형을 사용해야 합니까?

대답은 쉽습니다.절대로 뜨지 마세요. 절대 !

수레는 다음과 같았습니다. IEEE 754 항상 바이너리, 오직 새로운 표준 IEEE 754R 정의된 십진수 형식.많은 소수 이진 부분은 정확한 십진수 표현과 결코 동일할 수 없습니다.
모든 이진수는 다음과 같이 쓸 수 있습니다. m/2^n (m, n 양의 정수), 임의의 10진수 m/(2^n*5^n).
바이너리에는 소수가 없기 때문에 factor 5, 모든 이진수는 십진수로 정확하게 표시될 수 있지만 그 반대는 불가능합니다.

0.3 = 3/(2^1 * 5^1) = 0.3

0.3 = [0.25/0.5] [0.25/0.375] [0.25/3.125] [0.2825/3.125]

          1/4         1/8         1/16          1/32

따라서 주어진 십진수보다 높거나 낮은 숫자로 끝납니다.언제나.

그것이 왜 중요합니까?반올림.
일반적인 반올림은 아래로 0..4, 위로 5..9를 의미합니다.그래서 하다 결과가 있으면 중요합니다 0.049999999999....또는 0.0500000000...5센트라는 뜻인 줄은 알지만 컴퓨터는 그걸 모르고 반올림합니다. 0.4999...다운 (잘못) 그리고 0.5000...위(오른쪽).
부동 소수점 계산의 결과에는 항상 작은 오류 항목이 포함된다는 점을 고려하면 결정은 순전히 운입니다.이진수로 소수점 반올림을 처리하려면 절망적입니다.

확신이 없나요?당신은 당신의 계정 시스템에서 모든 것이 완벽하다고 주장합니까?
자산과 부채는 동일하다?좋아, 그런 다음 각 항목의 지정된 형식화된 숫자를 각각 가져와서 구문 분석하고 독립적인 십진법으로 합산하세요!이를 형식화된 합계와 비교합니다.
이런, 뭔가 문제가 있는 것 아닌가요?

이러한 계산을 위해 극심한 정확성과 충실도가 필요했습니다 (Oracle 's float를 사용했습니다). "Penny의 10 억의"를 기록 할 수있었습니다.

이 오류에 대해서는 도움이 되지 않습니다.모든 사람들은 컴퓨터의 합이 옳다고 자동으로 가정하기 때문에 사실상 아무도 독립적으로 확인하지 않습니다.

다른 팁

먼저 당신은 이것을 읽어야합니다 모든 컴퓨터 과학자가 부동 소수점 연산에 대해 알아야 할 사항.그렇다면 실제로 어떤 유형의 사용을 고려해야 합니다. 고정 소수점/임의 정밀도 숫자 패키지(예:java BigNum, python 십진 모듈) 그렇지 않으면 상처를 입을 것입니다.그런 다음 기본 SQL 10진수 유형을 사용하는 것으로 충분한지 알아보세요.

현재는 거의 쓸모가 없어진 빠른 x87 fp를 노출하기 위해 부동/복식이 존재합니다.계산의 정확성에 관심이 있거나 계산의 한계를 완전히 보상하지 못하는 경우에는 사용하지 마세요.

추가 경고와 마찬가지로 SQL Server와 .Net Framework는 반올림에 다른 기본 알고리즘을 사용합니다.Math.Round()에서 MidPointRounding 매개변수를 확인하세요..Net 프레임워크는 기본적으로 Bankers 알고리즘을 사용하고 SQL Server는 대칭 알고리즘 반올림을 사용합니다.Wikipedia 기사를 확인하세요. 여기

회계사에게 물어보세요!그들은 float를 사용하는 것에 대해 당신에게 눈살을 찌푸릴 것입니다.이전에 게시한 것과 마찬가지로 정확성에 관심이 없는 경우에만 float를 사용하세요.돈에 관해서는 항상 반대하겠지만.

회계 소프트웨어에서는 부동 소수점을 허용하지 않습니다.소수점 4자리가 있는 십진수를 사용합니다.

부동 소수점에 예상치 못한 무리수가 있습니다.

예를 들어 1/3은 소수로 저장할 수 없으며 0.3333333333...이 됩니다.(등등)

부동 소수점은 실제로 이진 값과 2 지수의 거듭제곱으로 저장됩니다.

따라서 1.5는 -1(또는 3/2)의 3 x 2로 저장됩니다.

이러한 밑이 2인 지수를 사용하면 다음과 같은 홀수 무리수를 생성할 수 있습니다.

1.1을 부동 소수점으로 변환한 다음 다시 다시 변환하면 결과는 다음과 같습니다.1.0999999999989

이는 1.1의 이진 표현이 실제로 154811237190861 x 2^-47이기 때문입니다. 이는 double이 처리할 수 있는 것 이상입니다.

이 문제에 대한 자세한 내용은 내 블로그, 그러나 기본적으로 저장을 위해서는 소수를 사용하는 것이 더 좋습니다.

Microsoft SQL Server에는 다음이 있습니다. money 데이터 유형 - 일반적으로 금융 저장에 가장 적합합니다.소수점 이하 4자리까지 정확합니다.

계산의 경우 더 많은 문제가 있습니다. 부정확성은 아주 작은 부분이지만 이를 거듭제곱 함수에 넣으면 빠르게 중요해집니다.

그러나 소수는 어떤 종류의 수학에도 적합하지 않습니다. 예를 들어 소수 거듭제곱에 대한 기본 지원은 없습니다.

SQL 서버를 사용하세요 소수 유형.

사용하지 마세요 또는 뜨다.

돈은 소수점 이하 4자리를 사용하므로 소수점 이하 자릿수를 사용하는 것보다 빠릅니다. 하지만 반올림과 관련하여 일부 명백한 문제와 그다지 명확하지 않은 일부 문제가 있습니다(이 연결 문제를 참조하세요)

제가 추천하는 것은 모든 것을 센트 단위로 저장하는 64비트 정수를 사용하는 것입니다.

여기에 약간의 배경이 있습니다....

어떤 숫자 체계도 모든 실수를 정확하게 처리할 수는 없습니다.모두에는 제한이 있으며 여기에는 표준 IEEE 부동 소수점과 부호 있는 십진수가 모두 포함됩니다.IEEE 부동 소수점은 사용된 비트당 더 정확하지만 여기서는 중요하지 않습니다.

재무 수치는 수세기에 걸쳐 종이와 펜으로 기록하는 방식과 관련 관례를 기반으로 합니다.상당히 정확하지만 더 중요한 것은 재현이 가능하다는 것입니다.다양한 숫자와 요율을 다루는 두 명의 회계사가 동일한 숫자를 제시해야 합니다.불일치의 여지는 사기의 여지가 있습니다.

따라서 재무 계산에 있어서 정답은 산수를 잘하는 공인회계사와 같은 답을 내는 것이 정답이다.이는 IEEE 부동 소수점이 아닌 십진수 산술입니다.

돈을 위해 Float를 사용하는 유일한 이유는 정확한 답변에 관심이 없기 때문입니다.

부동 소수점은 정확한 표현이 아니며 예를 들어 매우 큰 값과 매우 작은 값을 추가할 때 정밀도 문제가 발생할 수 있습니다.이것이 바로 정밀도 문제가 매우 드물더라도 통화에 소수 유형을 권장하는 이유입니다.

명확히 하자면, 10진수 12,2 유형은 해당 14자리 숫자를 정확하게 저장하는 반면, 부동 소수점 유형은 내부적으로 이진 표현을 사용하므로 저장하지 않습니다.예를 들어, 0.01은 부동 소수점 숫자로 정확하게 표현할 수 없습니다. 가장 가까운 표현은 실제로 0.0099999998입니다.

제가 개발을 도왔던 은행 시스템에서 저는 시스템의 "이자 발생" 부분을 담당했습니다.매일 내 코드는 그날 잔액에 발생한 이자가 얼마나 되는지 계산했습니다.

해당 계산에는 발생하는 "십억 분의 1 페니"를 기록할 수 있도록 극도의 정확성과 충실도가 필요했습니다(우리는 Oracle의 FLOAT를 사용했습니다).

이자를 "자본화"하는 경우(예:이자를 귀하의 계좌로 다시 지불) 금액은 1센트로 반올림되었습니다.계좌 잔액의 데이터 유형은 소수점 이하 두 자리였습니다.(사실 소수점 이하 여러 자리까지 사용할 수 있는 다중 통화 시스템이었기 때문에 더 복잡했습니다. 하지만 우리는 항상 해당 통화의 "페니"로 반올림했습니다.)예 - 손실과 이익의 "부분"이 있지만 컴퓨터 수치가 현실화되면(지불되거나 지불된 돈) 항상 실제 화폐 가치였습니다.

이는 회계사, 감사자 및 테스터를 만족시켰습니다.

따라서 고객에게 확인하십시오.그들은 귀하에게 은행/회계 규칙 및 관행을 알려줄 것입니다.

십진수를 사용하는 것보다 훨씬 더 나은 것은 단지 오래된 정수(또는 일종의 bigint)를 사용하는 것입니다.이렇게 하면 항상 최고의 정확도를 얻을 수 있지만 정밀도를 지정할 수도 있습니다.예를 들어 숫자 100 의미할 수 있다 1.00, 다음과 같은 형식입니다.

int cents = num % 100;
int dollars = (num - cents) / 100;
printf("%d.%02d", dollars, cents);

더 높은 정밀도를 원할 경우 다음과 같이 100을 더 큰 값으로 변경할 수 있습니다.10 ^ n, 여기서 n은 소수 자릿수입니다.

회계 시스템에서 알아야 할 또 다른 사항은 누구도 테이블에 직접 접근할 수 없다는 것입니다.이는 회계 시스템에 대한 모든 액세스가 저장된 프로세스를 통해 이루어져야 함을 의미합니다.이는 SQL 주입 공격뿐만 아니라 사기를 방지하는 것입니다.사기를 저지르려는 내부 사용자는 데이터베이스 테이블의 데이터를 직접 변경할 수 있는 능력이 있어서는 안 됩니다.이는 시스템의 중요한 내부 제어입니다.불만을 품은 일부 직원이 데이터베이스의 백엔드로 이동하여 수표를 작성하게 되기를 정말로 원하십니까?아니면 승인 권한이 없는 무단 공급업체에 비용을 승인했다는 사실을 숨기시겠습니까?전체 조직에서 단 두 사람만이 금융 데이터베이스, 즉 DBA와 그의 백업에 있는 데이터에 직접 액세스할 수 있어야 합니다.많은 dbas가 있는 경우 그 중 두 개만 이 액세스 권한을 가져야 합니다.

내가 이것을 언급하는 이유는 프로그래머가 회계 시스템에서 float를 사용한 경우 내부 통제 개념에 전혀 익숙하지 않고 프로그래밍 작업에서 이를 고려하지 않았을 가능성이 높기 때문입니다.

.Net에 대한 Money 유형과 같은 것을 언제든지 작성할 수 있습니다.

이 기사를 살펴보십시오. CLR의 화폐 유형 - 제 생각엔 작가님이 훌륭한 작품을 만드셨어요.

저는 화폐 가치를 저장하기 위해 SQL의 화폐 유형을 사용해 왔습니다.최근에 저는 여러 온라인 결제 시스템을 사용해 보았는데 그 중 일부가 금전적 가치를 저장하기 위해 정수를 사용한다는 사실을 발견했습니다.현재 프로젝트와 새 프로젝트에서 정수를 사용하기 시작했으며 이 솔루션에 꽤 만족합니다.

100개의 분수 n/100(여기서 n은 0 <= n이고 n < 100인 자연수) 중 4개만이 부동 소수점 숫자로 표현될 수 있습니다.이 C 프로그램의 출력을 살펴보십시오.

#include <stdio.h>

int main()
{
    printf("Mapping 100 numbers between 0 and 1 ");
    printf("to their hexadecimal exponential form (HEF).\n");
    printf("Most of them do not equal their HEFs. That means ");
    printf("that their representations as floats ");
    printf("differ from their actual values.\n");
    double f = 0.01;
    int i;
    for (i = 0; i < 100; i++) {
        printf("%1.2f -> %a\n",f*i,f*i);
    }
    printf("Printing 128 'float-compatible' numbers ");
    printf("together with their HEFs for comparison.\n");
    f = 0x1p-7; // ==0.0071825
    for (i = 0; i < 0x80; i++) {
        printf("%1.7f -> %a\n",f*i,f*i);
    }
    return 0;
}

달러 금액을 저장하기 위해 돈 데이터 유형을 사용하는 것을 고려해 보셨나요?

십진수가 1바이트를 더 차지한다는 단점에 대해서는 신경 쓰지 않는다고 말하고 싶습니다.100만 행에서는 1MB만 더 사용하게 되며 요즘에는 스토리지 가격이 매우 저렴합니다.

무엇을 하든 반올림 오류에 주의해야 합니다.표시되는 것보다 더 높은 정밀도를 사용하여 계산합니다.

통화 값에 대해 고정 소수점 표시 형식을 사용하고 싶을 수도 있습니다.또한 Banker's Rounding("반올림"이라고도 함)을 조사할 수도 있습니다. 이는 일반적인 "반올림" 방법에 존재하는 편향을 방지합니다.

회계사는 반올림 방법을 제어하고 싶어할 것입니다.float를 사용한다는 것은 일반적으로 FORMAT() 유형 문을 사용하여 지속적으로 반올림한다는 의미인데, 이는 원하는 방식이 아닙니다(대신 바닥/천장 사용).

float 또는 real 대신 사용해야 하는 통화 데이터 유형(money, smallmoney)이 있습니다.소수점 (12,2)을 저장하면 반올림이 제거되지만 중간 단계에서도 반올림이 제거됩니다. 이는 실제로 금융 응용 프로그램에서 전혀 원하지 않는 것입니다.

항상 10진수를 사용하세요.Float는 반올림 문제로 인해 부정확한 값을 제공합니다.

부동 소수점 숫자는 다음과 같습니다. 오직 밑의 음수 배수의 합인 숫자를 나타냅니다. 이진 부동 소수점의 경우 물론 2입니다.

이진 부동 소수점으로 정확하게 표현할 수 있는 소수는 4개뿐입니다.0, 0.25, 0.5, 0.75.다른 모든 것은 0.3333...과 같은 방식으로 근사치입니다.는 십진수 산술에서 1/3에 대한 근사치입니다.

부동 소수점은 결과의 규모가 중요한 계산에 적합한 선택입니다.소수점 이하 자릿수까지 정확하려고 하는 것은 잘못된 선택입니다.

이것은 설명하는 훌륭한 기사입니다. 부동소수점과 소수점을 사용해야 하는 경우.Float는 대략적인 값을 저장하고 Decimal은 정확한 값을 저장합니다.

요약하자면, 돈과 같은 정확한 값은 소수를 사용해야 하고, 과학적 측정과 같은 대략적인 값은 부동소수점을 사용해야 합니다.

다음은 부동소수점과 소수점 모두 정밀도를 잃을 수 있음을 보여주는 흥미로운 예입니다.정수가 아닌 숫자를 더한 다음 동일한 숫자를 빼면 float 결과로 정밀도가 떨어지지만 소수는 그렇지 않습니다.

    DECLARE @Float1 float, @Float2 float, @Float3 float, @Float4 float; 
    SET @Float1 = 54; 
    SET @Float2 = 3.1; 
    SET @Float3 = 0 + @Float1 + @Float2; 
    SELECT @Float3 - @Float1 - @Float2 AS "Should be 0";

Should be 0 
---------------------- 
1.13797860024079E-15

정수가 아닌 값을 곱하고 같은 숫자로 나누면 소수는 정밀도를 잃지만 부동 소수점은 그렇지 않습니다.

DECLARE @Fixed1 decimal(8,4), @Fixed2 decimal(8,4), @Fixed3 decimal(8,4); 
SET @Fixed1 = 54; 
SET @Fixed2 = 0.03; 
SET @Fixed3 = 1 * @Fixed1 / @Fixed2; 
SELECT @Fixed3 / @Fixed1 * @Fixed2 AS "Should be 1";

Should be 1 
--------------------------------------- 
0.99999999999999900
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top