java 精度问题

java中浮点数的计算
今天在数值计算时碰到一个问题.程序如下:
double a = (3.3-2.4)/0.1;
System.out.println(a);
你可能认为结果很简单,不就是9嘛,是事实上,结果为:8.999999998,为什么呢?我翻阅了一些资料,终于找出了原因.
为什么浮点数会丢失精度?
十进制数的二进制表示可能不够精确

浮点数或是双精度浮点数无法精确表示的情况并不少见。浮点数值没办法用十进制来精确表示的原因要归咎于CPU表示浮点数的方法。这样的话您就可能会牺牲一些精度,有些浮点数运算也会引入误差。以上面提到的情况为例,2.4的二进制表示并非就是精确的2.4。反而最为接近的二进制表示是 2.3999999999999999。原因在于浮点数由两部分组成:指数和尾数。浮点数的值实际上是由一个特定的数学公式计算得到的。您所遇到的精度损失会在任何操作系统和编程环境中遇到。
注意: 您可以使用Binary Coded Decimal (BCD)库来保持精度。BCD数字编码方法会把每一个十进制数字位单独编码。
类型失配
您可能混合了浮点数和双精度浮点数类型。请确定您在进行数学运算的时候所有的数据类型全部相同。
注意:float类型的变量只有7位的精度,而double类型的变量有15位的精度。
如何进行浮点数精度计算?            
      Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。现在终于理解为什么要有BCD码了。
这个问题相当严重,如果你有9.999999999999元,你的计算机是不会认为你可以购买10元的商品的。
在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。现在让我们看看如何解决这个问题。

四舍五入
我们的第一个反应是做四舍五入。Math类中的round方法不能设置保留几位小数,我们只能象这样(保留两位):
public double round(double value){
    return Math.round(value*100)/100.0;
}
非常不幸,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,如我们在上面看到的
4.015*100=401.49999999999994
因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算
java.text.DecimalFormat也不能解决这个问题:
System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
输出是4.02

BigDecimal
在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。BigDecimal一共有4个够造方法,我们不关心用BigInteger来够造的那两个,那么还有两个,它们是:
BigDecimal(double val)
          Translates a double into a BigDecimal.
BigDecimal(String val)
          Translates the String repre sentation of a BigDecimal into a BigDecimal.
上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段:
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不可!在《Effective Java》一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个小小的失误吧。

解决方案
现在我们已经可以解决这个问题了,原则是使用BigDecimal并且一定要用String来够造。
但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为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)
附录
源文件Arith.java:
import java.math.BigDecimal;

