floating-point - type - single floating point number




Double 또는 Float을 사용하여 통화를 나타내는 이유는 무엇입니까? (10)

계산에 여러 단계가 포함 된 경우 임의 정밀도 산술로 인해 100 %가 처리되지 않습니다.

결과의 완벽한 표현을 사용하는 유일한 신뢰할 수있는 방법 (나눗셈 분할 연산을 마지막 단계까지 수행 할 사용자 정의 분수 데이터 형식 사용) 및 마지막 단계에서 십진수 표기법으로 변환하십시오.

소수점이 너무 많은 숫자 나 항상 0.6666666과 같은 결과가있을 수 있기 때문에 임의의 정밀도는 도움이되지 않습니다. 임의의 표현은 마지막 예제를 다루지 않습니다. 따라서 각 단계마다 작은 오류가 발생합니다.

이 오류는 더해질 것이고 결국 더 이상 무시하기가 어려울 수 있습니다. 이를 오류 전달 이라고합니다.

저는 항상 double 또는 float 유형으로 돈을 대변 하지 말라고 말했고, 이번에는 당신에게 질문을 던집니다 : 왜?

아주 좋은 이유가 있다고 확신합니다. 나는 그것이 무엇인지 모릅니다.


나는 downvoted 위험을 감수 하겠지만, 나는 통화 계산을위한 부동 소수점 숫자의 부적합 함이 과대 평가된다고 생각한다. 반올림을 올바르게하고 zenak으로 설명 된 이진수 표현 불일치에 맞서기 위해 사용할 유효 자릿수가 충분하면 문제가되지 않습니다.

Excel에서 통화로 계산하는 사람들은 항상 배정 밀도 수레를 사용합니다 (Excel에서는 통화 유형이 없음). 나는 반올림 오류에 대해 불평하는 사람을 아직 보지 못했습니다.

물론, 당신은 이성 안에서 머물러야합니다. 예를 들어, 단순한 webshop은 double precision float에 아무런 문제가 없을 것입니다. 그러나 회계 또는 큰 수의 (제한되지 않은) 숫자를 추가해야하는 다른 작업을 수행하는 경우, 10 피트의 부동 소수점을 만지는 것을 원하지 않을 것입니다 폴.


대부분의 해답은 돈과 통화 계산을 위해 복식을 사용하지 않아야하는 이유를 강조했습니다. 그리고 나는 그들과 완전히 동의한다.

그 두 배는 결코 그 목적을 위해 사용될 수는 없다는 것을 의미하지는 않습니다.

매우 낮은 GC 요구 사항을 가진 여러 프로젝트에서 작업했으며 BigDecimal 객체를 사용하면 오버 헤드에 큰 기여를했습니다.

이 현명한 제안을 가져 오는 정확성과 정밀도를 다루는 데있어 이중 표현과 경험 부족에 대한 이해가 부족합니다.

프로젝트의 정확도 및 정확도 요구 사항을 처리 할 수있는 경우이를 수행 할 수 있습니다.이 요구 사항은 어떤 이중 값 범위를 처리해야하는지에 따라 수행해야합니다.

Guava의 FuzzyCompare 메소드를 참조하면 더 많은 아이디어를 얻을 수 있습니다. 매개 변수 허용차가 핵심입니다. 우리는 증권 거래 신청에 대해이 문제를 다루었으며 다양한 범위에서 다른 수치 값에 대해 어떤 허용 오차를 사용하는지 철저히 조사했습니다.

또한 해시 맵을 구현으로 사용하여 Double 래퍼를 맵 키로 사용하려는 경우가있을 수 있습니다. Double.equals 및 해시 코드 예를 들어 "0.5"및 "0.6 - 0.1"값이 큰 혼란을 야기하므로 매우 위험합니다.


부동 소수점 숫자의 결과는 정확하지 않으므로 근사값이 아닌 정확한 결과가 필요한 재무 계산에는 부적합합니다. float 및 double은 공학 및 과학 계산을 위해 설계되었으며 여러 번 정확한 결과를 산출하지 못합니다. 또한 부동 소수점 계산의 결과가 JVM에서 JVM으로 다를 수 있습니다. 아래의 BigDecimal과 money 값을 표현하는 데 사용되는 double 프리미티브의 예를 살펴보면 부동 소수점 계산이 정확하지 않을 수 있으며 재무 계산을 위해 BigDecimal을 사용해야한다는 것이 분명합니다.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

산출:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

수레와 복식은 근사치입니다. BigDecimal을 작성해, float를 생성자에 건네 주면 (자), float가 실제로 같은 것을 알 수 있습니다.

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

이것은 아마도 $ 1.01을 어떻게 표현할 것인가가 아닙니다.

