자바와 C++로 돈 버는 방법 : IEEE 754와 실생활 : 0.1+0.2=0.3 그래, 하지만 어떻게?

10358 단어
오랜만이야, 그게 다야.

바로 본론으로 들어가 돈을 조심스럽게 다루어야 하는 이유를 알아보겠습니다.
여기서 Java는 Java 17을 나타내고 C++는 C++ 20을 나타냅니다. 다르게 지정되지 않으면 JavaC++ 모두에 대해 w3schools 컴파일러를 사용합니다.

문제를 이해하는 좋은 시작은 link을 따르는 것입니다.

C++ : 0.1d + 0.2d != 0.3d



#include <iostream>
#include <limits>
typedef std::numeric_limits< double > dl;

int main() {
  std::cout.precision(dl::max_digits10);
  std::cout<<0.1d<<std::endl;
  std::cout<<0.2d<<std::endl;
  std::cout<<0.3d<<std::endl;
  std::cout<<0.1d + 0.2d<<std::endl;
  std::cout<<(0.1d + 0.2d == 0.3d)<<std::endl;
  return 0;
}



IEEE 754 다음에 오는 메모리 내 표현은 근사치라는 것이 분명합니다. 예상했던 것과 항상 동일한 근사값을 갖는다는 보장은 없습니다.

자바: 0.1d + 0.2d != 0.3d



Java에서 같은 것을 확인해 봅시다.

public class Main {
  public static void main(String[] args) {
    final String DOUBLE_FORMAT = "%.17f";
    System.out.println(String.format(DOUBLE_FORMAT, 0.1d));
    System.out.println(String.format(DOUBLE_FORMAT, 0.2d));
    System.out.println(String.format(DOUBLE_FORMAT, 0.3d));
    System.out.println(String.format(DOUBLE_FORMAT, 0.1d + 0.2d));
    System.out.println(0.1d + 0.2d == 0.3d);
  }
}




따라서 우리는 Java에서도 이중 표현을 사용할 수 없습니다.

합계 오류 감소



부동 소수점 숫자를 추가할 때 보다 일관된 결과를 얻으려면 Kahan summation algorithm을 확인하십시오.
그렇지 않으면 문제를 있는 그대로 해결하려면 더 간단한 것이 필요하며 그 이름은 decimal data type support 입니다.
사실 우리는 0.11만큼 오른쪽으로 이동한 것으로 나타낼 수 있으며, 0.20.3와 동일합니다. 그런 다음 0.10.2를 추가하는 것이 간단합니다. 1 ) 및 2 결과를 오른쪽으로 이동합니다. 사실 우리는 시프트가 있는 정수로 십진수 값을 나타냅니다. 자세한 내용은 decimal 64 링크를 참조하십시오.

10진수 유형으로 작업할 때 할당은 부동 소수점 유형이 아니라 문자열에서 10진수로 발생합니다.

C++ : 부스트 다중 정밀도 0.1 + 0.2 == 0.3



boost multiprecision

10진수 유형을 사용하여 다음을 얻습니다0.1 + 0.2 == 0.3.

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
using boost::multiprecision::cpp_dec_float_50;

int main() {
  cpp_dec_float_50 d01("0.1");
  cpp_dec_float_50 d02("0.2");
  cpp_dec_float_50 d03("0.3");
  std::cout<<std::fixed<<std::setprecision(50)<<d01<<std::endl;
  std::cout<<std::fixed<<std::setprecision(50)<<d02<<std::endl;
  std::cout<<std::fixed<<std::setprecision(50)<<d03<<std::endl;
  std::cout<<std::fixed<<std::setprecision(50)<<(d01 + d02)<<std::endl;
  std::cout<<std::fixed<<std::setprecision(50)<<(d01 + d02 == d03)<<std::endl;
  return 0;
}





곱셈도 간단합니다.

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
using boost::multiprecision::cpp_dec_float_50;

int main() {
  cpp_dec_float_50 d01("0.1");
  cpp_dec_float_50 d02("0.2");
  std::cout<<std::fixed<<std::setprecision(50)<<d01 * d02<<std::endl;
  return 0;
}





부서는 어떻습니까? 기본적으로 예상한 대로 작동합니다.

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
using boost::multiprecision::cpp_dec_float_50;