public class Arith{
    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;
    //这个类不能实例化
    private Arith(){
    }

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

   
    public static double div(double v1,double v2){
        return div(v1,v2,DEF_DIV_SCALE);
    }

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

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

针对double d = 2.4;
System.out.println(d);//输出2.4,却不是2.3999999999999999呢?
翻阅了一些资料,当单个输出double 型值时,可以正确的用十进制显示,具体为什么,俺也似懂非懂,但进行浮点计算,浮点计算是指浮点数参与的运算,這種运算通常伴随着因为无法精确表示而进行的近似或舍入。也许和Double.toString方法的 FloatingDecimal(d).toJavaFormatString()有关系
猜测大概是2.3999999999999999超出了输出的精度所以被截取的原因吧

<think>首先,用户的问题是关于Java中浮点数计算精度丢失的解决方案。根据系统指令,我需要: 1. 使用中文回答。 2. 结构清晰,逐步解决问题。 3. 保证回答真实可靠。 4. 在行内数学表达式中使用$...$格式。 5. 独立公式使用$$...$$格式并单独成段。 6. 生成相关问题。 7. 在引用的段落末尾添加引用标识,如[^1]。 用户引用了三个参考内容: - [^1]: 讨论了浮点数计算精度丢失问题的一般概念和成因。 - [^2]: 提供了Java中使用BigDecimal来解决精度问题的示例代码。 - [^3]: 指出浮点数精度问题不是Java独有的,而是二进制浮点数的普遍问题。 用户的具体问题是:“我想理解并解决Java中浮点数计算的精度问题 请问Java 浮点数计算精度丢失解决方案” 所以,我需要构建一个结构清晰的回答: 1. **解释问题成因**:简要说明为什么浮点数计算会丢失精度,基于引用[^1]和[^3]。 2. **提供解决方案**:重点使用引用[^2]中的BigDecimal方法,并扩展其他可能的方案。 3. **结构清晰**:分步解释,使用标题或列表。 4. **添加引用**:在相关部分末尾添加引用标识。 5. **生成相关问题**:在回答末尾添加相关问题。 结构建议: - 引言:简要介绍浮点数精度问题- 成因分析:解释为什么发生精度丢失。 - 解决方案:详细描述Java中的解决方案,包括BigDecimal示例。 - 其他建议:可能提到其他方法如使用整数计算。 - 生成相关问题。 在数学表达式中: - 行内数学:如$0.1 + 0.2$。 - 独立公式:如果需要,如浮点数表示公式。 例如,浮点数通常表示为$(-1)^s \times m \times 2^e$,其中s是符号位,m是尾数,e是指数。 现在,大纲回答: 1. **理解浮点数精度问题** - 解释浮点数在计算机中的表示(基于IEEE 754标准)。 - 例如,许多十进制小数无法精确表示为二进制浮点数,如$0.1$。 - 引用[^1]和[^3]。 2. **Java中的解决方案** - 使用BigDecimal类:这是最可靠的方法。 - 提供示例代码,基于引用[^2]。 - 解释如何创建和使用BigDecimal对象。 - 其他方法: - 使用整数进行计算(如以分为单位表示金额)。 - 避免直接比较浮点数(使用容差)。 3. **最佳实践** - 何时使用BigDecimal。 - 性能考虑。 4. **生成相关问题** - 例如: 1. 如何在Java中比较两个浮点数? 2. BigDecimal的性能影响是什么? 3. 浮点数精度问题在哪些编程语言中常见? 最后,确保回答是中文的。</think>### Java浮点数计算精度丢失问题及解决方案 在Java编程中,浮点数(如`float`和`double`类型)常用于表示小数数值,但它们在计算时容易出现精度丢失问题。这是由于计算机内部使用二进制浮点数表示法(如IEEE 754标准),许多十进制小数无法精确转换为二进制形式。例如,简单的计算如$0.1 + 0.2$可能不会精确等于$0.3$,而是得到一个近似值如$0.30000000000000004$。这种问题在金融计算、科学模拟等高精度场景中尤为突出,可能导致计算结果不准确[^1][^3]。下面我将逐步解释问题成因,并提供Java中的实用解决方案,确保回答基于可靠参考。 #### 1. 理解精度丢失问题的成因 浮点数在计算机中以二进制形式存储,其一般表示形式为: $$(-1)^s \times m \times 2^e$$ 其中$s$是符号位,$m$是尾数(mantissa),$e$是指数(exponent)。许多十进制小数(如$0.1$)在二进制中是无限循环小数,无法被精确表示。例如: - $0.1$在二进制中近似为$0.0001100110011...$(无限循环),存储时会被截断。 - 当进行多次运算时,这些截断误差会累积,导致最终结果偏离预期。 在Java中,使用`float`(32位)或`double`(64位)类型时,这种问题不可避免。例如: ```java System.out.println(0.1 + 0.2); // 输出0.30000000000000004,而非0.3 System.out.println(1.0 - 0.42); // 输出0.5800000000000001,而非0.58 ``` 这种精度丢失源于浮点数的二进制表示特性,并非Java独有,而是所有使用IEEE 754标准的语言(如C++、Python)的常见问题[^1][^3]。 #### 2. Java中的解决方案 为了解决浮点数精度问题Java提供了多种方法。最可靠且推荐的方式是使用`BigDecimal`类,它支持任意精度的十进制计算。以下是具体步骤和示例: ##### 方案1: 使用BigDecimal类 `BigDecimal`通过字符串初始化避免二进制转换误差,并提供精确的算术运算。参考引用[^2],以下是完整示例: - **创建BigDecimal对象**:使用字符串构造函数,而非直接浮点数,以避免初始化误差。 - **进行运算**:调用`add()`, `subtract()`, `multiply()`, `divide()`等方法,并指定舍入模式(如`RoundingMode.HALF_UP`)。 ```java import java.math.BigDecimal; import java.math.RoundingMode; public class FloatPrecisionSolution { public static void main(String[] args) { // 错误示例:浮点数直接计算 System.out.println(0.01 + 0.05); // 输出0.060000000000000005 System.out.println(4.015 * 100); // 输出401.49999999999994 // 正确示例:使用BigDecimal BigDecimal num1 = new BigDecimal("0.01"); BigDecimal num2 = new BigDecimal("0.05"); System.out.println(num1.add(num2)); // 输出0.06 BigDecimal num3 = new BigDecimal("4.015"); BigDecimal num4 = new BigDecimal("100"); // 乘法示例,指定舍入模式(避免除不尽时异常) System.out.println(num3.multiply(num4)); // 输出401.500 // 除法示例:需处理精度和舍入 BigDecimal num5 = new BigDecimal("123.3"); BigDecimal num6 = new BigDecimal("100"); System.out.println(num5.divide(num6, 2, RoundingMode.HALF_UP)); // 输出1.23,保留2位小数 } } ``` - **优点**:绝对精确,适用于金融、会计等场景。 - **注意点**: - 始终使用字符串初始化(如`new BigDecimal("0.1")`),而非`new BigDecimal(0.1)`,后者可能引入初始化误差。 - 在除法运算中,必须指定精度和舍入模式,否则可能抛出`ArithmeticException`。 - 性能较低,比原生浮点数慢10-100倍,适合对精度要求高的关键计算[^2]。 ##### 方案2: 其他辅助方法 如果`BigDecimal`性能开销过大,可考虑以下替代方案: - **使用整数模拟小数**:将金额转换为整数(如以分为单位),计算后再转回小数。例如: ```java int cents1 = 100; // 代表1.00元 int cents2 = 50; // 代表0.50元 int sum = cents1 + cents2; // 150分,即1.50元 ``` - **设置容差(epsilon)比较浮点数**:避免直接使用`==`比较,改用容差范围: ```java double a = 0.1 + 0.2; double b = 0.3; double epsilon = 1e-10; // 容差值 if (Math.abs(a - b) < epsilon) { System.out.println("近似相等"); } ``` - **使用第三方库**:如Apache Commons Math的`Precision`类,提供工具方法处理精度。 #### 3. 最佳实践建议 - **优先场景**:在金融计算、货币处理或科学计算中强制使用`BigDecimal`。 - **性能权衡**:对于非关键计算(如游戏物理引擎),可使用`double`并接受微小误差。 - **避免常见错误**: - 不要用浮点数存储精确值(如ID或金额)。 - 在数据库交互中,使用`DECIMAL`类型而非`FLOAT`。 - 总体而言,`BigDecimal`是Java中解决精度丢失的标准方案,能完全规避二进制表示问题[^2][^3]。 ### 相关问题 1. 如何在Java中比较两个浮点数是否“相等”? 2. 使用BigDecimal时,如何处理除不尽的情况? 3. 浮点数精度问题在哪些实际应用中风险最高? 4. 除了Java,其他语言(如Python或JavaScript)如何解决浮点数精度丢失? [^1]: 浮点数计算精度丢失问题及解决方案。在编程中,浮点数是我们经常使用的一种数据类型,用于表示带有小数的数值。然而,由于计算机内部表示浮点数的方式,浮点数计算可能出现精度丢失问题。这种问题在进行科学计算、金融应用以及需要高精度数值的场景中尤为突出。本文将详细探讨浮点数计算的精度丢失问题,分析其成因,并提供解决方案。 [^2]: java中对于浮点型计算精度丢失问题的解决方法。java在计算浮点型数字时往往会出现如下精度丢失的情况,导致计算不准确,为了解决这个问题,可以通过使用BigDecimel进行转换后再计算。一般可以定义一个类将计算方法写进去再调用。 [^3]: java浮点数精度丢失_Java 浮点数计算精度丢失问题?。这个问题并不只是在Javascript中才会出现,任何使用二进制浮点数的编程语言都会有这个问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值