Tom在一家汽车配件商店购买了一个价值$1.10的火花塞,但是他钱包中都是两美元一张的钞票。如果他用一张两美元的钞票支付这个火花塞,那么应该找给他多少零钱呢? 
下面是一个试图解决上述问题的程序,它会打印出什么呢? 
public class Change{
public static void main(String args[]){
System.out.println(2.00 – 1.10);
}
}
你可能会很天真地期望该程序能够打印出0.90,但是它如何才能知道你想要打印小数点后两位小数呢? 
如果你对在Double.toString文档中所设定的将double类型的值转换为字符串的规则有所了解,你就会知道该程序打印出来的小数,是足以将double类型的值与最靠近它的临近值区分出来的最短的小数,它在小数点之前和之后都至少有一位。因此,看起来,该程序应该打印0.9是合理的。 
这么分析可能显得很合理,但是并不正确。如果你运行该程序,你就会发现它打印的是0.8999999999999999。 
问题在于1.1这个数字不能被精确表示成为一个double,因此它被表示成为最接近它的double值。该程序从2中减去的就是这个值。遗憾的是,这个计算的结果并不是最接近0.9的double值。表示结果的double值的最短表示就是你所看到的打印出来的那个可恶的数字。 
更一般地说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示的。 
如果你正在用的是JDK
5.0或更新的版本,那么你可能会受其诱惑,通过使用printf工具来设置输出精度的方订正该程序: 
//拙劣的解决方案——仍旧是使用二进制浮点数
System.out.printf(“%.2f%n”,2.00 – 1.10);
这条语句打印的是正确的结果,但是这并不表示它就是对底层问题的通用解决方案:它使用的仍旧是二进制浮点数的double运算。浮点运算在一个范围很广的值域上提供了很好的近似,但是它通常不能产生精确的结果。二进制浮点对于货币计算是非常不适合的,因为它不可能将0.1——或者10的其它任何次负幂——精确表示为一个长度有限的二进制小数 
解决该问题的一种方式是使用某种整数类型,例如int或long,并且以分为单位来执行计算。如果你采纳了此路线,请确保该整数类型大到足够表示在程序中你将要用到的所有值。对这里举例的谜题来说,int就足够了。下面是我们用int类型来以分为单位表示货币值后重写的println语句。这个版本将打印出正确答案90分: 
System.out.println((200 – 110) + “cents”);
解决该问题的另一种方式是使用执行精确小数运算的BigDecimal。它还可以通过JDBC与SQL
DECIMAL类型进行互操作。这里要告诫你一点:
一定要用BigDecimal(String)构造器,而千万不要用BigDecimal(double)。后一个构造器将用它的参数的“精确”值来创建一个实例:new
BigDecimal(.1)将返回一个表示0.100000000000000055511151231257827021181583404541015625的BigDecimal。通过正确使用BigDecimal,程序就可以打印出我们所期望的结果0.90: 
import java.math.BigDecimal;
public class Change1{
public static void main(String args[]){
System.out.println(new BigDecimal(“2.00”).
subtract(new BigDecimal(“1.10”)));
}
}
这个版本并不是十分地完美,因为Java并没有为BigDecimal提供任何语言上的支持。使用BigDecimal的计算很有可能比那些使用原始类型的计算要慢一些,对某些大量使用小数计算的程序来说,这可能会成为问题,而对大多数程序来说,这显得一点也不重要。 
总之,
在需要精确答案的地方,要避免使用float和double;对于货币计算,要使用int、long或BigDecimal。对于语言设计者来说,应该考虑对小数运算提供语言支持。一种方式是提供对操作符重载的有限支持,以使得运算符可以被塑造为能够对数值引用类型起作用,例如BigDecimal。另一种方式是提供原始的小数类型,就像COBOL与PL/I所作的一样。 

此篇文章搬运于: (更多详情点进去阅读,此篇仅作搬运)

第二章 Java浮点数精确计算,第二章java浮点数

1、实际意义

在实际开发中,如果需要进行float或double的精确计算(尤其是财务计算),直接使用float或double是不行的(具体的例子看下边的代码的main方法的测试结果),需要使用BigDecimal。

 

2、代码

