BigDecimal(公式精确计算)+(精度丢失问题)

文章介绍了Java中使用BigDecimal进行精确的加减乘除运算,包括add、subtract、multiply、divide方法,并展示了不同舍入模式如ROUND_HALF_UP、ROUND_UP等的效果。同时,文章指出了double类型可能导致的精度丢失问题,推荐使用BigDecimal的构造函数或valueOf方法从字符串转换数值以保持精度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Java使用BigDecimal公式计算(精确计算)

介绍:

       使用BigDecimal加减乘除方法运算,可以使用BigDecimal类提供的add、subtract、multiply、divide方法函数实现。

公式加法计算~add

  public  static void main(String[] args){

       BigDecimal a = BigDecimal.valueOf(5.6);

       BigDecimal b = BigDecimal.valueOf(2.1);

       //BigDecimal计算add
       BigDecimal addResult = a.add(b);
       System.out.println("结果集: " +addResult);

       
   }

结果集: 

公式减法计算~subtract

public  static void main(String[] args){

       BigDecimal a = BigDecimal.valueOf(5.6);

       BigDecimal b = BigDecimal.valueOf(2.1);

       ////BigDecimal计算subtract
       BigDecimal subtract = a.subtract(b);

       System.out.println("结果集subtract: " +subtract);


   }

结果集:

 

公式乘法计算~multiply

public  static void main(String[] args){

       BigDecimal a = BigDecimal.valueOf(5.6);

       BigDecimal b = BigDecimal.valueOf(2.1);

       ////BigDecimal计算multiply
       BigDecimal multiply = a.multiply(b);

       System.out.println("结果集: " +multiply);


   }

结果集:

 

公式除法计算~divide

