java 处理高精度计算问题

来源:互联网 发布:云南白药 知乎 编辑:程序博客网 时间:2024/05/17 06:28

一、java  double float 为啥会丢失精度


由于对float或double 的使用不当,可能会出现精度丢失的问题。问题大概情况可以通过如下代码理解:

</pre><pre name="code" class="java">public class FloatDoubleTest {public static void main(String[] args) {float f = 20014999;double d = f;double d2 = 20014999;System.out.println("f=" + f);System.out.println("d=" + d);System.out.println("d2=" + d2);}}

得到的结果如下:

f=2.0015E7

d=2.0015E7

d2=2.0014999E7

从输出结果可以看出double 可以正确的表示20014999 ,而float 没有办法表示20014999 ,得到的只是一个近似值。这样的结果很让人讶异。20014999 这么小的数字在float下没办法表示。于是带着这个问 题,做了一次关于float和double学习,做个简单分享,希望有助于大家对java 浮 点数的理解。

 

关于 java  float  double

Java 语言支持两种基本的浮点类型: float 和 double java 的浮点类型都依据 IEEE 754 标准。IEEE 754 定义了32 位和 64 位双精度两种浮点二进制小数标准。

IEEE 754 用科学记数法以底数为 的小数来表示浮点数。32 位浮点数用 位表示数字的符号,用 位来表示指数,用 23 位来表示尾数,即小数部分。作为有符号整数的指数可以有正负之分。小数部分用二进制(底数 2 )小数来表示。对于64 位双精度浮点数,用 位表示数字的符号,用 11 位表示指数,52 位表示尾数。如下两个图来表示:

float(32位):

float

double(64位):

double

都是分为三个部分:

(1) 一 个单独的符号位s 直接编码符号s 。

(2)k 位 的幂指数E ,移 码表示 

(3)n 位 的小数,原码表示 

那么 20014999 为什么用 float 没有办法正确表示?

结合float和double的表示方法,通过分析 20014999 的二进制表示就可以知道答案了。

以下程序可以得出 20014999  double  float 下的二进制表示方式。

[java] view plaincopy
  1. public class FloatDoubleTest3 {  
  2. public static void main(String[] args) {  
  3. double d = 8;  
  4. long l = Double.doubleToLongBits(d);  
  5. System.out.println(Long.toBinaryString(l));  
  6. float f = 8;  
  7. int i = Float.floatToIntBits(f);  
  8. System.out.println(Integer.toBinaryString(i));  
  9. }  
  10. }  

输出结果如下:

Double:100000101110011000101100111100101110000000000000000000000000000

Float:1001011100110001011001111001100

对于输出结果分析如下。对于都不 double 的二进制左边补上符号位 0 刚好可以得到 64 位的二进制数。根据double的表 示法,分为符号数、幂指数和尾数三个部分如下:

0 10000010111 0011000101100111100101110000000000000000000000000000

对于 float 左边补上符 号位 0 刚好可以得到 32 位的二进制数。 根据float的表示法, 也分为 符号数、幂指数和尾数三个部分如下 

0 10010111 00110001011001111001100

绿色部分是符号位,红色部分是幂指数,蓝色部分是尾数。

对比可以得出:符号位都是 0 ,幂指数为移码表示,两者刚好也相等。唯一不同的是尾数。

 double 的尾数 为: 001100010110011110010111 0000000000000000000000000000 省略后面的零,至少需要24位才能正确表示

而在 float 下面尾数 为: 00110001011001111001100 ,共 23 位。

为什么会这样?原因很明显,因为 float尾数 最多只能表示 23 位,所以 24 位的 001100010110011110010111  float 下面经过四舍五入变成了 23 位的 00110001011001111001100 。所以 20014999  float 下面变成了 20015000 
也就是说
 
20014999 虽然是在float的表示范围之内,但  IEEE 754  float 表示法精度长度没有办法表示出 20014999 ,而只能通过四舍五入得到一个近似值。

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是 因为数的大小,而是因为数的精度。因此,产生的结果接近但不等于想要的结果。尤其在使用 float  double 作精确运 算的时候要特别小心。
可以考虑采用一些替代方案来实现。如通过
 