int main() {
  cpp_dec_float_50 d1("1");
  cpp_dec_float_50 d2("2");
  cpp_dec_float_50 d3("3");
  std::cout<<std::fixed<<std::setprecision(50)<<d1 / d2<<std::endl;
  std::cout<<std::fixed<<std::setprecision(50)<<d1 / d3<<std::endl;
  std::cout<<std::fixed<<std::setprecision(50)<<d2 / d3<<std::endl;
  return 0;
}





자바 : 큰 십진수 0.1 + 0.2 == 0.3



Big Decimal

BigDecimal을 사용한 요약:

import java.math.BigDecimal;
public class Main {
  public static void main(String[] args) {
    final String DOUBLE_FORMAT = "%.17f";
    BigDecimal d01 = new BigDecimal("0.1");
    BigDecimal d02 = new BigDecimal("0.2");
    BigDecimal d03 = new BigDecimal("0.3");
    System.out.println(String.format(DOUBLE_FORMAT, d01));
    System.out.println(String.format(DOUBLE_FORMAT, d02));
    System.out.println(String.format(DOUBLE_FORMAT, d03));
    System.out.println(String.format(DOUBLE_FORMAT, d01.add(d02)));
    System.out.println(d01.add(d02).equals(d03));
  }
}




곱셈 :

import java.math.BigDecimal;
public class Main {
  public static void main(String[] args) {
    final String DOUBLE_FORMAT = "%.17f";
    BigDecimal d01 = new BigDecimal("0.1");
    BigDecimal d02 = new BigDecimal("0.2");
    System.out.println(String.format(DOUBLE_FORMAT, d01.multiply(d02)));
  }
}





예상한 대로 작동합니다. 나눗셈에는 특수성이 있습니다. 나눗셈의 결과를 유한 십진법으로 표현할 수 있어야 하거나 MathContext을 전달해야 합니다.

import java.math.BigDecimal;
public class Main {
  public static void main(String[] args) {
    final String DOUBLE_FORMAT = "%.17f";
    BigDecimal d1 = new BigDecimal("1");
    BigDecimal d2 = new BigDecimal("2");
    BigDecimal d3 = new BigDecimal("3");
    System.out.println(String.format(DOUBLE_FORMAT, d1.divide(d2)));
  }
}






import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class Main {
  public static void main(String[] args) {
    final String DOUBLE_FORMAT = "%.17f";
    BigDecimal d1 = new BigDecimal("1");
    BigDecimal d2 = new BigDecimal("2");
    BigDecimal d3 = new BigDecimal("3");
    System.out.println(d1.divide(d3, MathContext.DECIMAL32));
    System.out.println(d1.divide(d3, MathContext.DECIMAL64));
    System.out.println(d1.divide(d3, MathContext.DECIMAL128));
    System.out.println(d1.divide(d3, 2, RoundingMode.HALF_EVEN));

  }
}





import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class Main {
  public static void main(String[] args) {
    final String DOUBLE_FORMAT = "%.17f";
    BigDecimal d1 = new BigDecimal("1");
    BigDecimal d2 = new BigDecimal("2");
    BigDecimal d3 = new BigDecimal("3");
    System.out.println(d2.divide(d3, MathContext.DECIMAL32));
    System.out.println(d2.divide(d3, MathContext.DECIMAL64));
    System.out.println(d2.divide(d3, MathContext.DECIMAL128));
    System.out.println(d2.divide(d3, 2, RoundingMode.HALF_EVEN));

  }
}






요약



돈으로 작업할 때마다 정수 또는 십진수 유형을 사용하는 것이 좋습니다. 필요에 따라 사용자 정의 10진수 유형을 작성해야 할 수 있습니다. 이 경우 일반적인 지침은 다음과 같습니다.
  • 문자열(또는 팩토리)의 생성자
  • 시프트(역시 정수)가 포함된 정수로 내부 표현
  • 나누기 중 반올림 처리

  • 따라서 제공된 10진수 유형 또는 lib의 사용을 고려하는 것이 더 나은 아이디어일 수 있습니다.
    잘 지내세요.

    좋은 웹페이지 즐겨찾기