public  static void main(String[] args){

       BigDecimal a = BigDecimal.valueOf(5.6);

       BigDecimal b = BigDecimal.valueOf(2.1);

       //BigDecimal计算divide
       //ROUND_HALF_UP:向“最接近的”整数舍入。 若舍入位大于等于5,则对舍入部分的前一位数字加1;若舍入位小于5,则直接舍弃。即为四舍五入模式。
       BigDecimal divide = a.divide(b,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留两位小数.

       System.out.println("结果集: " +divide);


   }

结果集:

二、 BigDecimal(舍入模式)选择

简介:

BigDecimal.setScale主要用于对BigDecimal数据小数点后的位数进行 进位、舍位、截断等操作

java.math.RoundingMode:这是一种枚举类型,它定义了8种数据的舍入模式。它与java.math.BigDecimal类中定义的8个同名静态常量的作用相同,可用BigDecimal.setScale(int newScale, RoundingMode roundingMode)来设置数据的精度和舍入模式。
 

1、ROUND_UP:向远离零的方向舍入。

        若舍入位为非零,则对舍入部分的前一位数字加1;若舍入位为零,则直接舍弃。即为向外取整模式。

2、ROUND_DOWN:向接近零的方向舍入。

        不论舍入位是否为零,都直接舍弃。即为向内取整模式。

3、ROUND_CEILING:向正无穷大的方向舍入。

        若 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;若为负,则舍入行为与 ROUND_DOWN 相同。即为向上取整模式。

4、ROUND_FLOOR:向负无穷大的方向舍入。

        若 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;若为负,则舍入行为与 ROUND_UP 相同。即为向下取整模式。

5、ROUND_HALF_UP:向“最接近的”整数舍入。

        若舍入位大于等于5,则对舍入部分的前一位数字加1;若舍入位小于5,则直接舍弃。即为四舍五入模式。

6、ROUND_HALF_DOWN:向“最接近的”整数舍入。

        若舍入位大于5,则对舍入部分的前一位数字加1;若舍入位小于等于5,则直接舍弃。即为五舍六入模式。

7、ROUND_HALF_EVEN:向“最接近的”整数舍入。

        若(舍入位大于5)或者(舍入位等于5且前一位为奇数),则对舍入部分的前一位数字加1;

        若(舍入位小于5)或者(舍入位等于5且前一位为偶数),则直接舍弃。即为银行家舍入模式。

8、ROUND_UNNECESSARY

        断言请求的操作具有精确的结果,因此不需要舍入。

        如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
 

案例: 

 public  static void main(String[] args){

       BigDecimal a = BigDecimal.valueOf(5.6);

       BigDecimal b = BigDecimal.valueOf(2.1);

       //BigDecimal计算divide
       //ROUND_HALF_UP:向“最接近的”整数舍入。 若舍入位大于等于5,则对舍入部分的前一位数字加1;若舍入位小于5,则直接舍弃。即为四舍五入模式。
       //ROUND_UP:向远离零的方向舍入。  若舍入位为非零,则对舍入部分的前一位数字加1;若舍入位为零,则直接舍弃。即为向外取整模式。
       BigDecimal divide = a.divide(b,BigDecimal.ROUND_HALF_UP, 2).setScale(2,ROUND_UP);

       System.out.println("结果集: " +divide);


   }

结果集:

计算器结果集如下图:

三、 BigDecimal.setScale用法案例

BigDecimal.setScale主要用于对BigDecimal数据小数点后的位数进行 进位、舍位、截断等操作

public  static void main(String[] args){

       BigDecimal a = BigDecimal.valueOf(5.6);

       BigDecimal b = BigDecimal.valueOf(2.1);

       //BigDecimal计算divide
       BigDecimal divide = a.divide(b,2,BigDecimal.ROUND_UP).setScale(2, BigDecimal.ROUND_UP);

       System.out.println("结果集: " +divide);


   }

结果集:

四、Java使用BigDecimal精度丢失问题

介绍:

在实际应用中,经常会使用到计算,举例如 '银行' 特别是在我们交易的时候,计算结果要求的更加精确,这个时候我们就会使用到java.math包中提供的API类BigDecimal,用于对超过16位有效位数的数字进行精确的计算。

以如下代码为例:

public  static void main(String[] args){

       System.out.println("复现精度丢失:"+new BigDecimal(234.97));

   }

运行后结果集出现精度丢失: 

结果可以看出使用new BigDecimal(234.97),传入参数为double类型发生精度丢失,运行的结果并不等于234.97,而是为234.969999999999998863131622783839702606201171875。

原因是double不能表示为任何有限长度的二进制小数;

解决方法:    

使用new BigDecimal(String)构造函数,创建一个参数以字符串表示数值的对象

public  static void main(String[] args){

       double a = 234.97;

       String  b = "234.97";

       // 方式一:double的封装类Double中的toString()方法解决
       System.out.println("使用bigDecimal进行转换" + new BigDecimal(new Double(a).toString()));

       // 方式二:使用String接收数值
       System.out.println("使用bigDecimal进行转换" + new BigDecimal(b));


   }

 结果集如下:

注意:  String、int 不会丢失精度,将double,float转成String再计算也可以避免精度丢失问题。

解决案例:

public  static void main(String[] args){

        BigDecimal a = new BigDecimal(234.97);

        //复现精度丢失
        System.out.println("精度丢失: "+a);

       //进行转换
        System.out.println("使用bigDecimal进行转换: " +  BigDecimal.valueOf(new Double(String.valueOf(a))).toString());


   }

结果:

 解决案例:

public  static void main(String[] args){

        BigDecimal a = BigDecimal.valueOf(234.97); 

        //解决精度丢失
        System.out.println("解决精度不丢失: "+a);

       //进行转换
        System.out.println("使用bigDecimal进行转换: " +  BigDecimal.valueOf(new Double(String.valueOf(a))).toString());


   }

结果:

注意 : 结果看并没丢失,原因是这样定义的 BigDecimal.valueOf(234.97); 

总结:   BigDecimal.valueof(234.97) 等同于new BigDecimal("234.97") 结果是一样的 注意(new BigDecimal("234.97"))这里是有" "号的

<think>好的,我现在要帮用户解答关于double类型精度丢失的原因和解决方案的问题。首先,我需要回忆一下相关的知识,并整理出清晰的思路。 首先,用户的问题是关于double类型的精度丢失,所以我要先解释为什么会发生这种情况。记得之前学过的IEEE 754标准,这应该是浮点数表示的基础。因为double是64位的,按照这个标准,分为符号位、指数位和尾数位。尾数部分的位数有限,所以无法精确表示所有的小数,特别是那些二进制下无法有限表示的数,比如0.1这样的十进制数。这时候会出现舍入误差,导致精度丢失。 然后,需要举例子来说明,比如0.1在double中实际存储的可能是一个近似值。这样用户更容易理解。同时,运算过程中的累积误差也是一个重要因素,多次运算后误差会被放大,比如0.1累加十次可能不等于1.0。 接下来是解决方案。根据之前看到的引用内容,用户提到了几种方法,比如四舍五入、使用BigDecimal类(Java)或者转换成整数进行计算。这些都需要详细说明。例如,BigDecimal可以精确表示小数,但使用时需要注意构造方法,避免使用double参数,而是用字符串。另外,比较的时候要用compareTo而不是equals,这点也要强调。 还有,转换成整数处理也是一个方法,比如在处理货币时用分而不是元,这样可以避免小数运算。不过这可能只适用于特定场景,需要用户根据情况选择。 然后,用户可能想知道其他编程语言中的解决方案,比如JavaScript中的处理方式,比如使用toFixed方法,但需要注意其局限性,或者使用第三方库如decimal.js。这些可能需要补充,但根据提供的引用,可能主要参考Java的解决方案,但也可以简要提到其他语言的情况。 最后,要确保回答结构清晰,分点列出原因和解决方案,并引用提供的资料中的内容。需要检查是否有遗漏的点,比如BigDecimal的使用注意事项,或者IEEE 754标准的限制。 现在,组织语言的时候要注意用中文,行内公式用$符号,比如IEEE 754标准的double结构,可能需要用数学表达式来表示各部分位数。例如,符号位1位,指数11位,尾数52位,总共有64位,可以用$符号包裹。 还要注意用户可能的应用场景,比如金融计算或需要高精度的地方,这时候解决方案尤为重要。同时,提醒用户避免直接比较浮点数,而是比较它们的差值是否在某个很小的范围内。 总结一下,回答的结构应该是:先解释原因,再分点列出解决方案,最后可能的应用建议。确保每个部分都有引用支持,比如引用1、2、3提到的内容,正确标注在相应的位置。 在编写过程中,要避免使用Markdown格式,但根据用户提供的系统指令,可能需要使用$$...$$来单独显示公式,比如表示IEEE 754的结构时。不过用户示例中的公式都是行内的,所以可能不需要独立公式。不过如果必要的话,可以适当使用。 最后,生成相关问题,比如如何避免运算中的累积误差,或者在不同语言中的具体解决方案等。这些需要根据回答内容来设计。 现在开始撰写正式的回答,确保符合所有要求,包括引用标注和结构清晰。</think>### Double类型精度丢失的原因及解决方案 #### 一、精度丢失的原因 1. **二进制浮点数的固有缺陷** double类型遵循$IEEE\ 754$标准,使用64位存储数据(1位符号位,11位指数位,52位尾数位)。由于二进制无法精确表示某些十进制小数(如$0.1$),存储时会进行舍入近似,导致误差[^2]。例如: $$0.1_{十进制} = 0.\overline{00011}_二进制$$ 实际存储值为近似值,而非精确值。 2. **运算累积误差** 多次浮点运算会放大误差。例如,连续十次加$0.1$,结果可能不等于$1.0$: ```java double sum = 0; for (int i = 0; i < 10; i++) sum += 0.1; System.out.println(sum == 1.0); // 输出false ``` 3. **数值范围与精度限制** double类型的指数部分允许表示极大或极小数,但尾数位数固定(52位),超出精度的部分会被截断[^2]。 --- #### 二、解决方案 1. **四舍五入控制精度** 对计算结果进行四舍五入,限制小数位数。例如Java中使用`BigDecimal`: ```java BigDecimal result = new BigDecimal("0.1").multiply(new BigDecimal("3")); result = result.setScale(2, RoundingMode.HALF_UP); // 保留两位小数 double finalValue = result.doubleValue(); // 转回double ``` **注意**:构造`BigDecimal`时需用字符串参数,避免直接用double初始化[^1][^3]。 2. **使用高精度数据类型** - **Java:`BigDecimal`** 支持任意精度运算,但需注意: - 比较时用`compareTo()`而非`equals()`(`equals()`会检查小数位数是否相同)。 - 避免除法运算导致无限循环小数,需指定舍入模式。 - **JavaScript:`decimal.js`库** 提供精确的十进制运算,避免原生浮点误差。 3. **转换为整数运算** 将小数放大为整数进行计算,最后还原结果。例如处理金额时以“分”为单位: ```java int priceCents = 100; // 1.00元表示为100分 int quantity = 3; int totalCents = priceCents * quantity; // 300分 ``` 4. **避免直接比较浮点数** 使用误差范围(epsilon)判断相等性: ```java boolean isEqual = Math.abs(a - b) < 1e-10; ``` --- #### 三、应用建议 - **金融计算**:优先使用`BigDecimal`或整数运算。 - **科学计算**:明确误差范围,或采用符号计算库。 - **前端交互**:JavaScript中可用`toFixed()`格式化显示,但需注意其返回字符串类型[^2]。 ---
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你才是臭弟弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值