double精度丢失

double测试代码:

    @Test
    public void testDouble() {
        double d1 = 2.01d;
        double d2 = 2.0099999999999997868371792719699442386627197265625d;
        System.out.println("d1 == d2 : " + (d1 == d2));
        System.out.println("d1 * 100 = " + (d1 * 100));
        System.out.println("d2 * 100 = " + (d2 * 100));
    }

测试结果

d1 == d2 : true
d1 * 100 = 200.99999999999997
d2 * 100 = 200.99999999999997

说明以double类型保存在计算机里面的浮点数d1=2.01的真实值是d2的值,double类型无法精准表示2.01,声明赋值时,就已经丢失精度了

BigDecimal表示小数:准确的用法是以字符串形式,使用BigDecimal构造函数创建

    @Test
    public void testBigDecimal() {
        double d1 = 2.01d;
        String d1Str = "2.01";
        System.out.println(new BigDecimal(d1Str).toPlainString());
        //与上面以字符串为参数,用构造函数创建是等价的
        System.out.println(BigDecimal.valueOf(d1).toPlainString());
        //以double类型传入,会得到因double而丢失精度之后的值
        System.out.println(new BigDecimal(d1).toPlainString());
    }

结果:

2.01
2.01
2.0099999999999997868371792719699442386627197265625

这里之所以要以字符串为BigDecimal构造函数参数,是因为BigDecimal是精确的,如果直接传入double类型参数,会把double类型记录在计算机中的真实值(例如声明 double d1 = 2.01d;此处由于十进制的小数不能用有限长的二进制小数来表示而丢失精度,实际保存的真实值是2.0099999999999997868371792719699442386627197265625,与2.01近似而已,在声明double类型赋值时,精度就丢失了)传给BigDecimal,而我们真正想要的值就是我们看到的值2.01,所以应该直接传入字符串"2.01"。

其他测试

    @Test
    public void testEquals() {
        double d1 = 2.01d;
        double d2 = 2.0099999999999997868371792719699442386627197265625d;
        System.out.println(d1 == d2);
        System.out.println(Double.toString(d1));
        System.out.println(Double.toString(d2));
        System.out.println(new BigDecimal(d1).equals(BigDecimal.valueOf(d1)));
        System.out.println(new BigDecimal(d2).equals(BigDecimal.valueOf(d2)));
        System.out.println(new BigDecimal(d1).equals(new BigDecimal(d2)));
        System.out.println(BigDecimal.valueOf(d1).equals(BigDecimal.valueOf(d2)));
    }

测试结果:

true
2.01
2.01
false
false
true
true

double丢失精度原因:
N 位二进制的小数的精度是 1/2 的 N 次方。也就是说,N 位二进制数只能表示(1/2 的 N 次方)的整数倍的数。M 位十进制的小数的精度是 1/10 的 M 次方。1/10 = 1/2 * 1/5。那么 1/5 这个数(即0.2),就不能用有限长的二进制小数来表示。
参考:
Double和Float运算以及DecimalFormat格式化精度丢失踩坑记录
https://blog.youkuaiyun.com/xdkb159/article/details/106081425

### Java Long 类型转换为 Double 类型时出现精度丢失的原因 在Java中,`Long`类型用于表示64位带符号整数,其范围是从-2^63到2^63-1。然而,当将`Long`类型的值转换为`Double`类型时,可能会发生精度丢失的问题[^1]。 原因在于`Double`类型遵循IEEE 754标准中的双精度浮点数格式,它能够精确表示大约15至17位有效十进制数字。对于超出此范围的大整数值,`Double`可能无法完全准确地存储这些值的所有细节,从而导致部分信息被舍弃或四舍五入,进而造成精度损失[^3]。 具体来说,由于`Double`内部采用科学计数法的形式保存数据,即由符号位、指数位以及尾数位组成,因此并非所有整数都能得到确切表达。特别是那些非常大或者非常小的整数,在转化为`Double`之后很可能会失去一些低位上的准确性[^4]。 ### 解决方案 为了防止这种情况下发生的精度问题,可以采取以下几种措施: #### 方法一:使用字符串作为中间媒介 一种常见的做法是在传输过程中把`Long`对象先转变为字符串形式传递给前端或其他组件,然后再根据需求解析回相应的数值类型。这种方式能有效地保留原始数据的全部信息而不受接收端所使用的数值类型影响[^2]。 ```java // 将Long转成String发送 public static String longToString(Long value){ return String.valueOf(value); } // 接收方接收到的是String, 需要的时候再parse回去 public static Long stringToLong(String strValue) throws NumberFormatException { return Long.parseLong(strValue); } ``` #### 方法二:自定义序列化/反序列化逻辑 如果正在使用像Jackson这样的库来进行JSON序列化操作,则可以通过配置特定字段的序列化方式来避免自动将`Long`转换为`Double`的行为。例如设置属性为只接受整数输入或将整个实体类标记为仅支持整数输出等策略都可以帮助维持原有数据的完整性。 ```json { "id": {"type":"integer"} } ``` 另外还可以通过编写自己的序列化器和反序列化器来自定义处理过程,确保每次都将`Long`直接映射为目标平台上的合适类型而不是默认走一遍通用路径。 #### 方法三:调整业务逻辑设计 从根本上讲,应该尽量减少不必要的类型转换次数,并且尽可能保持原生的数据结构不变直到确实有必要改变为止。比如数据库查询结果可以直接映射到对应的POJO(Plain Old Java Object)实例上;API接口响应体也应按照实际需要选择最恰当的表现形式等等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值