Floating point 为什么不使用Double或Float来表示货币?
我一直被告知永远不要用Floating point 为什么不使用Double或Float来表示货币?,floating-point,currency,Floating Point,Currency,我一直被告知永远不要用double或float类型来表示货币,这次我向你提出了一个问题:为什么 我确信有一个很好的理由,我只是不知道它是什么。因为浮点数和双精度不能准确地表示我们用于货币的基数10倍。这个问题不仅针对Java,还针对任何使用Base2浮点类型的编程语言 在10进制中,可以将10.25写成1025*10-2(10的幂的整数倍)。它们是不同的,但考虑它们的一个非常简单的方法是乘以二的幂。例如,你可以看到164*2-4(一个整数乘以二的幂),也就是10.25。这不是数字在内存中的表示方
double
或float
类型来表示货币,这次我向你提出了一个问题:为什么
我确信有一个很好的理由,我只是不知道它是什么。因为浮点数和双精度不能准确地表示我们用于货币的基数10倍。这个问题不仅针对Java,还针对任何使用Base2浮点类型的编程语言 在10进制中,可以将10.25写成1025*10-2(10的幂的整数倍)。它们是不同的,但考虑它们的一个非常简单的方法是乘以二的幂。例如,你可以看到164*2-4(一个整数乘以二的幂),也就是10.25。这不是数字在内存中的表示方式,但数学含义是相同的 即使以10为基数,这种表示法也不能准确地表示最简单的分数。例如,您不能表示1/3:十进制表示是重复的(0.3333…),因此没有可以乘以10的幂得到1/3的有限整数。你可以选择一个长的3的序列和一个小的指数,比如333*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.01,然后将其乘以10,则不会得到1。相反,您将得到0.099999786之类的值 将货币表示为
double
或float
起初可能看起来不错,因为软件会将微小的错误舍入,但当您对不精确的数字执行更多的加法、减法、乘法和除法时,错误会复合,最终会得到明显不准确的值。这使得浮点数和双倍点数不足以处理货币,因为在处理货币时,要求基本10次方幂的倍数具有完美的精度
一种几乎适用于任何语言的解决方案是使用整数代替,并计算美分。例如,1025美元将是10.25美元。一些语言还内置了处理金钱的类型。除此之外,Java有类,C有类型。因为浮点和双精度不能准确地表示我们用于货币的基数10倍。这个问题不仅针对Java,还针对任何使用Base2浮点类型的编程语言 在10进制中,可以将10.25写成1025*10-2(10的幂的整数倍)。它们是不同的,但考虑它们的一个非常简单的方法是乘以二的幂。例如,你可以看到164*2-4(一个整数乘以二的幂),也就是10.25。这不是数字在内存中的表示方式,但数学含义是相同的 即使以10为基数,这种表示法也不能准确地表示最简单的分数。例如,您不能表示1/3:十进制表示是重复的(0.3333…),因此没有可以乘以10的幂得到1/3的有限整数。你可以选择一个长的3的序列和一个小的指数,比如333*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.01,然后将其乘以10,则不会得到1。相反,您将得到0.099999786之类的值 将货币表示为
double
或float
起初可能看起来不错,因为软件会将微小的错误舍入,但当您对不精确的数字执行更多的加法、减法、乘法和除法时,错误会复合,最终会得到明显不准确的值。这使得浮点数和双倍点数不足以处理货币,因为在处理货币时,要求基本10次方幂的倍数具有完美的精度
一种几乎适用于任何语言的解决方案是使用整数代替,并计算美分。例如,1025美元将是10.25美元。一些语言还内置了处理金钱的类型。除此之外,Java拥有类,C#拥有类型。浮点和双精度是近似值。如果创建一个BigDecimal并将一个浮点值传递给构造函数,则可以看到该浮点值实际上等于:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
这可能不是您想要表示1.01美元的方式
问题是IEEE规范没有一种精确表示所有分数的方法,其中一些分数最终会成为重复分数,因此最终会出现近似错误。因为会计师喜欢把事情说得一清二楚
System.out.println(1.03 - .42);
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;
}
}
System.out.println(1000000.0f + 1.2f - 1000000.0f);
1.1875
// 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 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...");
// so be careful comparing equality in this cases!!!
}
round problems?: current total: 0.9999999999999999
round problems?: current total: 2.7755575615628914E-17
round problems?: is total equal to ZERO? No...
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
// 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();