一、写java小程序的时候碰到的奇怪事儿
今天同学让帮忙给她解决一个数学迭代问题,题目很简单,写代码用个循环就可以计算出结果。核心的java代码最开始如下:
float priceBefore=1.0f; float priceNow = 1.0f;
for(int i = 1;i<10000;i++){
priceNow = priceNow + 0.02f;
System.out.println("第"+i+"天,大米的价格是:"+priceNow+" 要大于"+priceBefore*1.05+"才可以卖的哦");
if(priceNow>priceBefore*1.05){
System.out.println("第"+i+"天,大米可以卖啦");
priceBefore = priceNow;
price.add(priceNow);
priceTime.add(i);
int canSell = (int) (sell*0.05);
sell = sell - canSell;
sum = sum+canSell;
if(sum*priceNow>10000){
System.out.println("第"+i+"天,大米价格是:"+priceNow+",可卖大米"+sum+"斤,赚:"+sum*priceNow+"元钱!");
break;
}
}
我们看到代码的输出是:
第1天,大米的价格是:1.02 要大于1.05才可以卖的哦
第2天,大米的价格是:1.04 要大于1.05才可以卖的哦
第3天,大米的价格是:1.06 要大于1.05才可以卖的哦
第3天,大米可以卖啦
第3天,可卖大米500
第4天,大米的价格是:1.0799999 要大于1.1129999399185182才可以卖的哦
第5天,大米的价格是:1.0999999 要大于1.1129999399185182才可以卖的哦
第6天,大米的价格是:1.1199999 要大于1.1129999399185182才可以卖的哦
第6天,大米可以卖啦
...
我们看到奇怪的到循环第四次的时候,出现了1.0799999这样一连串数字,按照代码逻辑这时参数的结果应该是1.08才对!这对于我这个强迫症患者来说绝对不能容忍,虽说差别只是0.0000001,但是总感觉不是很准确。
二、这是float类型计算不精确搞的鬼
《Effective Java》中已经讲出了这种问题,float/double不能停供完全精确的计算结果。这个原理其实很简单,float/int都是32bit(也就是一共有2^32个精确值),而int的范围是-2^31 ~ 2^31-1,而Float的最大值是3.4028235e+38,远大于2^31 - 1。而且,int只负责个数有限的整数,而浮点却要用来表示个数无穷的小数,显然力不从心。浮点精确值可以简单视作一个以0为中心的正态分布,绝对值越小(越接近0的地方),相邻两个精确值月密集。比如,最近的两个值可能只相差0.00000...几十个0...01,而最远的两个精确值,却差了2.028241E31。
三、如何解决float类型计算不精确的问题
已经封装好的工具包java.math.BigDecimal可以帮助我们完美地解决float/double类型计算不精确的问题。与浮点不同,它可以提供精度任意(当然在硬件限制范围内)的计算结果,但是,只能进行四则运算或者基于四则运算的其他简单运算。
重新修改我的代码:
float priceBefore=1.0f; float priceNow = 1.0f;
for(int i = 1;i<10000;i++){
BigDecimal temp = new BigDecimal(Float.toString(priceNow));
BigDecimal add = new BigDecimal(Float.toString(0.02f));
priceNow=temp.add(add).floatValue();
BigDecimal priceBeforeTemp = new BigDecimal(Float.toString(priceBefore));
BigDecimal multi = new BigDecimal(Float.toString(1.05f));
float priceThrold = priceBeforeTemp.multiply(multi).floatValue();
System.out.println("第"+i+"天,大米的价格是:"+priceNow+" 要大于"+priceThrold+"才可以卖的哦");
if(priceNow>priceThrold){
System.out.println("第"+i+"天,大米可以卖啦");
priceBefore = priceNow;
price.add(priceNow);
priceTime.add(i);
int canSell = (int) (sell*0.05);
sell = sell - canSell;
sum = sum+canSell;
if(sum*priceNow>10000){
System.out.println("第"+i+"天,大米价格是:"+priceNow+",可卖大米"+sum+"斤,赚:"+sum*priceNow+"元钱!");
break;
}
}
我们看到关键代码行
BigDecimal temp = new BigDecimal(Float.toString(priceNow));
BigDecimal add = new BigDecimal(Float.toString(0.02f));
priceNow=temp.add(add).floatValue();
以及:
BigDecimal priceBeforeTemp = new BigDecimal(Float.toString(priceBefore));
BigDecimal multi = new BigDecimal(Float.toString(1.05f));
float priceThrold = priceBeforeTemp.multiply(multi).floatValue();
就是利用BigDecimal对float类型数据分别进行加法以及乘法运算。
这时候我们再来看输出:
第1天,大米的价格是:1.02 要大于1.05才可以卖的哦
第2天,大米的价格是:1.04 要大于1.05才可以卖的哦
第3天,大米的价格是:1.06 要大于1.05才可以卖的哦
第3天,大米可以卖啦
第4天,大米的价格是:1.08 要大于1.113才可以卖的哦
第5天,大米的价格是:1.1 要大于1.113才可以卖的哦
第6天,大米的价格是:1.12 要大于1.113才可以卖的哦
第6天,大米可以卖啦
完美!