문제는 IEEE 규격에는 모든 분수를 정확하게 표현할 수있는 방법이 없으며 일부는 반복 분수로 끝나기 때문에 근사 오차로 끝납니다. 회계사가 물건을 정확하게 팔고 고객이 청구서를 지불하고 지불이 처리 된 후 그들은 빚을졌고 수수료가 부과되거나 계정을 폐쇄 할 수 없으므로 짜증나게됩니다. 10 진수 (C #의 경우) 또는 Java의 java.math.BigDecimal과 같은 정확한 유형.

그것은 당신 원한다면 오류 제어 할 수 없습니다 아니에요 : 피터 Lawrey이 문서를 참조하십시오 . 처음부터 반올림하지 않는 것이 더 쉽습니다. 돈을 처리하는 대부분의 응용 프로그램은 많은 수학을 필요로하지 않으며 작업은 물건을 추가하거나 양을 다른 버킷에 할당하는 것으로 구성됩니다. 부동 소수점 및 반올림을 도입하면 사물이 복잡해집니다.


수레와 복식은 우리가 돈을 위해 사용하는 기본 10 배수를 정확하게 나타낼 수 없기 때문입니다. 이 문제는 Java에만 해당되는 것이 아니라 기본 2 부동 소수점 유형을 사용하는 프로그래밍 언어에 해당합니다.

10 진수에서는 10.25를 1025 * 10 -2 (10 배의 정수 배)로 쓸 수 있습니다. IEEE-754 부동 소수점 숫자 는 다르지만, 생각해 보는 가장 간단한 방법은 대신에 2의 거듭 제곱을 곱하는 것입니다. 예를 들어, 당신은 164 * 2 -4 (정수 곱하기 2의 거듭 제곱)를 볼 수 있습니다. 이것은 또한 10.25와 같습니다. 숫자가 메모리에 어떻게 표시되는지는 아니지만 수학의 함축적 의미는 같습니다.

10 진법에서도이 표기법은 대부분의 단순한 분수를 정확하게 나타낼 수 없습니다. 예를 들어 1/3을 표현할 수 없습니다. 10 진수 표현은 반복됩니다 (0.3333 ...). 그래서 1/10을 얻기 위해 10의 제곱으로 곱할 수있는 유한 정수가 없습니다. 3의 긴 시퀀스와 333333333 * 10 -10 과 같은 작은 지수로 정할 수 있지만 정확하지 않습니다. 3을 곱하면 1이되지 않습니다.

그러나 돈을 계산할 목적으로 적어도 미국 달러의 가치가있는 국가의 경우 보통 10 -2의 배수를 저장할 수 있어야합니다. 따라서 중요하지 않습니다. 1/3은 표현 될 수 없다.

수레와 복식의 문제는 대다수 의 돈과 같은 숫자가 2의 거듭 제곱의 정수 배로 정확한 표현을 갖지 않는다는 것입니다. 실제로 0과 1 사이의 0.01의 유일한 배수 IEEE-754 이진 부동 소수점 수로 표현할 수있는 정수는 0, 0.25, 0.5, 0.75 및 1입니다. 나머지는 모두 소량입니다. 0.333333 예제와 유사하게 부동 소수점 값을 0.1로 취한 다음 10을 곱하면 1을 얻지 못합니다.

double float 것으로 돈을 표현하는 것은 소프트웨어가 작은 오류를 마무리하는 것처럼 처음에는 좋게 보일 것입니다. 그러나 부정확 한 숫자에 대해 더하기, 빼기, 곱셈 및 나누기를 수행하면 오류가 복잡해질 것이며 그 결과 값은 시각적으로 정확하지 않습니다. 이로 인해 부동 소수점 및 복식은 기본 10 배수의 배수로 완벽한 정확성이 요구되는 돈 처리에 적합하지 않습니다.

거의 모든 언어에서 작동하는 솔루션은 대신 정수를 사용하고 센트를 계산합니다. 예를 들어, 1025 달러는 10.25 달러입니다. 여러 언어에는 돈을 처리 할 수있는 기본 제공 유형이 있습니다. 다른 것들 중에서 Java는 BigDecimal 클래스를 가지고 있고 C #은 decimal 타입을 가지고 있습니다.


이 질문에 게시 된 답변 중 많은 부분이 IEEE와 부동 소수점 산술을 둘러싼 표준을 다루고 있습니다.

비 컴퓨터 과학 배경 (물리학 및 공학)에서 왔기 때문에 나는 다른 관점에서 문제를 보는 경향이있다. 필자가 수학 계산에서 double 또는 float을 사용하지 않는 이유는 너무 많은 정보를 잃어 버리기 때문입니다.

대안은 무엇입니까? 많은 것이 있습니다 (그리고 그 중 많은 것들이 제가 인식하지 못하는 것입니다!).

Java의 BigDecimal은 Java 언어에서 기본입니다. Apfloat는 Java 용 임의 정밀도 라이브러리입니다.

C #의 10 진수 데이터 형식은 28 개의 중요한 숫자에 대한 Microsoft의 .NET 대안입니다.

SciPy (Scientific Python)도 재무 계산을 처리 할 수 ​​있습니다 (시도하지는 않았지만 그렇게 생각합니다).

GNU 다중 정밀 라이브러리 (GMP)와 GNU MFPR 라이브러리는 C와 C ++을위한 두 가지 무료 오픈 소스 리소스입니다.

자바 스크립트 (!)를위한 수치 정밀 라이브러리도 있으며 재무 계산을 처리 할 수있는 PHP라고 생각합니다.

또한 많은 컴퓨터 언어에 대해 독점적 인 (특히 Fortran의 경우) 및 오픈 소스 솔루션이 있습니다.

나는 컴퓨터 과학자가 아닙니다.그러나 나는 Java에서 BigDecimal 또는 C #에서 십진수를 사용하는 경향이있다. 필자가 나열한 다른 솔루션을 시도하지는 않았지만 아마도 매우 훌륭 할 것입니다.

필자는 BigDecimal을 지원하기 때문에 BigDecimal을 좋아한다. C #의 십진법은 매우 훌륭하지만, 내가 원하는만큼 그것으로 작업 할 기회가 없었습니다. 내 여가 시간에 저에게 관심있는 과학적 계산을하고 BigDecimal은 부동 소수점 숫자의 정밀도를 설정할 수 있기 때문에 매우 잘 작동하는 것 같습니다. BigDecimal의 단점은 무엇입니까? 때로는 속도가 느려질 수 있습니다. 특히 분할 방법을 사용하는 경우 특히 그렇습니다.

속도를 높이기 위해 C, C ++ 및 Fortran의 무료 및 독점 라이브러리를 살펴보십시오.


이것은 정확성의 문제가 아니며 정확성의 문제도 아닙니다. 예를 들어, 재무 계산을 위해 복식을 사용한다고해서 수학적 의미에서 "잘못"된 답을 얻지는 못하지만 다음과 같은 해답을 얻을 수 있습니다. 재정적 인면에서 기대되는 것이 아닙니다.

출력하기 직전에 결과를 반올림 한 경우에도 예상과 일치하지 않는 복식을 사용하여 가끔 결과를 얻을 수 있습니다.

계산기를 사용하거나 수동으로 결과를 계산하면 1.40 * 165 = 231입니다. 그러나 내적으로 컴파일러 / 운영 체제 환경에서 double을 사용하면 230.99999에 가까운 이진수로 저장됩니다. 따라서 숫자를 자르면 231 대신 230이됩니다. 반올림 대신에 반올림을 사용하면 원하는 231의 결과를 얻었습니다. 그것은 사실이지만 반올림은 항상 절단을 포함합니다. 어떤 반올림 기법을 사용하든, 반올림을 기대할 때 반올림 할 경계 조건이 여전히 있습니다. 그들은 아주 드물기 때문에 캐주얼 테스팅이나 관찰을 통해 발견되지 않습니다. 예상대로 작동하지 않는 결과를 보여주는 예제를 찾으려면 코드를 작성해야 할 수도 있습니다.

가장 가까운 페니에 무언가를 반올림한다고 가정하십시오. 그래서 최종 결과를 얻고, 100을 곱하고, 0.5를 더하고, 잘라 내고, 결과를 100으로 나눠서 페니로 돌아갑니다. 저장된 내부 번호가 3.465 대신 3.46499999 .... 인 경우 가장 가까운 페니로 번호를 반올림 할 때 3.46 대신 3.46을 얻게됩니다. 그러나 기본 계산에 따르면 답이 정확히 3.465 여야하며, 3.46까지는 아니고 3.47까지 반올림해야합니다. 재무 계산을 위해 복식을 사용하는 경우 이러한 종류의 일이 실제 생활에서 가끔 발생합니다. 그것은 드물기 때문에 종종 문제로 주목을받지 못합니다.

두배가 아닌 내부 계산에 밑수 10을 사용하면 코드에 다른 버그가 없다고 가정 할 때 항상 사람이 예상하는 답변을 얻을 수 있습니다.


Bloch, J., Effective Java, 2nd ed, Item 48 :

floatdouble 유형은 float 또는 double 값으로 0.1 (또는 10의 다른 음수 값)을 나타내는 것이 불가능하기 때문에 통화 계산에 특히 적합하지 않습니다.

예를 들어, $ 1.03이 있고 42c를 소비한다고 가정합니다. 얼마나 많은 돈이 남았습니까?

System.out.println(1.03 - .42);

0.6100000000000001 출력합니다.

이 문제를 해결하는 올바른 방법은 화폐 계산을 위해 int 또는 long BigDecimal 을 사용하는 것입니다.


몇 가지 예는 ... 거의 모든 프로그래밍 언어에서 작동합니다 (실제로 예상대로 작동하지 않습니다) ... 저는 Delphi, VBScript, Visual Basic, JavaScript 및 Java / Android로 시도했습니다.

    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
    }

산출:

round problems?: current total: 0.9999999999999999 round problems?: current total: 2.7755575615628914E-17 round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!





currency