나는 항상 돈 이나 유형 을 대표 하지 말라고 들었습니다. 이번에는 나는 당신에게 질문을 제기합니다 : 왜?double
float
나는 아주 좋은 이유가 있다고 확신합니다. 나는 그것이 무엇인지 모릅니다.
나는 항상 돈 이나 유형 을 대표 하지 말라고 들었습니다. 이번에는 나는 당신에게 질문을 제기합니다 : 왜?double
float
나는 아주 좋은 이유가 있다고 확신합니다. 나는 그것이 무엇인지 모릅니다.
답변:
float와 double은 우리가 돈을 위해 사용하는 기본 10 배수를 정확하게 나타낼 수 없기 때문입니다. 이 문제는 Java만을위한 것이 아니라 기본 2 부동 소수점 유형을 사용하는 모든 프로그래밍 언어에 관한 것입니다.
10 진법에서는 10.25를 1025 * 10으로 쓸 수 있습니다. 10-2 (정수 곱하기 10의 거듭 제곱) . IEEE-754 부동 소수점 숫자 는 다르지만이를 생각하는 매우 간단한 방법은 2의 거듭 제곱을 곱하는 것입니다. 예를 들어 164 * 2 -4 (정수 곱하기 2의 거듭 제곱)를 볼 수 있으며 10.25와 같습니다. 그것이 숫자가 메모리에 표현되는 방식은 아니지만 수학적인 의미는 동일합니다.
10 진법에서도이 표기법은 가장 간단한 분수를 정확하게 나타낼 수 없습니다. 예를 들어 1/3을 표현할 수 없습니다. 10 진수 표현이 반복되고 (0.3333 ...), 10의 거듭 제곱에 곱하여 1/3을 구할 수있는 유한 정수는 없습니다. 333333333 * 10 -10 과 같이 3의 긴 시퀀스와 작은 지수를 정할 수는 있지만 정확하지는 않습니다. 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
유형이 있습니다.
1.0 / 10 * 10
1.0과 같지 않을 수 있습니다.
Bloch, J., Effective Java, 2nd ed, 항목 48에서 :
float
과double
는 0.1 (또는 임의의 다른 열 음극 전원)이 같은 표현하는 것은 불가능하기 때문에, 특히 화폐 종류 계산에 부적당된다float
또는double
정확하게한다.예를 들어, $ 1.03이고 42c를 소비한다고 가정하십시오. 돈이 얼마나 남았습니까?
System.out.println(1.03 - .42);
인쇄합니다
0.6100000000000001
.이 문제를 해결하는 올바른 방법은 사용하는 것입니다
BigDecimal
,int
또는long
금전적 계산.
BigDecimal
몇 가지주의 사항이 있지만 (현재 허용되는 답변을 참조하십시오).
long a = 104
은 달러 대신 센트로 사용 하고 계산합니다.
BigDecimal
입니다.
이것은 정확성의 문제가 아니며 정확성의 문제도 아닙니다. 기초 2 대신 계산에 기초 10을 사용하는 인간의 기대치를 충족시키는 것이 문제입니다. 예를 들어, 재무 계산에 복식을 사용하면 수학적으로 "틀린"답이 나오지 않지만 다음과 같은 답을 얻을 수 있습니다. 재정적 인 의미에서 기대되는 것이 아닙니다.
출력 전 마지막 순간에 결과를 반올림하더라도 기대치에 맞지 않는 배가를 사용하여 결과를 얻을 수 있습니다.
계산기를 사용하거나 직접 손으로 결과를 계산하면 1.40 * 165 = 231입니다. 그러나 내부적으로 내 컴파일러 / 운영 체제 환경에서 doubles를 사용하면 230.99999에 가까운 이진수로 저장됩니다. 따라서 숫자를 자르면 231 대신 230이됩니다. 잘라 내기 대신 반올림하는 이유는 다음과 같습니다. 231의 원하는 결과를 얻었습니다. 사실이지만 반올림은 항상 잘립니다. 어떤 반올림 기술을 사용하든 반올림을 기대할 때 반올림되는 경계 조건이 여전히 있습니다. 그들은 일상적인 테스트 또는 관찰을 통해 종종 발견되지 않을 정도로 희귀합니다. 예상대로 작동하지 않는 결과를 나타내는 예제를 검색하려면 코드를 작성해야 할 수도 있습니다.
가장 가까운 페니로 반올림한다고 가정하십시오. 따라서 최종 결과에 100을 곱하고 0.5를 더한 다음 잘라 내고 결과를 100으로 나누어 페니로 돌아갑니다. 저장 한 내부 번호가 3.465 대신 3.46499999 .... 인 경우 가장 가까운 페니로 반올림하면 3.47 대신 3.47이됩니다. 그러나 기본 10 계산에서는 답이 정확히 3.465 여야하며, 3.46이 아니라 3.47로 올림해야합니다. 이러한 종류의 일은 재무 계산에 복식을 사용할 때 실제 생활에서 가끔 발생합니다. 드물기 때문에 종종 문제로 눈에 띄지 않지만 발생합니다.
배가 아닌 내부 계산에 기수 10을 사용하면 코드에 다른 버그가 없다고 가정 할 때 항상 사람이 기대하는 것입니다.
Math.round(0.49999999999999994)
입니까?
나는 이러한 응답 중 일부에 어려움을 겪고 있습니다. 나는 복식과 수레가 재무 계산에 자리를 잡고 있다고 생각합니다. 확실히, 분수가 아닌 화폐 금액을 더하고 뺄 때 정수 클래스 또는 BigDecimal 클래스를 사용할 때 정밀도가 손실되지 않습니다. 그러나 더 복잡한 연산을 수행 할 때 숫자를 저장하는 방법에 관계없이 종종 소수 또는 소수 자릿수로 끝나는 결과가 나타납니다. 문제는 결과를 제시하는 방법입니다.
결과가 반올림과 반올림 사이의 경계선에 있고 마지막 페니가 실제로 중요하다면, 소수 자릿수를 더 많이 표시하여 답변이 거의 중간에 있음을 시청자에게 알려 주어야합니다.
복식의 문제, 그리고 수레의 문제는 큰 숫자와 작은 숫자를 결합하는 데 사용되는 경우입니다. 자바에서는
System.out.println(1000000.0f + 1.2f - 1000000.0f);
결과
1.1875
수레와 복식은 근사치입니다. BigDecimal을 생성하고 float를 생성자에 전달하면 float이 실제로 동일한 것을 볼 수 있습니다.
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
이것은 아마도 당신이 $ 1.01을 표현하고 싶지 않은 방법 일 것입니다.
문제는 IEEE 사양에 모든 분수를 정확하게 표현할 수있는 방법이 없으며, 일부는 반복 분수로 끝나기 때문에 근사 오차가 발생한다는 것입니다. 회계사가 정확히 1 페니로 나오고 고객이 청구서를 지불하면 고객이 화가 났고 지불이 처리 된 후 .01을 지불해야하며 수수료가 부과되거나 계정을 폐쇄 할 수 없으므로 사용하는 것이 좋습니다 소수점 이하 자릿수 (C #) 또는 Java의 java.math.BigDecimal과 같은 정확한 유형
반올림하면 오류를 제어 할 수있는 것은 아닙니다 . Peter Lawrey의이 기사를 참조하십시오 . 처음에는 반올림하지 않아도 더 쉽습니다. 돈을 처리하는 대부분의 응용 프로그램은 많은 수학을 요구하지 않습니다. 작업은 물건을 추가하거나 다른 버킷에 금액을 할당하는 것으로 구성됩니다. 부동 소수점과 반올림을 도입하면 문제가 복잡해집니다.
float
, double
하고 BigDecimal
있는 대표 정확한 값을. 다른 작업뿐만 아니라 코드에서 개체로의 변환도 정확하지 않습니다. 유형 자체는 정확하지 않습니다.
하락세가 발생할 위험이 있지만 통화 계산에 부동 소수점 숫자의 부적합이 과대 평가되었다고 생각합니다. zneak이 설명하는 2 진법 표현 불일치를 해결하기 위해 센트 반올림을 올바르게 수행하고 작업하기에 충분한 유효 자릿수가있는 한 문제는 없습니다.
Excel에서 통화로 계산하는 사람들은 항상 배정 밀도 부동 소수점을 사용했으며 (Excel에는 통화 유형이 없습니다) 반올림 오류에 대해 불평하는 사람은 아직 없습니다.
물론, 당신은 이성 안에 머물러 있어야합니다. 예를 들어 간단한 웹숍은 배정 밀도 부동 소수점에 문제가 발생하지 않지만 회계 또는 많은 양의 (제한되지 않은) 숫자를 추가 해야하는 다른 작업을 수행하는 경우 10 피트로 부동 소수점 숫자를 만지고 싶지 않습니다. 폴.
부동 소수점 유형은 근사 적으로 십진 데이터 만 나타낼 수 있지만 숫자를 제시하기 전에 필요한 정밀도로 반올림하면 올바른 결과를 얻는다는 것도 사실입니다. 보통.
일반적으로 이중 유형의 정밀도는 16 자리 미만입니다. 더 나은 정밀도가 필요한 경우 적합한 유형이 아닙니다. 또한 근사치가 누적 될 수 있습니다.
고정 소수점 산술을 사용하더라도 여전히 숫자를 반올림해야하는데,주기적인 10 진수를 얻는 경우 BigInteger 및 BigDecimal에서 오류가 발생하지 않기 때문입니다. 여기에도 근사치가 있습니다.
예를 들어 역사적으로 재무 계산에 사용 된 COBOL의 최대 정밀도는 18입니다. 따라서 종종 암시 적 반올림이 있습니다.
결론적으로, 이중은 16 자리 정밀도에 부적합하며, 이는 대략적이기 때문에 충분하지 않을 수 있습니다.
후속 프로그램의 다음 출력을 고려하십시오. double을 반올림 한 후에는 BigDecimal과 동일한 결과를 정밀도 16까지 제공합니다.
Precision 14
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611
Double : 56789.012345 / 1111111111 = 0.000051110111115611
Precision 15
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110
Double : 56789.012345 / 1111111111 = 0.0000511101111156110
Precision 16
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101
Double : 56789.012345 / 1111111111 = 0.00005111011111561101
Precision 17
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611011
Double : 56789.012345 / 1111111111 = 0.000051110111115611013
Precision 18
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double : 56789.012345 / 1111111111 = 0.0000511101111156110125
Precision 19
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double : 56789.012345 / 1111111111 = 0.00005111011111561101252
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;
public class Exercise {
public static void main(String[] args) throws IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
String amount = "56789.012345";
String quantity = "1111111111";
int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
for (int i = 0; i < precisions.length; i++) {
int precision = precisions[i];
System.out.println(String.format("Precision %d", precision));
System.out.println("------------------------------------------------------");
execute("BigDecimalNoRound", amount, quantity, precision);
execute("DoubleNoRound", amount, quantity, precision);
execute("BigDecimal", amount, quantity, precision);
execute("Double", amount, quantity, precision);
System.out.println();
}
}
private static void execute(String test, String amount, String quantity,
int precision) throws IllegalArgumentException, SecurityException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
String.class, int.class);
String price;
try {
price = (String) impl.invoke(null, amount, quantity, precision);
} catch (InvocationTargetException e) {
price = e.getTargetException().getMessage();
}
System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
quantity, price));
}
public static String divideUsingDoubleNoRound(String amount,
String quantity, int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
String price = Double.toString(price0);
return price;
}
public static String divideUsingDouble(String amount, String quantity,
int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
MathContext precision0 = new MathContext(precision);
String price = new BigDecimal(price0, precision0)
.toString();
return price;
}
public static String divideUsingBigDecimal(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
MathContext precision0 = new MathContext(precision);
//calculation
BigDecimal price0 = amount0.divide(quantity0, precision0);
// presentation
String price = price0.toString();
return price;
}
public static String divideUsingBigDecimalNoRound(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
//calculation
BigDecimal price0 = amount0.divide(quantity0);
// presentation
String price = price0.toString();
return price;
}
}
부동 소수점 숫자의 결과는 정확하지 않으므로 정확한 결과가 필요하고 근사값이 아닌 재무 계산에 적합하지 않습니다. float 및 double은 공학 및 과학 계산 용으로 설계되었으며 여러 번 정확한 결과를 생성하지 않으며 부동 소수점 계산 결과는 JVM마다 다를 수 있습니다. 돈 가치를 나타내는 데 사용되는 BigDecimal 및 double primitive의 예를 아래에서 살펴보십시오. 부동 소수점 계산이 정확하지 않을 수 있으며 재무 계산에 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
double
FP를 센트로 사용하면 FP를 소수점으로 계산하지 않아도 0.5 센트로 계산하는 데 문제가 없습니다. 부동 소수점 계산이 이진 FP 또는 십진 FP를 통해 123.499941 ¢의이자 값을 생성하는 경우 이중 반올림 문제는 동일합니다. 전제는 수학적으로 정확한 값으로 가정하고 십진수 FP는 동일하다고 가정합니다. 십진 FP조차도 보장하지 않습니다. 0.5 / 7.0 * 7.0은 이진 및 십진 FP의 문제입니다. IAC는 다음 버전의 C가 십진 FP를 제공 할 것으로 기대하기 때문에 대부분은 혼란 스러울 것입니다.
앞서 언급했듯이 소프트웨어가 작은 오류를 반올림하면 돈을 이중 또는 부동으로 표현하는 것이 처음에는 좋아 보일 것입니다. 이로 인해 10 개의 기본 거듭 제곱의 배수에 대한 완벽한 정확도가 필요한 돈을 처리하기에는 플로트와 배가 부적절합니다. "
마지막으로 Java에는 Currency And Money를 사용하는 표준 방법이 있습니다!
JSR 354 : 화폐 및 통화 API
JSR 354는 Money and Currency를 사용하여 포괄적 인 계산을 표현, 전송 및 수행하기위한 API를 제공합니다. 이 링크에서 다운로드 할 수 있습니다.
사양은 다음과 같이 구성됩니다.
- 화폐 금액 및 통화 처리를위한 API
- 상호 교환 가능한 구현을 지원하는 API
- 구현 클래스의 인스턴스를 작성하기위한 팩토리
- 금액 계산, 변환 및 서식 지정 기능
- Java 9에 포함될 예정인 Money and Currencies 작업을위한 Java API
- 모든 사양 클래스와 인터페이스는 javax.money. * 패키지에 있습니다.
JSR 354의 샘플 예 : Money and Currency API :
MonetaryAmount를 작성하여 콘솔에 인쇄하는 예는 다음과 같습니다.
MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
참조 구현 API를 사용할 때 필요한 코드는 훨씬 간단합니다.
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
API는 MonetaryAmounts를 사용한 계산도 지원합니다.
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
통화 단위 및 통화 금액
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount에는 지정된 통화, 숫자 금액, 정밀도 등을 액세스 할 수있는 다양한 방법이 있습니다.
MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();
int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5
// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;
반올림 연산자를 사용하여 금액을 반올림 할 수 있습니다.
CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35
MonetaryAmount 콜렉션으로 작업 할 때 필터링, 정렬 및 그룹화를위한 유용한 유틸리티 메소드를 사용할 수 있습니다.
List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));
사용자 정의 통화 금액 조작
// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
return Money.of(tenPercent, amount.getCurrency());
};
MonetaryAmount dollars = Money.of(12.34567, "USD");
// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567
자원:
Java 9 Money and Currency API 살펴보기 (JSR 354)
다음 사항도 참조 : JSR 354-통화 및 돈
계산에 다양한 단계가 포함 된 경우 임의 정밀도 연산은 100 %를 포함하지 않습니다.
완벽한 결과 표현을 사용하는 신뢰할 수있는 유일한 방법 (마지막 단계로 나누기 작업을 수행하는 사용자 지정 분수 데이터 형식 사용)은 마지막 단계에서 10 진수 표기법으로 만 변환합니다.
소수점 이하 자릿수가 너무 많은 숫자가 항상있을 수 있기 때문에 임의의 정밀도는 도움이되지 않습니다 0.6666666
. 따라서 각 단계마다 작은 오류가 발생합니다.
이러한 오류는 더해져 결국에는 더 이상 무시하기 쉽지 않을 수 있습니다. 이것을 오류 전파 라고 합니다.
대부분의 답변은 돈과 통화 계산에 복식을 사용해서는 안되는 이유를 강조했습니다. 그리고 나는 그들에게 완전히 동의합니다.
복식을 절대 그 목적으로 사용할 수는 없습니다.
나는 매우 낮은 gc 요구 사항을 가진 많은 프로젝트에서 일했으며 BigDecimal 객체를 갖는 것이 그 오버 헤드에 큰 기여를했습니다.
이중 표현에 대한 이해 부족과이 현명한 제안을 제공하는 정확성과 정밀도 처리 경험이 부족합니다.
프로젝트의 정밀도 및 정확도 요구 사항을 처리 할 수 있으면 처리 할 수 있습니다.이 범위는 다루는 이중 값의 범위를 기반으로 수행되어야합니다.
구아바의 FuzzyCompare 방법을 참조하여 더 많은 아이디어를 얻을 수 있습니다. 파라미터 공차가 핵심입니다. 우리는 증권 거래 애플리케이션에 대해이 문제를 다루었 고, 다른 범위의 다른 숫자 값에 사용할 공차를 철저히 조사했습니다.
또한 이중 래퍼를 해시 맵이 구현 된 맵 키로 사용하려는 경우가 있습니다. Double.equals 및 해시 코드 (예 : 값 "0.5"및 "0.6-0.1")가 큰 혼란을 초래하기 때문에 매우 위험합니다.
이 질문에 게시 된 많은 답변은 IEEE와 부동 소수점 산술 관련 표준에 대해 설명합니다.
컴퓨터 과학이 아닌 배경 (물리 및 공학)에서 나온 문제를 다른 관점에서 보는 경향이 있습니다. 나에게 수학 계산에서 double 또는 float을 사용하지 않는 이유는 너무 많은 정보를 잃어 버릴 수 있기 때문입니다.
대안은 무엇입니까? 많은 것들이 있습니다.
Java의 BigDecimal은 Java 언어에 고유합니다. Apfloat는 Java를위한 또 다른 임의 정밀도 라이브러리입니다.
C #의 10 진수 데이터 형식은 28 개의 유효 숫자에 대한 Microsoft의 .NET 대안입니다.
SciPy (Scientific Python)는 아마도 재무 계산을 처리 할 수도 있습니다 (시도하지는 않았지만 그렇게 생각합니다).
GMP (GNU Multiple Precision Library) 및 GNU MFPR 라이브러리는 C 및 C ++를위한 2 개의 무료 오픈 소스 리소스입니다.
JavaScript (!)에 대한 수치 정밀도 라이브러리도 있으며 재무 계산을 처리 할 수있는 PHP라고 생각합니다.
많은 컴퓨터 언어뿐만 아니라 독점적 인 (특히 Fortran의 경우) 공개 소스 솔루션도 있습니다.
나는 훈련을받는 컴퓨터 과학자가 아닙니다. 그러나 Java의 BigDecimal 또는 C #의 10 진수에 의존하는 경향이 있습니다. 내가 나열한 다른 솔루션을 시도하지 않았지만 아마도 매우 좋습니다.
저에게는 BigDecimal이 지원하는 방법 때문에 BigDecimal을 좋아합니다. C #의 10 진수는 매우 좋지만 원하는만큼 사용할 수있는 기회가 없었습니다. 나는 여가 시간에 과학적으로 관심있는 계산을 수행하며 BigDecimal은 부동 소수점 숫자의 정밀도를 설정할 수 있기 때문에 잘 작동하는 것 같습니다. BigDecimal의 단점은 무엇입니까? 특히 나누기 방법을 사용하는 경우 시간이 느려질 수 있습니다.
C, C ++ 및 Fortran의 무료 독점 라이브러리를 빠른 속도로 살펴볼 수 있습니다.
이전 답변을 추가하기 위해 BigDecimal 외에도 Java에서 Joda-Money 를 구현 하여 질문에서 해결 된 문제를 해결할 수있는 옵션이 있습니다. Java 모듈 이름은 org.joda.money입니다.
Java SE 8 이상이 필요하며 종속성이 없습니다.
더 정확하게 말하면 컴파일 타임 종속성이 있지만 필수는 아닙니다.
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
Joda Money 사용 예 :
// create a monetary value
Money money = Money.parse("USD 23.87");
// add another amount with safe double conversion
CurrencyUnit usd = CurrencyUnit.of("USD");
money = money.plus(Money.of(usd, 12.43d));
// subtracts an amount in dollars
money = money.minusMajor(2);
// multiplies by 3.5 with rounding
money = money.multipliedBy(3.5d, RoundingMode.DOWN);
// compare two amounts
boolean bigAmount = money.isGreaterThan(dailyWage);
// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
// use a BigMoney for more complex calculations where scale matters
BigMoney moneyCalc = money.toBigMoney();
문서 : http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html
구현 예 : https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money
몇 가지 예 ... 이것은 거의 모든 프로그래밍 언어에서 작동합니다 (실제로 예상대로 작동하지 않습니다) ... 나는 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!!!
Float는 다른 디자인의 이진 형태의 Decimal입니다. 그것들은 서로 다른 두 가지입니다. 서로 변환 할 때 두 유형간에 오류가 거의 없습니다. 또한 float는 과학적으로 무한한 많은 수의 값을 나타내도록 설계되었습니다. 즉, 고정 바이트 수를 사용하여 정밀도를 극도로 작고 극도로 크게 잃도록 설계되었습니다. 10 진수는 무한한 수의 값을 나타낼 수 없으며 10 진수의 수에만 한정됩니다. Float와 Decimal은 다른 목적을위한 것입니다.
통화 값에 대한 오류를 관리하는 몇 가지 방법이 있습니다.
긴 정수를 사용하고 대신 센트 단위로 계산하십시오.
배정도를 사용하고 유효 숫자를 15로만 유지하면 소수점을 정확하게 시뮬레이션 할 수 있습니다. 값을 제시하기 전에 반올림하십시오. 계산할 때 자주 반올림합니다.
Java BigDecimal과 같은 10 진수 라이브러리를 사용하므로 10 진수를 시뮬레이션하기 위해 double을 사용할 필요가 없습니다.
추신 : 대부분의 휴대용 과학 계산기 브랜드는 부동 소수점 대신 십진수로 작동한다는 것을 알고 있습니다. 따라서 하나의 불만 제기 플로팅 오류가 없습니다.