`

精确运算避免使用float和double

 
阅读更多

1. 概述

float和double类型的主要设计目的是为了科学计算和工程计算。它们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。float和double类型对于货币计算尤为不合适,因为要让一个float或者double精确地表示0.1(或者10的任何负数次方值)是不可能的,比如System.out.println(2.0-1.1)将会打印0.899999999999999,而不是你所希望的0.9,这种舍入错误产生的原因是浮点数实际上是用二进制系统实现的,而分数1/10在二进制系统中没有精确的表示,其道理就如同在十进制系统中无法精确表示1/3一样;再比如0.5在二进制系统中有精确表示,而0.55在二进制系统中没有精确表示。

许多数都是无法在有限的n内完全精确的表示出来的,我们只能利用更大的n值来更精确的表示这个数,但更大的n也只是更加逼近精确值而无法得到精确值。例如

  1. public static void main(String[] args) {
  2.     System.out.println("100.5 - 100                 = " + (100.5 - 100));
  3.     System.out.println("100.5F - 100F               = " + (100.5F - 100F));
  4.     System.out.println("100.55 - 100.00             = " + (100.55 - 100.00));
  5.     System.out.println("100.55F - 100.00F           = " + (100.55F - 100.00F));
  6.     System.out.println("new Double(100.55 - 100.00) = " + new Double(100.55 - 100.00));
  7.     System.out.println("new Float(100.55 - 100.00)  = " + new Float(100.55 - 100.00));
  8.     System.out.println("(double)(100.55 - 100.00)   = " + (double)(100.55 - 100.00));
  9.     System.out.println("(float)(100.55 - 100.00)    = " + (float)(100.55 - 100.00));
  10.     
  11.     System.out.println("2.0 - 1.1               = " + (2.0 - 1.1));
  12.     System.out.println("2.0F - 1.1F             = " + (2.0F - 1.1F));
  13.     System.out.println("new Double(2.0 - 1.1)   = " + new Double(2.0 - 1.1));
  14.     System.out.println("new Float(2.0 - 1.1)    = " + new Float(2.0 - 1.1));
  15.     System.out.println("(double)(2.0 - 1.1)     = " + (double)(2.0 - 1.1));
  16.     System.out.println("(float)(2.0 - 1.1)      = " + (float)(2.0 - 1.1));
  17.     System.out.println("1.0 - 0.9               = " + (1.0 - 0.9));
  18.     System.out.println("1.0F - 0.9F             = " + (1.0F - 0.9F));
  19.     System.out.println("new Double(1.0 - 0.9)   = " + new Double(1.0 - 0.9));
  20.     System.out.println("new Float(1.0 - 0.9)    = " + new Float(1.0 - 0.9));
  21.     System.out.println("(double)(1.0 - 0.9)     = " + (double)(1.0 - 0.9));
  22.     System.out.println("(float)(1.0 - 0.9)      = " + (float)(1.0 - 0.9));
  23.     /*
  24.      * 输出结果:
  25.      * 100.5 - 100                 = 0.5
  26.      * 100.5F - 100F               = 0.5
  27.      * 100.55 - 100.00             = 0.5499999999999972
  28.      * 100.55F - 100.00F           = 0.55000305
  29.      * new Double(100.55 - 100.00) = 0.5499999999999972
  30.      * new Float(100.55 - 100.00)  = 0.55 (将一个Double转换为Float自然会有精度丢失, 所以舍入为0.55)
  31.      * (double)(100.55 - 100.00)   = 0.5499999999999972
  32.      * (float)(100.55 - 100.00)    = 0.55 (将一个double转换为float自然会有精度丢失, 所以舍入为0.55)
  33.      * 
  34.      * 2.0 - 1.1                = 0.8999999999999999
  35.      * 2.0F - 1.1F              = 0.9 (这里用Float可以得到正确值, 我不知道原因, 你去问cpu吧. 只要记住这是一个巧合就可以了)
  36.      * new Double(2.0 - 1.1)    = 0.8999999999999999
  37.      * new Float(2.0 - 1.1)     = 0.9 (将一个Double转换为Float自然会有精度丢失, 所以舍入为0.9)
  38.      * (double)(2.0 - 1.1)      = 0.8999999999999999
  39.      * (float)(2.0 - 1.1)       = 0.9 (将一个double转换为float自然会有精度丢失, 所以舍入为0.9)
  40.      * 
  41.      * 1.0 - 0.9                = 0.09999999999999998
  42.      * 1.0F - 0.9F              = 0.100000024
  43.      * new Double(1.0 - 0.9)    = 0.09999999999999998
  44.      * new Float(1.0 - 0.9)     = 0.1 (将一个Double转换为Float自然会有精度丢失, 所以舍入为0.1)
  45.      * (double)(1.0 - 0.9)      = 0.09999999999999998
  46.      * (float)(1.0 - 0.9)       = 0.1 (将一个double转换为float自然会有精度丢失, 所以舍入为0.1)
  47.      * */
  48. }

2. 为什么浮点数会丢失精度(浮点数没办法用十进制来精确表示)

(1)十进制与二进制转换

要归咎于CPU表示浮点数的方法:2.4的二进制表示并非就是精确的2.4,而是最为接近2.3999999999999999;原因在于浮点数由两部分组成:指数和尾数,浮点数的值实际上是由cpu的某个数学公式计算得到的,所以您所遇到的精度损失会在任何操作系统和编程环境中遇到。

(2)类型失配

您可能混合了浮点数和双精度浮点数类型。请确定您在进行数学运算的时候所有的数据类型全部相同。注意:float类型的变量只有7位的精度,而double类型的变量有15位的精度。

3. 解决方法

(1)java.lang.Math中的public static int round(float a)和public static long round(double a)不能解决问题; java.lang.Object --> java.text.Format --> java.text.NumberFormat --> java.text.DecimalFormat 也不能解决问题
(2)使用BigDecimal、int或long(将小数转换为整数再进行计算,例如:将以美元为单位的货币计算改为以美分为单位的货币计算)。java.lang.Object --> java.lang.Number -- Direct Known Subclasses: BigDecimal, Byte, Double, Float, Integer, Long, Short

BigDecimal常用构造方法:

public BigDecimal(double val) --> Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value.

public BigDecimal(String val) --> Translates the string representation of a BigDecimal into a BigDecimal

通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段。

The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.

The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.

When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.

原来我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》一书中的例子也是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个小小的失误吧。

使用BigDecimal加法运算步骤:(1)将两个浮点数转为String,然后构造成BigDecimal;(2)在其中一个上调用add()方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。这样比较繁琐,我们可以自己写一个工具类Arith来简化操作:

  1. package edu.hust.test;
  2. import java.math.BigDecimal;
  3. /**
  4.  * 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精确的浮点数运算,包括加减乘除和四舍五入。
  5.  * 所以提供以下静态方法:
  6.  * public static double add(double v1,double v2)
  7.  * public static double sub(double v1,double v2)
  8.  * public static double mul(double v1,double v2)
  9.  * public static double div(double v1,double v2)
  10.  * public static double div(double v1,double v2,int scale)
  11.  * public static double round(double v,int scale)
  12.  * 
  13.  */
  14. public class Arith {
  15.     //默认除法运算精度
  16.     private static final int DEF_DIV_SCALE = 10;
  17.     //这个类不能实例化
  18.     private Arith(){}
  19.     /**
  20.      * 提供精确的加法运算。
  21.      * @param v1 被加数
  22.      * @param v2 加数
  23.      * @return 两个参数的和
  24.      */
  25.     public static double add(double v1,double v2){
  26.         BigDecimal b1 = new BigDecimal(Double.toString(v1));
  27.         BigDecimal b2 = new BigDecimal(Double.toString(v2));
  28.         return b1.add(b2).doubleValue();
  29.     }
  30.     /**
  31.      * 提供精确的减法运算。
  32.      * @param v1 被减数
  33.      * @param v2 减数
  34.      * @return 两个参数的差
  35.      */
  36.     public static double sub(double v1,double v2){
  37.         BigDecimal b1 = new BigDecimal(Double.toString(v1));
  38.         BigDecimal b2 = new BigDecimal(Double.toString(v2));
  39.         return b1.subtract(b2).doubleValue();
  40.     } 
  41.     /**
  42.      * 提供精确的乘法运算。
  43.      * @param v1 被乘数
  44.      * @param v2 乘数
  45.      * @return 两个参数的积
  46.      */
  47.     public static double mul(double v1,double v2){
  48.         BigDecimal b1 = new BigDecimal(Double.toString(v1));
  49.         BigDecimal b2 = new BigDecimal(Double.toString(v2));
  50.         return b1.multiply(b2).doubleValue();
  51.     }
  52.     /**
  53.      * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到小数点以后10位,以后的数字四舍五入。
  54.      * @param v1 被除数
  55.      * @param v2 除数
  56.      * @return 两个参数的商
  57.      */
  58.     public static double div(double v1,double v2){
  59.         return div(v1,v2,DEF_DIV_SCALE);
  60.     }
  61.     /**
  62.      * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
  63.      * @param v1 被除数
  64.      * @param v2 除数
  65.      * @param scale 表示表示需要精确到小数点以后几位。
  66.      * @return 两个参数的商
  67.      */
  68.     public static double div(double v1,double v2,int scale){
  69.         if(scale<0){
  70.             throw new IllegalArgumentException("The scale must be a positive integer or zero");
  71.         }
  72.         BigDecimal b1 = new BigDecimal(Double.toString(v1));
  73.         BigDecimal b2 = new BigDecimal(Double.toString(v2));
  74.         return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
  75.     }
  76.     /**
  77.      * 提供精确的小数位四舍五入处理。
  78.      * @param v 需要四舍五入的数字
  79.      * @param scale 小数点后保留几位
  80.      * @return 四舍五入后的结果
  81.      */
  82.     public static double round(double v,int scale){
  83.         if(scale<0){
  84.             throw new IllegalArgumentException("The scale must be a positive integer or zero");
  85.         }
  86.         BigDecimal b = new BigDecimal(Double.toString(v));
  87.         BigDecimal one = new BigDecimal("1");
  88.         return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
  89.     }
  90. }

使用BigDecimal缺点:要创建对象并占用内存;其floatValue()方法有时会精度丢失而造成计算错误(所以一般小数计算尽量使用double)。

 

最后补充一点:也正因为此,浮点数不能用于循环判断,否则容易造成死循环。

分享到:
评论

相关推荐

    基于C++浮点数(float、double)类型数据比较与转换的详解

    所以浮点数在运算过程中通常伴随着因为无法精确表示而进行的近似或舍入。但是这种设计的好处是可以在固定的长度上存储更大范围的数。1、将字符串转换为float、double过程存在精度损失,只是float、double各自损失的...

    Java 精确计算-double-float-String

    NULL 博文链接:https://thinktothings.iteye.com/blog/801301

    BigDecimal 加减乘除运算

    float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的...

    double.js:javascript中的double-double运算。 具有31个精确十进制数字的浮点扩展

    具有31个准确的十进制数字(106位)的浮点展开,也称为双精度双精度算术或模拟float128。 该库对于扩展精度的快速计算很有用。 例如,在轨道力学,计算几何和数值不稳定算法中,例如执行三角剖分,多边形修剪,求逆...

    C语言基础知识

    double 和 float 的区别是double精度高,有效数字16位,float精度7位。但double消耗内存是float的两倍,double的运算速度比float慢得多 scanf函数,与printf函数一样,都被定义在头文件stdio.h里,因此在使用scanf...

    BigDecimalUtils

    float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的...

    Java与MySQL中小数保存问题解析.pptx.pptx

    在Java和MySQL中,小数的精度可能会受到限制,如float类型的小数只能精确到6-7位,double类型也只能精确到15-16位。 浮点数运算问题 在进行浮点数运算时,可能会出现精度丢失的问题,例如0.1+0.2的结果并不等于0.3...

    iOS NSDecimalNumber解决数值计算不精确问题,一句话解决精确计算,精确比较

    iOS 解决数值计算(floatValue,doubleValue)不精确问题,一句话解决精确计算,精确比较

    java代码-1·byte short int 在计算是会自动转化为int 2.float double 为近似值,byte short int 转化时可能会精确丢失 3.把大类型转化小的类型时可能会丢失

    java代码-1·byte short int 在计算是会自动转化为int 2.float double 为近似值,byte short int 转化时可能会精确丢失 3.把大类型转化小的类型时可能会丢失

    分数的加减乘除运算Rational Numbers

    例如1/3=0.0000…,它不能使用数据类型double或float的浮点格式精确表示出来,为了得到准确结果,必须使用有理数。 – Java提供了整数和浮点数的数据类型,但是没有提供有理数的类型。 – 由于有理数与整数、...

    Java BigDecimal详解_动力节点Java学院整理

    借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供...

    Java中实现浮点数的精确计算

    Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。

    IEEE-754双精度数值至字符转换

    IEEE二进制浮点数算术标准(IEEE 754)...例如,IEEE 754问世之前就有的C语言,现在有包括IEEE算术,但不算作强制要求(C语言的float通常是指IEEE单精确度,而double是指双精确度)。 本程序就是IEEE-754双精度转换程序

    mysql基础只是总结

    //float和double在desc中不会显示精确度 //select 中通过 float查询的前提是MD都有或者是double double //25-53代表double 但是不可以写精确度 &gt;15位用科学计数法 字符串类型 char varchar text blob enum...

    labview IEEE-754单精度数值至字符转换

    例如,IEEE 754问世之前就有的C语言,现在有包括IEEE算术,但不算作强制要求(C语言的float通常是指IEEE单精确度,而double是指双精确度)。 该标准的全称为IEEE二进制浮点数算术标准(ANSI/IEEE Std 754-1985),又...

    你必须知道的495个C语言问题

    如果定义明确的溢出特征很重要而负值无关紧要,或者希望在操作二进制位和字节时避免符号扩展的问题,请使用对应的unsigned类型。(但是,在表达式中混用有符号和无符号值的时候,要特别注意。参见问题3.21。)尽管...

    一维动态数组实现的矩阵类

    提供了多种初始化方式,int[]、float[]、double[]均可构造初始化,或则先构造出CVector再由CVector初始化。 3、定义了一个最大允许误差#define permit_eof (1.0e-13),判断相等使用宏 #define EQUAL(a,b) ( ((b) ...

    Java-关于基本数据类型中浮点数计算产生的精度问题

    BigDecimal类是一个大小数操作类,可以用来对超过16位有效位的数据进行精确的运算,在这里我们使用BigDecimal类来解决浮点数计算产生的精度丢失问题。 精度问题 在这里我们讨论一个问题:3 – 2.7 == 0.3 的值是什么...

    整理后java开发全套达内学习笔记(含练习)

    注:float 和 double 的小数部分不可能精确,只能近似。 比较小数时,用 double i=0.01; if ( i - 0.01 ) ... 不能直接 if (i==0.01)... 默认,整数是int类型,小数是double类型 long类型值,需跟L或l在数据后;...

    实例讲解Python中浮点型的基本内容

    float(浮点型)是Python基本数据类型中的一种,Python的浮点数类似数学中的小数和C语言中的double类型; 2.浮点型的运算 浮点数和整数在计算机内部存储的方式是不同的,整数运算永远是精确的,然而浮点数的运算则...

Global site tag (gtag.js) - Google Analytics