图片 1package
com.xxx.util; import java.math.BigDecimal; /** * 浮点数精准算法 */
public class BigDecimalArithUtil { private static final int DIV_SCALE =
10;//除法精度(除不尽时保留10为小数) /** 小数精确加法 */ public
static double add(double d1,double d2) { BigDecimal bd1 =
BigDecimal.valueOf(d1); BigDecimal bd2 = BigDecimal.valueOf(d2); return
bd1.add(bd2).doubleValue(); } /** 小数精确减法 */ public static
double sub(double d1,double d2) { BigDecimal bd1 =
BigDecimal.valueOf(d1); BigDecimal bd2 = BigDecimal.valueOf(d2); return
bd1.subtract(bd2).doubleValue(); } /** 小数精确乘法 */ public static
double mul(double d1,double d2) { BigDecimal bd1 =
BigDecimal.valueOf(d1); BigDecimal bd2 = BigDecimal.valueOf(d2); return
bd1.multiply(bd2).doubleValue(); } /** 小数精确除法 */ public static
double div(double d1,double d2) { BigDecimal bd1 =
BigDecimal.valueOf(d1); BigDecimal bd2 = BigDecimal.valueOf(d2); /* *
当除不尽时,以四舍五入的方式(关于除不尽后的值的处理方式有很多种)保留小数点后10位小数
*/ return bd1.divide(bd2, DIV_SCALE,
BigDecimal.ROUND_HALF_UP).doubleValue(); } public static void
main(String[] args) { //测试加法
System.out.println(“0.05+0.01=”+BigDecimalArithUtil.add(0.05,0.01));
System.out.println(“0.05+0.01=”+(0.05+0.01)); //测试减法
System.out.println(“1.0-0.42=”+BigDecimalArithUtil.sub(1.0,0.42));
System.out.println(“1.0-0.42=”+(1.0-0.42)); //测试乘法
System.out.println(“4.015*100=”+BigDecimalArithUtil.mul(4.015,100));
System.out.println(“4.015*100=”+(4.015*100)); //测试除法
System.out.println(“123.3/100=”+BigDecimalArithUtil.div(123.3,100));
System.out.println(“123.3/100=”+(123.3/100)); } } View Code

 

3、注意点

  • 上边的程序我用的是BigDecimal.valueOf(double x)来将double型的x封装成BigDecimal,查看源码如下:
    图片 2
    /** * 注意:这通常是将double和float转换为一个BigDecimal的最好方式
    */ public static BigDecimal valueOf(double val) { return new
    BigDecimal(Double.toString(val)); } View Code

    在这里直接先将double转换为了String,然后使用如下构造方法再将String转换为BigDecimal

     public BigDecimal(String val)
    

    这是最好的做法。如果使用的是直接将double或float转换为BigDecimal的方式,也就是说使用的如下构造器,那么将可能得不到精确结果。(看注释)

    图片 3
    /** * 注意:The results of this constructor can be somewhat
    unpredictable. * 该构造器的结果有时是不准确的 */ public
    BigDecimal(double val) View
    Code

  • 在实际使用中还可能使用int和long来进行浮点数的精确计算(具体做法:将浮点数先乘以相应的倍数转化为int(<=9位十进制数)或long(<=18位十进制数),计算之后再除以之前的倍数,得出结果),而且该种方式的性能会更高,具体的int、long和BigDecimal各自的使用看《Effective
    Java(第二版)》第48条。

  • 需要指出的是,在实际开发中,BigDecimal的性能差的问题基本可以忽略,是浮点数精确计算的首选,而且根据上一条来看,如果将浮点数转化后的整数大于18位的话,也必须用BigDecimal

Java浮点数精确计算,第二章java浮点数
1、实际意义 在实际开发中,如果需要进行float或double的精确计算(尤其是
财务计算 ),直接…

本文来自CSDN博客,转载请标明出处:

Java浮点数float和double精确计算的精度误差问题总结

1、float整数计算误差

案例:会员积分字段采用float类型,导致计算会员积分时,7位整数的数据计算结果出现误差。

原因:超出float精度范围,无法精确计算。

float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。

float:2^23 =
8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;

double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。

难道只是位数多大的问题,字段类型换成double就可以解决吗?对于本案例是这样,因为都是整数计算,但如果有小数位,就不一定了,见下面案例。

2、double小数转bigdecimal后四舍五入计算有误差

案例:

doubleg=12.35;

BigDecimal bigG=newBigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP);
//期望得到12.4

System.out.println(“test G:”+bigG.doubleValue());

test G:12.3

原因:

定义double g= 12.35;
 而在计算机中二进制表示可能这是样:定义了一个g=12.34444444444444449,

