经典的精度丢失问题

本文探讨了Java中float和double类型的精度丢失问题,并通过示例说明了如何使用BigDecimal来解决此类问题,确保金融计算的准确性。

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

Java中的类型float、double用来做计算会有精度丢失问题,下面来看下面的示例。

  1. public static void main(String[] args) {

  2.    test1();

  3.    test2();

  4. }

  5.  

  6. private static void test1() {

  7.    double totalAmount = 0.09;

  8.    double feeAmount = 0.02;

  9.    double tradeAmount = totalAmount - feeAmount;

  10.    System.out.println(tradeAmount);

  11. }

上面的程序输出结果是多少?

0.07?非也!

正确的结果是:

  1. 0.06999999999999999

为什么是这样?

浮点数可能丢失精度,浮点十进制数通常没有完全相同的二进制的表示形式,这是CPU所采用的浮点数据表示形式的副作用。为此,可能会有一些精度丢失,并且一些浮点运算可能会产生未知的结果。

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。所以,在使用float、double作精确运算的时候一定要特别小心,除非能容忍精度丢失,不然产生的误差也是会造成双方对账不一致的结果。

怎么解决

在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。

BigDecimal适合更精度的运算,也提供了丰富的操作符类型,小数位控制,四舍五入规则等。

不过,使用BigDecimal不当也有精度丢失的情况,如double的构造方法:

  1. BigDecimal(double val)

再来看这个示例:

  1. private static void test2() {

  2.    double totalAmount = 0.09;

  3.    double feeAmount = 0.02;

  4.    BigDecimal tradeAmount = new BigDecimal(totalAmount).subtract(new BigDecimal(feeAmount));

  5.    System.out.println(tradeAmount);

  6. }

输出:

  1. 0.0699999999999999962529972918900966760702431201934814453125

这个精度就更恐怖了。。

所以,一定要使用String的构造方法:

  1. BigDecimal(String val)

  1. private static void test3() {

  2.    double totalAmount = 0.09;

  3.    double feeAmount = 0.02;

  4.    BigDecimal tradeAmount = new BigDecimal(String.valueOf(totalAmount))

  5.            .subtract(new BigDecimal(String.valueOf(feeAmount)));

  6.    System.out.println(tradeAmount);

  7. }

总结
  • 金额运算尽量使用BigDecimal(String val)进行运算。

  • 数据库存储金额,一般有整型和浮点型两种存储方式。如果是有汇率转换的,建议使用浮点数decimal进行存储,可以灵活的控制精度,decimal直接对应java类型BigDecimal。当然,用整数存储分这种形式也可以,转账的时候单位为元而如果忘了转换分为元,那就悲剧了。

转载于:https://www.cnblogs.com/chinaifae/p/10640890.html

<think>我们正在讨论将任意小数从Java后端传输到JavaScript时的精度问题。根据引用[1],任何使用二进制浮点数的编程语言都会有精度问题,但JavaScript作为弱类型语言,这个问题尤为突出。引用[2]和[3]指出,JavaScript的Number类型使用IEEE754双精度浮点数表示法,最多只能精确表示15-17位十进制数字。因此,当小数超过这个精度范围时,必然会出现精度丢失。解决方案:将小数转为字符串传输,或使用高精度库处理。在Java端,可以通过@JsonFormat(shape=JsonFormat.Shape.STRING)注解将BigDecimal序列化为字符串;在JavaScript端,使用decimal.js等库解析字符串并进行精确计算。注意:即使小数在Java中精确存储(如BigDecimal),通过JSON传输时若被转为数字类型,仍可能丢失精度。因此字符串序列化是最可靠的方法。</think>### 任意小数传输到 JavaScript 的精度问题分析 #### 核心结论 **会丢失精度**,原因在于 JavaScript 使用 IEEE 754 双精度浮点数标准表示所有数字[^1][^3]。具体表现: 1. **有效位数限制** JavaScript 最多精确表示 **15-17 位十进制小数**,超出部分会被截断或舍入: ```javascript // Java 传输值:0.1234567890123456789 // JavaScript 接收值:0.12345678901234568 // 第17位后丢失精度 ``` 2. **二进制表示误差** 某些十进制小数无法精确表示为二进制浮点数: ```javascript // 经典示例 0.1 + 0.2 === 0.3; // false → 实际结果 0.30000000000000004 ``` #### 根本原因:IEEE 754 双精度浮点数限制 | 参数 | 值 | 影响 | |--------------|----------------|-------------------------------| | **尾数位** | 52 位 | 决定有效数字精度(约15-17位) | | **指数位** | 11 位 | 决定数值范围($10^{-308}$~$10^{308}$) | | **存储方式** | 二进制分数 | 导致某些十进制小数无法精确表示 | 数学原理: $$ \text{浮点数} = (-1)^{\text{sign}} \times 2^{\text{exponent}} \times \left(1 + \sum_{k=1}^{52} b_{52-k} \times 2^{-k}\right) $$ #### 解决方案 1. **后端序列化为字符串(推荐)** ```java // Spring Boot 示例 public class DecimalDTO { @JsonFormat(shape = JsonFormat.Shape.STRING) private BigDecimal preciseValue; // 精确传输 "0.1234567890123456789" } ``` 2. **前端使用高精度库处理** ```javascript // 使用 decimal.js 库 import { Decimal } from 'decimal.js'; const value = new Decimal(apiResponse.preciseValue); value.plus(0.0000000000000001).toString(); // 精确计算 ``` 3. **约定精度范围(适用有限场景)** ```java // 限制小数位数 @DecimalMin("0.000000000000000") @DecimalMax("999999999999999.999999999999999") private BigDecimal limitedValue; ``` #### 精度丢失示例对比 | 原始值 (Java) | JSON 数字传输 (错误) | JSON 字符串传输 (正确) | |-----------------------|---------------------------|------------------------------| | 0.1234567890123456789 | → 0.12345678901234568 | → "0.1234567890123456789" | | 1/3 = 0.333... | → 0.3333333333333333 | → "0.33333333333333333333" | | 9007199254740992.123 | → 9007199254740992.1 | → "9007199254740992.123" | #### 关键注意事项 1. **服务间传输风险** 即使双方都是 Java 服务: - 若接收方使用 `double` 类型解析 → 仍会丢失精度 - 解决方案:统一使用 `BigDecimal` + 字符串序列化 2. **性能考量** | 方案 | 精度保障 | 传输体积 | 解析性能 | |-----------------|----------|----------|----------| | 数字传输 | ❌ | ⭐⭐ | ⭐⭐⭐ | | 字符串传输 | ✅ | ⭐ | ⭐⭐ | | 科学计数法 | ⭐ | ⭐⭐⭐ | ⭐⭐ | > **最佳实践**:金融、科学计算等场景必须使用字符串传输 + 前端高精度库处理[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值