计算机程序内部都是二进制表示的,日常的编程实战中,除了处理字符串外,大部分情况下,都在处理数值的计算,如计算多个值的和或平均值。在Java语言体系中,关于数值的类型有int,float,double,long等。
对于有小数的数值,开发人员首先想到的是可以用float或者double表示。比如求1.0和0.42两个值的差,那么心算可得0.58,但是如果用Java来实际计算一下,那么其结果可能和我们预期的是不一样的。
下面结合特殊场景,来给出Java常见的数值加减乘除运算示例,看看结果都是对的么?具体的示例代码如下:
packagecom.jyd; //Java 数值计算精度问题探讨示例publicclassNumDemo { publicstaticvoidmain(String[] args) { inta=10; intc0=a/3 ; System.out.println(c0);//3,只保留整数//float和double大部分情况精确的,但有时会计算错误// 在商业计算上(如金融、银行,财务),不建议使用。floatb=0.1f; floatc2=b/3.0f;//0.033333335System.out.println(c2); floatb2=20019999; System.out.println(b2); //2.002E7doubled=45.9D; doublec3=d/3.0D ; System.out.println(c3);//15.299999999999999 (not 15.3)doublee=2001299.32D; doublee2=0.11D; System.out.println(e+e2); //2001299.4300000002System.out.println(1.0-0.42);//0.5800000000000001System.out.println(4.015*100);//401.49999999999994System.out.println(0.01+0.05);//0.060000000000000005 } }
上述示例代码中,int类型的数值10,/ 除以3,实际返回的是int类型,即3,只保留整数部分。这个计算结果只要我们知道,一般都可以理解。Java中的浮点类型(如float和double)采用 IEEE 754 标准。float类型的值0.1除以3.0时,返回的还是float类型的值,按照数学角度来说,应该返回0.3333...(循环),但是Java程序返回0.033333335。float类型长度为32位,其中用 1 位表示数字的符号,用 8 位表示指数,用 23 位表示小数部分。感觉这个长度有的长,但是实际使用起来精度还是不够。即使是64位的double也是如此。
示例中,20019999这个float类型的值,输出后却是2.002E7,即20020000,真是奇乎怪哉!,如果这个是银行账户的金额,1个账户多1元,那么几亿个户头则多出了几个亿的金额,足见这个计算精度的问题是在特殊场景下,是一个不可接受的方案。 其他的示例涉及到加法、减法、乘法,也同样会出现计算错误的问题。
按照官网的说法,float和double一般用于工程计算,而不能应用商业计算,如银行,财务等。那么商业计算上,该如何解决此问题呢?此时需要用到一个特殊的类型,为BigDecimal。此类型是一个精确的类型,下面给出示例:
packagecom.jyd; importjava.math.BigDecimal; importjava.math.MathContext; //Java 数值计算精度问题探讨示例publicclassBigNumDemo { publicstaticvoidmain(String[] args) { BigDecimalb=newBigDecimal("0.1"); BigDecimalb1=newBigDecimal("3.0"); BigDecimalc2=b.divide(b1,10,BigDecimal.ROUND_DOWN) ;//0.0333333333System.out.println(c2); BigDecimalb2=newBigDecimal(""+20019999); System.out.println(b2); //20019999BigDecimald=newBigDecimal("45.9"); BigDecimald2=newBigDecimal("3.0"); BigDecimalc3=d.divide(d2,10,BigDecimal.ROUND_CEILING) ; System.out.println(c3);//15.3000000000BigDecimale=newBigDecimal("2001299.32"); BigDecimale2=newBigDecimal("0.11"); System.out.println(e.add(e2)); //2001299.43BigDecimale3=newBigDecimal("1.0"); BigDecimale4=newBigDecimal("0.42"); System.out.println(e3.subtract(e4));//0.58BigDecimale5=newBigDecimal("4.015"); BigDecimale6=newBigDecimal("100"); System.out.println(e5.multiply(e6, MathContext.UNLIMITED));//401.500BigDecimale7=newBigDecimal("0.01"); BigDecimale8=newBigDecimal("0.05"); System.out.println(e7.add(e8));//0.06BigDecimale9=newBigDecimal("999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"); System.out.println(e9.add(e3));//1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 } }
BigDecimal有多种构造函数,常用的有2种。建议使用String构造方式,不建议使用double构造方式,可能会出现精度丢失问题。通过上述示例可以看出,商业计算应该使用 BigDecimal类型来完成,虽然在加减乘除这块使用起来有点不够简便,但是仍然比较容易理解。且操作字符串,也更容易操作。在乘除计算中,强制需要制定额外的参数,比如明确小数个数,四舍五入的模式等。
最后,Java编程的细节还有很多,比如不同精度的数值的赋值和转换问题,float和double为什么会导致数据精度的丢失等,可以参考官网文档或其他资料。这里不再赘述。