new BigDecimal(g)   g还是12.34444444444444449

new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); 得到12.3

正确的定义方式是使用字符串构造函数:

new BigDecimal(“12.35”).setScale(1, BigDecimal.ROUND_HALF_UP)

3、float和double做四则运算误差

案例:

public class Test{

public static void main(String args[]){

System.out.println(0.05+0.01);

System.out.println(1.0-0.42);

System.out.println(4.015*100);

System.out.println(123.3/100);

}

}

结果:

0.060000000000000005

0.5800000000000001

401.49999999999994

1.2329999999999999

原因:

那么为什么会出现精度丢失呢?在查阅了一些资料以后,我稍微有了一些头绪,下面是本人的愚见,仅供参考。

首先得从计算机本身去讨论这个问题。我们知道,计算机并不能识别除了二进制数据以外的任何数据。无论我们使用何种编程语言,在何种编译环境下工作,都要先把源程序翻译成二进制的机器码后才能被计算机识别。以上面提到的情况为例,我们源程序里的2.4是十进制的,计算机不能直接识别,要先编译成二进制。但问
题来了,2.4的二进制表示并非是精确的2.4,反而最为接近的二进制表示是2.3999999999999999。原因在于浮点数由两部分组成:指数和尾数,这点如果知道怎样进行浮点数的二进制与十进制转换,应该是不难理解的。如果在这个转换的过程中,浮点数参与了计算,那么转换的过程就会变得不可预
知,并且变得不可逆。我们有理由相信,就是在这个过程中,发生了精度的丢失。而至于为什么有些浮点计算会得到准确的结果,应该也是碰巧那个计算的二进制与
十进制之间能够准确转换。而当输出单个浮点型数据的时候,可以正确输出,如

double d = 2.4;

System.out.println(d);

输出的是2.4,而不是2.3999999999999999。也就是说,不进行浮点计算的时候,在十进制里浮点数能正确显示。这更印证了我以上的想法,即如果浮点数参与了计算,那么浮点数二进制与十进制间的转换过程就会变得不可预知,并且变得不可逆。

事实上,浮点数并不适合用于精确计算,而适合进行科学计算。这里有一个小知识:既然float和double型用来表示带有小数点的数,那为什么我们不称
它们为“小数”或者“实数”,要叫浮点数呢?因为这些数都以科学计数法的形式存储。当一个数如50.534,转换成科学计数法的形式为5.053e1,它
的小数点移动到了一个新的位置(即浮动了)。可见,浮点数本来就是用于科学计算的,用来进行精确计算实在太不合适了。

4、bigdecimal构造函数使用不当带来异常

案例:

BigDecimal其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的String表示作为输入。要小心使用BigDecimal(double)构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或String的构造函数。

如果使用BigDecimal(double)构造函数不恰当,在传递给
JDBCsetBigDecimal()方法时,会造成似乎很奇怪的 JDBC
驱动程序中的异常。例如,考虑以下 JDBC
代码,该代码希望将数字0.01存储到小数字段:

PreparedStatement ps =

connection.prepareStatement(“INSERT INTO Foo SET name=?, value=?”);

ps.setString(1, “penny”);

ps.setBigDecimal(2, new BigDecimal(0.01));

ps.executeUpdate();

在执行这段似乎无害的代码时会抛出一些令人迷惑不解的异常(这取决于具体的
JDBC 驱动程序),因为0.01的双精度近似值会导致大的换算值,这可能会使 JDBC
驱动程序或数据库感到迷惑。JDBC
驱动程序会产生异常,但可能不会说明代码实际上错在哪里,除非意识到二进制浮点数的局限性。相反,使用BigDecimal(“0.01”)或BigDecimal(1,

2)构造BigDecimal来避免这类问题,因为这两种方法都可以精确地表示小数。

5、解决浮点数精确计算有误差的方法

在《Effective  
Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。使用BigDecimal并且一定要用String来够造。

BigDecimal用哪个构造函数?

BigDecimal(double val)

BigDecimal(String val)

上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现参数是double的构造方法的详细说明中有这么一段:

Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.

The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(“.1”) is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.

原来我们如果需要精确计算,非要用String来够造BigDecimal不可!

6、定点数和浮点数的区别

在计算机系统的发展过程中,曾经提出过多种方法表达实数。典型的比如相对于浮点数的定点数(Fixed
Point
Number)。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如
99.00 或者 00.99
可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL
中的 NUMBER
数据类型就是利用定点数来定义的。还有一种提议的表达方式为有理数表达方式,即用两个整数的比值来表达实数。

