我爱分享----精确计算时,不要使用float或double

shine333 2011-10-21 11:18:45
照例,还是来一段Effective Java的内容。内容大家应该已经都熟悉,不过还是发现很多新人完全不清楚,所以拿来共享一下。
一、病例:
System.out.println(1.03 - 0.42); // 0.6100000000000001
System.out.println(1.00 - 9 * 0.10); // 0.09999999999999998

又比如,每个单价0.10元,0.20元,0.30元,0.40元……,每种比前一种多0.10元。每样买一个,1元钱能买几个,找零多少

public static void main(String[] args) {
double funds = 1.00;
int itemsBought = 0;
for (double price = .10; funds >= price ; price += .10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}

如果运行这个程序,它会告诉你共可以买3样,找零$0.3999999999999999,显然这是错误答案。

二、原因:
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。浮点的表示采用IEEE 754,大家可以参考一下。

另外浮点数特别不适合用于货币计算。因为浮点型不可能精确表示0.1或者任何10的负数次幂的值。同样,如果计算的数字极大,或者精度要求很高,也不应该用浮点进行计算。

三、解决方案
A java.math.BigDecimal
与浮点不同,它可以提供精度任意(当然在硬件限制范围内)的计算结果,但是,只能进行四则运算或者基于四则运算的其他简单运算。

public static void main(String[] args) {
BigDecimal funds = new BigDecimal("1.00");
int itemsBought = 0;
for (BigDecimal price = new BigDecimal(".10");
funds.compareTo(price) >= 0;
price = price.add(new BigDecimal(".10"))) {
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}

需要注意的是:
1 虽然提供了double型构造函数或方法,但是仍然应使用String以提高精度
2 BigDecimal与String,Integer等类似,为不可变对象(Immutable),计算结果需要重新赋值给变量,下面的代码,没有任何效果。
a.add(b);

3 对于有些可能影响精度的计算(比如除法除不尽)可能需要提供计算结果的精确度及取舍依据。当然@since 1.5,可以不再提供,但是如果无法得出精确值或者除不尽,仍会ArithmeticException
a.divide(b,
3, // 保留3位小数
BigDecimal.ROUND_HALF_UP); // 四舍五入,see BigDecimal.ROUND_XXXX,

ROUND_HALF_UP 四舍五入最常见
ROUND_HALV_EVEN 奇进偶不进,末尾如果不是5,同四舍五入 0.129 --> 0.13,末尾如是5,参考前一位奇偶 0.125 --> 0.12 0.115 --> 0.12,在科学计算时较常见,特别是有舍入后累加的情况,好于四舍五入
ROUND_DOWN,去尾,货币计算让零较常见

B int/long
在有些情况下,float/double可以直接用int/long替代。包括有些情况下的数据库存储也类似。
只说一句话,大家应该可以理解。话说:
1米 = 10分米 = 100厘米 = 1000毫米
1元 = 10角 = 100分
...全文
667 34 打赏 收藏 转发到动态 举报
写回复
用AI写文章
34 条回复
切换为时间正序
请发表友善的回复…
发表回复
shine333 2011-10-29
  • 打赏
  • 举报
回复
[Quote=引用 32 楼 leecyz 的回复:]
引用 1 楼 shine333 的回复:
float/double不能停供完全精确的计算结果。这个原理其实很简单,float/int都是32bit(也就是一共有2^32个精确值),而int的范围是-2^31 ~ 2^31-1,而Float的最大值是3.4028235e+38,远大于2^31 - 1。而且,int只负责个数有限的整数,而浮点却要用来表示个数无穷的小数,显然力不从心。


这里……
[/Quote]
1 我并没有说无数个就能支持
2 如果只支持有限个小数位数,那有限个byte自然可以完成任务。并不一定要用现在的浮点表示方法,底数也并不一定要是2,或者10,甚至以60做底数也可以。况且,早期的计算机,不同机型,其浮点格式千差万别。
leecyz 2011-10-29
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 shine333 的回复:]
float/double不能停供完全精确的计算结果。这个原理其实很简单,float/int都是32bit(也就是一共有2^32个精确值),而int的范围是-2^31 ~ 2^31-1,而Float的最大值是3.4028235e+38,远大于2^31 - 1。而且,int只负责个数有限的整数,而浮点却要用来表示个数无穷的小数,显然力不从心。
[/Quote]

这里不够确切,4个字节无法精确,无数个字节你也无法精确。
计算机处理的是二进制小数,十进制小数和二进制小数之间根本就没法精确,只要转换就有精确的问题。
十进制:0.1 二进制0.00011001100110011001100110011001......
qianmz 2011-10-28
  • 打赏
  • 举报
回复
用 Bigdecimal ~~~
pmlxp 2011-10-28
  • 打赏
  • 举报
回复
之前遇到过不过用js处理了
小基 2011-10-28
  • 打赏
  • 举报
回复
收藏学习
teemai 2011-10-28
  • 打赏
  • 举报
回复
斑竹捏?好帖要推荐
lyhmy 2011-10-28
  • 打赏
  • 举报
回复
学习ing
xia_x123 2011-10-28
  • 打赏
  • 举报
回复
学习了
java1109 2011-10-28
  • 打赏
  • 举报
回复
前几天用 两个long类型的数去相除
然后怎么算 结果都是 整数
我嘞个去 2011-10-28
  • 打赏
  • 举报
回复
进来学习哈~~~
coooliang 2011-10-28
  • 打赏
  • 举报
回复
帮顶咯。希望多分享。
Brad_Lau 2011-10-28
  • 打赏
  • 举报
回复
感谢分享
夕水溪下 2011-10-28
  • 打赏
  • 举报
回复
学习了
yjflinchong 2011-10-28
  • 打赏
  • 举报
回复
学习了。。BigDecimal 好东西。 记住了
chenlin860209 2011-10-28
  • 打赏
  • 举报
回复
学习~~ 谢谢分享~
小笨熊 2011-10-28
  • 打赏
  • 举报
回复
谢谢lz
wode_java 2011-10-26
  • 打赏
  • 举报
回复
学习了,很深刻,然后接分~
五哥 2011-10-26
  • 打赏
  • 举报
回复
学习啦 ,好东西啊
leecyz 2011-10-26
  • 打赏
  • 举报
回复
Math.round也可以解决
dinghun8leech 2011-10-26
  • 打赏
  • 举报
回复
帮顶!
我收藏下。
谢谢楼主分享。
加载更多回复(9)

67,513

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