一、float整数计算偏差html
案例:会员积分字段采用float类型,致使计算会员积分时,7位整数的数据计算结果出现偏差。java
缘由:超出float精度范围,没法精确计算。mysql
float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,因为它是不变的,故不能对精度形成影响。sql
float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。数据库
难道只是位数多大的问题,字段类型换成double就能够解决吗?对于本案例是这样,由于都是整数计算,但若是有小数位,就不必定了,见下面案例。编程
二、double小数转bigdecimal后四舍五入计算有偏差app
案例:less
double g= 12.35;
编程语言
BigDecimal bigG=new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); //指望获得12.4
ide
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)
三、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,它 的小数点移动到了一个新的位置(即浮动了)。可见,浮点数原本就是用于科学计算的,用来进行精确计算实在太不合适了。
四、bigdecimal构造函数使用不当带来异常
案例:
BigDecimal
其中一个构造函数以双精度浮点数做为输入,另外一个以整数和换算因子做为输入,还有一个以小数的 String
表示做为输入。要当心使用 BigDecimal(double)
构造函数,由于若是不了解它,会在计算过程当中产生舍入偏差。请使用基于整数或 String
的构造函数。
若是使用 BigDecimal(double)
构造函数不恰当,在传递给 JDBC setBigDecimal()
方法时,会形成彷佛很奇怪的 JDBC 驱动程序中的异常。例如,考虑如下 JDBC 代码,该代码但愿将数字 0.01
存储到小数字段:
<span style="font-family: 'Microsoft YaHei';">PreparedStatement ps = connection.prepareStatement("INSERT INTO Foo SET name=?, value=?"); ps.setString(1, "penny"); ps.setBigDecimal(2, new BigDecimal(0.01)); ps.executeUpdate();</span> |
在执行这段彷佛无害的代码时会抛出一些使人疑惑不解的异常(这取决于具体的 JDBC 驱动程序),由于 0.01
的双精度近似值会致使大的换算值,这可能会使 JDBC 驱动程序或数据库感到迷惑。JDBC 驱动程序会产生异常,但可能不会说明代码实际上错在哪里,除非意识到二进制浮点数的局限性。相反,使用 BigDecimal("0.01")
或 BigDecimal(1, 2)
构造 BigDecimal
来避免这类问题,由于这两种方法均可以精确地表示小数。
在《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不可!
六、定点数和浮点数的区别
在计算机系统的发展过程当中,曾经提出过多种方法表达实数。典型的好比相对于浮点数的定点数(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的浮点数类型和定点数类型以下表所示:
类型名称 | 字节数 | 负数的取值范围 | 非负数的取值范围 |
---|---|---|---|
FLOAT | 4 | -3.402823466E+38~ -1.175494351E-38 |
0和1.175494351E-38~ 3.402823466E+38 |
DOUBLE | 8 | -1.7976931348623157E+308~ -2.2250738585072014E-308 |
0和2.2250738585072014E-308~ 1.7976931348623157E+308 |
DECIMAL(M,D)或DEC(M,D) | M+2 | 同DOUBLE型 | 同DOUBLE型 |
从上表中能够看出,DECIMAL型的取值范围与DOUBLE相同。可是,DECIMAL的有效取值范围由M和D决定,并且DECIMAL型的字节数是M+2,也就是说,定点数的存储空间是根据其精度决定的。
七、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) //小于
八、简化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)
精确运算:
BigDecimal payroll = new BigDecimal("2.232");
BigDecimal finance = new BigDecimal("3.453");
if(payroll.compareTo(new BigDecimal("3500.00"))>0){
System.err.println(payroll.compareTo(new BigDecimal("3500"))>0);
}else{
System.err.println(payroll.compareTo(new BigDecimal("3500"))>0);
}
BigDecimal balance = new BigDecimal("4.56");
BigDecimal total = payroll.add(finance);
String weString = payroll.add(finance).add(balance).toString();
// payroll.divide(finance)
// payroll.multiply(finance)
// payroll.subtract(finance)
System.out.println("down="+total.setScale(2,BigDecimal.ROUND_HALF_DOWN)+"\tup="+total.setScale
(2,BigDecimal.ROUND_HALF_UP));
参考:
http://justjavac.iteye.com/blog/1073775
http://www.iteye.com/problems/51604
http://blog.163.com/howl_prowler/blog/static/2661971520114553211964/
http://www.cnblogs.com/wingsless/p/3426108.html
http://zhidao.baidu.com/link?url=2L4pkHgVCXlwEeDM0GRHY2gYUwR9d2JC3knqxvHwdyrrdz_LwK92gVAaIy3hhKEQYdUwNjMLe_RJO3cl8sJvbcAnFK-_rMS4Oy_viystUEe