定点数表达法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。最终,绝大多数现代的计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa
),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如
123.45 用十进制科学计数法可以表达为 1.2345 × 102 ,其中 1.2345
为尾数,10 为基数,2
为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。

在MySQL中使用浮点数类型和定点数类型来表示小数。浮点数类型包括单精度浮点数(FLOAT型)和双精度浮点数(DOUBLE型)。定点数类型就是DECIMAL型。MySQL的浮点数类型和定点数类型如下表所示:

类型名称字节数负数的取值范围非负数的取值范围

FLOAT4-3.402823466E+38~

-1.175494351E-380和1.175494351E-38~

3.402823466E+38

DOUBLE8-1.7976931348623157E+308~

-2.2250738585072014E-3080和2.2250738585072014E-308~

1.7976931348623157E+308

DECIMAL(M,D)或DEC(M,D)M+2同DOUBLE型同DOUBLE型

从上表中可以看出,DECIMAL型的取值范围与DOUBLE相同。但是,DECIMAL的有效取值范围由M和D决定,而且DECIMAL型的字节数是M+2,也就是说,定点数的存储空间是根据其精度决定的。

7、bigdecimal比等方法

如浮点类型一样,BigDecimal也有一些令人奇怪的行为。尤其在使用equals()方法来检测数值之间是否相等时要小心。equals()方法认为,两个表示同一个数但换算值不同(例如,100.00和100.000)的BigDecimal值是不相等的。然而,compareTo()方法会认为这两个数是相等的,所以在从数值上比较两个BigDecimal值时,应该使用compareTo()而不是equals()。

另外还有一些情形,任意精度的小数运算仍不能表示精确结果。例如,1除以9会产生无限循环的小数.111111…。出于这个原因,在进行除法运算时,BigDecimal可以让您显式地控制舍入。movePointLeft()方法支持
10 的幂次方的精确除法。

与零比较:

int r=big_decimal.compareTo(BigDecimal.Zero); //和0,Zero比较

if(r==0) //等于

if(r==1) //大于

if(r==-1) //小于

8、简化bigdecimal计算的小工具类

如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造成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)

importjava.math.BigDecimal;

/**

* 进行BigDecimal对象的加减乘除,四舍五入等运算的工具类

* @author ameyume

*

*/

publicclassArith {

/**

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

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

*/

//默认除法运算精度

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();

}

/**

* 提供精确的类型转换(Float)

* @param v 需要被转换的数字

* @return 返回转换结果

*/

publicstaticfloatconvertsToFloat(doublev){

BigDecimal b =newBigDecimal(v);

returnb.floatValue();

}

/**

* 提供精确的类型转换(Int)不进行四舍五入

* @param v 需要被转换的数字

* @return 返回转换结果

*/

publicstaticintconvertsToInt(doublev){

BigDecimal b =newBigDecimal(v);

returnb.intValue();

}

/**

* 提供精确的类型转换(Long)

* @param v 需要被转换的数字

* @return 返回转换结果

*/

publicstaticlongconvertsToLong(doublev){

BigDecimal b =newBigDecimal(v);

returnb.longValue();

}

/**

* 返回两个数中大的一个值

* @param v1 需要被对比的第一个数

* @param v2 需要被对比的第二个数

* @return 返回两个数中大的一个值

*/

publicstaticdoublereturnMax(doublev1,doublev2){

BigDecimal b1 =newBigDecimal(v1);

BigDecimal b2 =newBigDecimal(v2);

returnb1.max(b2).doubleValue();

}

/**

* 返回两个数中小的一个值

* @param v1 需要被对比的第一个数

* @param v2 需要被对比的第二个数

* @return 返回两个数中小的一个值

*/

publicstaticdoublereturnMin(doublev1,doublev2){

BigDecimal b1 =newBigDecimal(v1);

BigDecimal b2 =newBigDecimal(v2);

returnb1.min(b2).doubleValue();

}

/**

* 精确对比两个数字

* @param v1 需要被对比的第一个数

* @param v2 需要被对比的第二个数

* @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1

*/

publicstaticintcompareTo(doublev1,doublev2){

BigDecimal b1 =newBigDecimal(v1);

BigDecimal b2 =newBigDecimal(v2);

returnb1.compareTo(b2);

}

}

参考: