关于float和double丢失精度问题及解决方案

double result = 1.0 - 0.9;

System.out.println(result);//0.09999999999999998

出现这种结果的原因:float和double类型尤其不适合用于货币运算,因为要让一个float或double精确的表示0.1或者任何其他负数次方值是不可能的(十进制系统中不能准确的表示出1/3,同样二进制系统也不能准确的表示1/10)。

1.十进制整数转为二进制数:

例子:11表示成二进制数:

11/2 =5 余1

5/2 = 2 余1

2/2 = 1 余0

1/2 = 0 余1

0结束,11二进制表示为(从下往上):1011

注意:只要遇到除以后的结果为0就结束了。所有的整数除以2一定能够最终得到0,但是小数就不能,小数转变为二进制的算法就有可能会无限循环下去。

2.十进制小数转为二进制数

算法是乘以2知道没有了小数为止,例子:

0.9表示成二进制数:

0.9*2 = 1.8 取整数部分:1

0.8*2 = 1.6 取整数部分:1

0.6*2 = 1.2 取整数:1

0.2*2 = 0.4 取整数:0

0.4*2 = 0.8 取整数:0

0.8*2 = 1.6 取整数:1

。。。。

0.9二进制表示为(从上往下):1100100100100.......

注意:上面的计算过程循环了,也就是说乘以2永远不能消灭小数部分,这样算法将无限下去。显然,小数的二进制表示有时是不能精确的。道理很简单,十进制系统中不能准确的表示出1/3,同样二进制也无法准确的表示1/10。这也是浮点型出现精度丢失问题的主要原因。

解决方案一:

如果不介意记录十进制的小数点,而且数值不大,那么可以使用long,int等基本类型,

int resultInt = 10 -9;

double result  = (double)resultInt / 100;//自己控制小数点

解决方案二:

使用BigDecimal,而且需要在构造参数使用String类型.

float和double只能用来做科学计算或者工程计算,在商业计算等精确计算中,要用java.math.BigDecimal。

在《EffectiveJava》这本书中就给出了一个解决方法。该书中也指出,float和double只能用来做科学计算或者是工程计算,在商业计算等精确计算中,我们要用java.math.BigDecimal。

BigDecimal类一个有4个方法,我们只关心对我们解决浮点型数据进行精确计算有用的方法,即

BigDecimal(double value) // 将double型数据转换成BigDecimal型数据

思路很简单,我们先通过BigDecimal(double

value)方法,将double型数据转换成BigDecimal数据,然后就可以正常进行精确计算了。等计算完毕后,我们可以对结果做一些处理,比如

对除不尽的结果可以进行四舍五入。最后,再把结果由BigDecimal型数据转换回double型数据。

这个思路很正确,但是如果你仔细看看API里关于BigDecimal的详细说明,你就会知道,如果需要精确计算,我们不能直接用double,而非要用

String来构造BigDecimal不可!所以,我们又开始关心BigDecimal类的另一个方法,即能够帮助我们正确完成精确计算的

BigDecimal(String value)方法。

// BigDecimal(String value)能够将String型数据转换成BigDecimal型数据

那么问题来了,想像一下吧,如果我们要做一个浮点型数据的加法运算,需要先将两个浮点数转为String型数据,然后用

BigDecimal(String

value)构造成BigDecimal,之后要在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮

点数。如果每次做浮点型数据的计算都要如此,你能够忍受这么烦琐的过程吗?至少我不能。所以最好的办法,就是写一个类,在类中完成这些繁琐的转换过程。这

样,在我们需要进行浮点型数据计算的时候,只要调用这个类就可以了。网上已经有高手为我们提供了一个工具类Arith来完成这些转换操作。它提供以下静态

方法,可以完成浮点型数据的加减乘除运算和对其结果进行四舍五入的操作:

public static double add(double v1,double v2)

public static double sub(double v1,double v2)

public static double mul(double v1,double v2)

public static double div(double v1,double v2)

public static double div(double v1,double v2,int scale)

public static double round(double v,int scale)

下面会附上Arith的源代码,大家只要把它编译保存好,要进行浮点数计算的时候,在你的源程序中导入Arith类就可以使用以上静态方法来进行浮点数的精确计算了。

importjava.math.BigDecimal;

/**

* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精

* 确的浮点数运算,包括加减乘除和四舍五入。

*/

publicclassArith{

//默认除法运算精度

privatestaticfinalintDEF_DIV_SCALE =10;

//这个类不能实例化

privateArith(){

}

/**

* 提供精确的加法运算。

* @param v1 被加数

* @param v2 加数

* @return 两个参数的和

*/

publicstaticdoubleadd(doublev1,doublev2){

BigDecimal b1 =newBigDecimal(Double.toString(v1));

BigDecimal b2 =newBigDecimal(Double.toString(v2));

returnb1.add(b2).doubleValue();

}

/**

* 提供精确的减法运算。

* @param v1 被减数

* @param v2 减数

* @return 两个参数的差

*/

publicstaticdoublesub(doublev1,doublev2){

BigDecimal b1 =newBigDecimal(Double.toString(v1));

BigDecimal b2 =newBigDecimal(Double.toString(v2));

returnb1.subtract(b2).doubleValue();

}

/**

* 提供精确的乘法运算。

* @param v1 被乘数

* @param v2 乘数

* @return 两个参数的积

*/

publicstaticdoublemul(doublev1,doublev2){

BigDecimal b1 =newBigDecimal(Double.toString(v1));

BigDecimal b2 =newBigDecimal(Double.toString(v2));

returnb1.multiply(b2).doubleValue();

}

/**

* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到

* 小数点以后10位,以后的数字四舍五入。

* @param v1 被除数

* @param v2 除数

* @return 两个参数的商

*/

publicstaticdoublediv(doublev1,doublev2){

returndiv(v1,v2,DEF_DIV_SCALE);

}

/**

* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指

* 定精度,以后的数字四舍五入。

* @param v1 被除数

* @param v2 除数

* @param scale 表示表示需要精确到小数点以后几位。

* @return 两个参数的商

*/

publicstaticdoublediv(doublev1,doublev2,intscale){

if(scale<0){

thrownewIllegalArgumentException(

"The scale must be a positive integer or zero");

}

BigDecimal b1 =newBigDecimal(Double.toString(v1));

BigDecimal b2 =newBigDecimal(Double.toString(v2));

returnb1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();

}

/**

* 提供精确的小数位四舍五入处理。

* @param v 需要四舍五入的数字

* @param scale 小数点后保留几位

* @return 四舍五入后的结果

*/

publicstaticdoubleround(doublev,intscale){

if(scale<0){

thrownewIllegalArgumentException(

"The scale must be a positive integer or zero");

}

BigDecimal b =newBigDecimal(Double.toString(v));

BigDecimal one =newBigDecimal("1");

returnb.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();

}

};


转自;http://blog.csdn.net/wanted_tao/article/details/52880737

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容