String 结合 BigDecimal 或 者通过使用 long 类型来转换。

下面我们谈下关于怎么解决高精度计算问题


Double.valueOf(String) and Float.valueOf(String)都会丢失精度。
为了解决这个问题,需要用到BigDecimal类。

使用的BigDecimal类的时候需要注意的地方:
1. 在实例化BigDecimal 的时候用 new BigDecimal(String)  代替new BigDecimal(double) ,new BigDecimal(float)在《Effective Java》书中有提到 
2. 比较两个数的时候用compareTo  小于返回-1 , 等于返回0 , 大于返回1
import java.math.BigDecimal;public class ArithmeticUtil {     /*      * 小数精确的位数      */     private static final int DEF_DIV_SCALE = 10;     /**      * 提供精确的加法运算。      *      * @param v1      *            被加数      * @param v2      *            加数      * @return 两个参数的和      */     public static double add(double v1, double v2) {         BigDecimal b1 = new BigDecimal(Double.toString(v1));         BigDecimal b2 = new BigDecimal(Double.toString(v2));         return b1.add(b2).doubleValue();     }        /**      * 提供精确的加法运算。      *      * @param v1      *            被加数      * @param v2      *            加数      * @return 两个参数的和      */     public static BigDecimal add(String v1, String v2) {         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.add(b2);     }        /**      * 提供精确的加法运算。 String      *      * @param v1      *            被加数      * @param v2      *            加数      * @return 两个参数的和      */     public static String strAdd(String v1, String v2,int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();     }     /**      * 提供精确的减法运算。      *      * @param v1      *            被减数      * @param v2      *            减数      * @return 两个参数的差      */     public static double sub(double v1, double v2) {         BigDecimal b1 = new BigDecimal(Double.toString(v1));         BigDecimal b2 = new BigDecimal(Double.toString(v2));         return b1.subtract(b2).doubleValue();     }        /**      * 提供精确的减法运算。      *      * @param v1      *            被减数      * @param v2      *            减数      * @return 两个参数的差      */     public static BigDecimal sub(String v1, String v2) {         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.subtract(b2);     }        /**      * 对一个数字取精度      * @param v      * @param scale      * @return      */     public static BigDecimal round(String v, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b = new BigDecimal(v);         BigDecimal one = new BigDecimal("1");         return b.divide(one, scale, BigDecimal.ROUND_HALF_UP);     }        /**      * 提供精确的减法运算。String      *      * @param v1      *            被减数      * @param v2      *            减数      * @return 两个参数的差      */     public static String strSub(String v1, String v2,int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();     }     /**      * 提供精确的乘法运算。      *      * @param v1      *            被乘数      * @param v2      *            乘数      * @return 两个参数的积      */     public static double mul(double v1, double v2) {         BigDecimal b1 = new BigDecimal(Double.toString(v1));         BigDecimal b2 = new BigDecimal(Double.toString(v2));         return b1.multiply(b2).doubleValue();     }        /**      * 提供精确的乘法运算。      *      * @param v1      *            被乘数      * @param v2      *            乘数      * @return 两个参数的积      */     public static BigDecimal mul(String v1, String v2) {         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.multiply(b2);     }        /**      * 提供精确的乘法运算。 保留scale 位小数      *      * @param v1      *            被乘数      * @param v2      *            乘数      * @return 两个参数的积      */     public static double mul2(double v1, double v2,int scale) {         BigDecimal b1 = new BigDecimal(Double.toString(v1));         BigDecimal b2 = new BigDecimal(Double.toString(v2));         return  round(b1.multiply(b2).doubleValue(),scale);     }        /**      * 提供精确的乘法运算。 保留scale 位小数 String      *      * @param v1      *            被乘数      * @param v2      *            乘数      * @return 两个参数的积      */     public static String strMul2(String v1, String v2,int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();     }     /**      * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入。      *      * @param v1      *            被除数      * @param v2      *            除数      * @return 两个参数的商      */     public static BigDecimal div(String v1, String v2) {         return div(v1, v2, DEF_DIV_SCALE);     }        /**      * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入。      *      * @param v1      *            被除数      * @param v2      *            除数      * @return 两个参数的商      */     public static double div(double v1, double v2) {         return div(v1, v2, DEF_DIV_SCALE);     }     /**      * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入。      *      * @param v1      *            被除数      * @param v2      *            除数      * @param scale      *            表示需要精确到小数点以后几位。      * @return 两个参数的商      */     public static double div(double v1, double v2, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(Double.toString(v1));         BigDecimal b2 = new BigDecimal(Double.toString(v2));         return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();     }        /**      * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入。      *      * @param v1      *            被除数      * @param v2      *            除数      * @param scale      *            表示需要精确到小数点以后几位。      * @return 两个参数的商      */     public static BigDecimal div(String v1, String v2, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP);     }     /**      * 精确的除法运算。除不尽时,由scale参数指 定精度 四舍五入。string      *      * @param v1      *            被除数      * @param v2      *            除数      * @param scale      *            表示需要精确到小数点以后几位。      * @return 两个参数的商      */     public static String strDiv(String v1, String v2, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();     }        /**      * 精确的除法运算。除不尽时,由scale参数指 定精度 四舍五入。string      *      * @param v1      *            被除数      * @param v2      *            除数      * @param scale      *            表示需要精确到小数点以后几位。      * @return 两个参数的商      */     public static BigDecimal bigDiv(String v1, String v2, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP);     }     /**      * 取余数  string      * @param v1      * @param v2      * @param scale      * @return      */     public static BigDecimal strRemainder(String v1,String v2, int scale){         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP);     }     /**      * 取余数  string      * @param v1      * @param v2      * @param scale      * @return  string      */     public static String strRemainder2Str(String v1,String v2, int scale){         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();     }        /**      * 比较大小 如果v1 大于v2 则 返回true 否则false      * @param v1      * @param v2      * @return      */     public static boolean strcompareTo(String v1,String v2){         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         int bj = b1.compareTo(b2);         boolean res ;         if(bj>0)             res = true;         else             res = false;         return res;     }        /**      * 比较大小 如果v1 大于等于v2 则 返回true 否则false      * @param v1      * @param v2      * @return      */     public static boolean strcompareTo2(String v1,String v2){         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         int bj = b1.compareTo(b2);         boolean res ;         if(bj>=0)             res = true;         else             res = false;         return res;     }        /**      * 比较大小 如果v1 等于v2 则 返回true 否则false      * @param v1      * @param v2      * @return      */     public static boolean strcompareTo3(String v1,String v2){         BigDecimal b1 = new BigDecimal(v1);         BigDecimal b2 = new BigDecimal(v2);         int bj = b1.compareTo(b2);         boolean res ;         if(bj==0)             res = true;         else             res = false;         return res;     }         /**      * 取余数  BigDecimal      * @param v1      * @param v2      * @param scale      * @return      */     public static BigDecimal bigRemainder(BigDecimal v1,BigDecimal v2, int scale){         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);     }         /**      * 提供精确的小数位四舍五入处理。      *      * @param v      *            需要四舍五入的数字      * @param scale      *            小数点后保留几位      * @return 四舍五入后的结果      */     public static double round(double v, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b = new BigDecimal(Double.toString(v));         BigDecimal one = new BigDecimal("1");         return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();     }        /**      * 提供精确的小数位四舍五入处理。string      *      * @param v      *            需要四舍五入的数字      * @param scale      *            小数点后保留几位      * @return 四舍五入后的结果      */     public static String strRound(String v, int scale) {         if (scale < 0) {             throw new IllegalArgumentException(                     "The scale must be a positive integer or zero");         }         BigDecimal b = new BigDecimal(v);         return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();     }       }


0 0
原创粉丝点击