一直有人告诉我,永远不要用double
float
型或float
型表明钱,这一次我向你提出一个问题:为何? java
我敢确定有一个很好的理由,我根本不知道这是什么。 ide
这不是精度问题,也不是精度问题。 这是要知足使用10而不是2进行计算的人们的指望的问题。例如,使用double进行财务计算不会产生数学意义上“错误”的答案,但能够得出答案。而不是财务上的预期。 测试
即便您在输出前的最后一分钟对结果进行四舍五入,您仍然偶尔会使用不符合预期的双精度来得到结果。 spa
使用计算器或手动计算结果,则精确地为1.40 * 165 = 231。 可是,在内部使用双打,在个人编译器/操做系统环境中,它存储为接近230.99999的二进制数字...所以,若是截断该数字,则会获得230而不是231。您可能会认为舍入而不是截断会给出了231的指望结果。是的,可是舍入老是涉及截断。 不管您使用哪一种舍入技术,仍然存在像这样的边界条件,当您指望将其舍入时,该边界条件将舍入。 它们很是稀有,常常经过偶然的测试或观察将不会被发现。 您可能必须编写一些代码来搜索示例,这些示例说明结果与预期不符。 操作系统
假设您想四舍五入到最接近的美分。 这样就获得了最终结果,乘以100,再加上0.5,截断,而后将结果除以100,即可以获得几分钱。 若是您存储的内部号码是3.46499999 ....而不是3.465,则将数字四舍五入时将获得3.46而不是3.47。 可是,以10为基数的计算可能已经代表,答案应该刚好是3.465,显然应该向上舍入为3.47,而不是向下为3.46。 当您使用倍数进行财务计算时,这类事情偶尔会在现实生活中发生。 它不多见,所以一般不会引发人们的注意,可是它确实会发生。 设计
若是您使用10为基数进行内部计算而不是使用双精度数,那么假设代码中没有其余错误,答案老是彻底是人类指望的结果。 code
确实,浮点类型只能表示近似十进制数据,但若是在显示数字以前将数字四舍五入到必要的精度,则也能够获得正确的结果。 一般。 orm
一般由于double类型的精度小于16个数字。 若是您须要更高的精度,则不适合使用此类型。 近似值也会累积。 ci
必须说,即便您使用定点算术,您仍然必须对数字进行四舍五入,这是否不是由于若是您得到周期十进制数,则BigInteger和BigDecimal会给出错误。 所以,这里也有一个近似值。 get
例如,过去用于财务计算的COBOL的最大精度为18个数字。 所以一般会有一个隐式的舍入。
最后,我认为双精度不适合其16位精度,这多是不够的,不是由于它是近似值。
考虑后续程序的如下输出。 它显示在四舍五入后,得出与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; } }
我对其中的一些回应感到困扰。 我认为双打和浮动交易在财务计算中占有一席之地。 固然,当使用整数类或BigDecimal类进行加减时,不会减小精度。 可是,当执行更复杂的操做时,不管以何种方式存储数字,结果一般会排到几个或多个小数位。 问题是您如何呈现结果。
若是您的结果是在四舍五入和四舍五入之间的边界上,而最后一分钱确实很重要,那么您应该告诉观众答案几乎在中间-经过显示更多的小数位。
双精度数(尤为是浮点数)的问题在于将它们用于组合大数和小数时。 在Java中,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
结果是
1.1875
浮点数的结果不精确,这使其不适用于须要精确结果而不是近似值的任何财务计算。 float和double是为工程和科学计算而设计的,不少时候也没法得出准确的结果,并且浮点计算的结果可能因JVM而异。 请看下面的示例BigDecimal和用于表示货币价值的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
若是您的计算涉及多个步骤,那么任意精度的算术都不会100%覆盖您。
使用结果的完美表示的惟一可靠方法(使用自定义的Fraction数据类型,该数据类型将对最后一步进行除法运算)而且仅在最后一步中转换为十进制表示法。
任意精度将无济于事,由于总会有小数位那么多的数字,或者诸如0.6666666之类的结果。 所以,您在每一个步骤中都会有一些小错误。
该错误将累加,最终可能变得不容易被忽略。 这称为错误传播 。