BigDecimal精度丢失处理

本文探讨了在使用BigDecimal进行数值计算时,直接传入double或float会导致精度丢失的问题。通过示例代码展示了构造BigDecimal对象时,从字符串转换能避免精度丢失。并提供了一个实用工具类`BigDecimalUtils`,包含了加减乘除和比较操作,确保在运算过程中保持精度。测试结果显示,使用该工具类能正确计算并打印预期结果。

BigDecimal 有参构造方法,传入 double,float 会丢失精度。

运行下面代码,期待结果都是11、1.1、1.1、1.1,
实际结果:
11
1.1
1.100000000000000088817841970012523233890533447265625
1.10000002384185791015625

BigDecimal bigDecimal=new BigDecimal(11);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal("1.1");
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(1.1d);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(1.1f);
System.out.println(bigDecimal);

精度丢失处理

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

package com.example.demo.utils;

import java.math.BigDecimal;

public class BigDecimalUtils {
    
    /**
     * 求和
     * @param v1
     * @param v2
     * @return v1 + v2 的和
     */
    public static BigDecimal doubleAdd(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }

    /**
     * 求和
     * @param v1
     * @param v2
     * @return v1 + v2 的和
     */
    public static BigDecimal floatAdd(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.add(b2);
    }

    /**
     * 求差
     * @param v1
     * @param v2
     * @return v1 - v2 的差
     */
    public static BigDecimal doubleSubtract(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2);
    }

    /**
     * 求差
     * @param v1
     * @param v2
     * @return v1 - v2 的差
     */
    public static BigDecimal floatSubtract(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.subtract(b2);
    }

    /**
     * 求积
     * @param v1 被乘数
     * @param v2 乘数
     * @return v1 * v2 的积
     */
    public static BigDecimal doubleMultiply(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2);
    }

    /**
     * 求积
     * @param v1 被乘数
     * @param v2 乘数
     * @return v1 * v2 的积
     */
    public static BigDecimal floatMultiply(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.multiply(b2);
    }

    /**
     * 求商
     * @param v1 被除数
     * @param v2 除数
     * @return v1 / v2 的商
     */
    public static BigDecimal doubleDivide(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 保留小数点后两位 ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 求商
     * @param v1 被除数
     * @param v2 除数
     * @return v1 / v2 的商
     */
    public static BigDecimal floatDivide(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        // 保留小数点后两位 ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 比较v1,v2大小
     * @param v1
     * @param v2
     * @return v1>v2 return 1, v1=v2 return 0, v1<v2 return -1
     */
    public static int doubleCompareTo(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return  b1.compareTo(b2);
    }

    /**
     * 比较v1,v2大小
     * @param v1
     * @param v2
     * @return v1>v2 return 1, v1=v2 return 0, v1<v2 return -1s
     */
    public static int floatCompareTo(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return  b1.compareTo(b2);
    }

}

测试

System.out.println("-----------------Add------------------");
System.out.println(BigDecimalUtils.doubleAdd(1.1, 2.2));
System.out.println(BigDecimalUtils.floatAdd(1.1f, 2.2f));
System.out.println("-----------------Subtract------------------");
System.out.println(BigDecimalUtils.doubleSubtract(2.2, 1.1));
System.out.println(BigDecimalUtils.floatSubtract(2.2f, 1.1f));
System.out.println("-----------------Multiply------------------");
System.out.println(BigDecimalUtils.doubleMultiply(2.2, 1.1));
System.out.println(BigDecimalUtils.floatMultiply(2.2f, 1.1f));
System.out.println("-----------------Divide------------------");
System.out.println(BigDecimalUtils.doubleDivide(2.2, 1.1));
System.out.println(BigDecimalUtils.floatDivide(2.2f, 1.1f));

结果打印:
-----------------Add------------------
3.3
3.3
-----------------Subtract------------------
1.1
1.1
-----------------Multiply------------------
2.42
2.42
-----------------Divide------------------
2.00
2.00

<think>我们正在解决Jackson序列化BigDecimal精度丢失问题。根据引用内容,我们知道当使用Mybatis的resultType="map"时,Jackson会默认将数值类型序列化为BigDecimal,而如果只配置了Long的序列化,对map中的BigDecimal不会生效,需要额外配置BigDecimal和BigInteger的序列化。 解决方案: 1. 自定义一个ObjectMapper,并注册一个模块,该模块包含针对BigDecimal和BigInteger的序列化器,将它们序列化为字符串以避免精度丢失。 2. 使用自定义的序列化器,例如ToStringSerializerForJackson(这是一个自定义的序列化器,将BigDecimal和BigInteger换为字符串)。 具体步骤: a) 创建自定义的序列化器类(如果使用Jackson提供的ToStringSerializer,则可以直接使用,但注意它会在数字前后加上引号,使其成为字符串。不过,我们也可以自定义一个,确保不改变数值的精度)。 b) 在自定义的ObjectMapper中注册这个序列化器。 引用[1]中提到了一个自定义的序列化类ToStringSerializerForJackson,我们可以参考它。实际上,Jackson已经提供了一个`ToStringSerializer`,它可以将任何对象调用其`toString()`方法进行序列化。对于BigDecimal,我们可以使用这个序列化器,这样就会将BigDecimal换为字符串形式,从而避免精度丢失。 但是,注意:使用`ToStringSerializer`会将数字序列化为字符串,这可能会影响前端的使用(前端需要知道这个字段是字符串形式的数字)。另一种做法是使用自定义的序列化器,将BigDecimal序列化为其原始数值(不丢失精度),但BigDecimal换成double时可能会丢失精度,所以换为字符串是最安全的方式。 因此,我们采用以下方案: 步骤1:创建自定义的ObjectMapper 步骤2:注册模块,并为BigDecimal和BigInteger添加序列化器(使用ToStringSerializer) 代码示例: ```java import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import java.math.BigDecimal; import java.math.BigInteger; public class JacksonUtil { private static final ObjectMapper mapper; static { mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); // 使用ToStringSerializer来处理BigDecimal和BigInteger module.addSerializer(BigDecimal.class, ToStringSerializer.instance); module.addSerializer(BigInteger.class, ToStringSerializer.instance); mapper.registerModule(module); } public static String toJSONString(Object obj) { try { return mapper.writeValueAsString(obj); } catch (Exception e) { throw new RuntimeException(e); } } } ``` 这样,在使用`JacksonUtil.toJSONString`时,BigDecimal和BigInteger都会被序列化为字符串,从而避免精度丢失。 注意:ToStringSerializer会将数字换为字符串,所以在反序列化时,前端需要以字符串的形式传递,或者我们在反序列化时也需要做相应的处理(将字符串换回BigDecimal)。如果前端期望的是数字字符串,那么这种方法是可行的。 另外,引用[2]提到,在反序列化时,如果原始数据是浮点数,Jackson会将其视为DOUBLE类型,然后使用BigDecimal来存储,但这里我们主要解决序列化的问题。 如果希望序列化成数字而不是字符串,那么需要自定义一个序列化器,将BigDecimal不经过任何换直接输出为数字字符串(不带引号)?但这样不符合JSON规范,因为JSON中数字是不带引号的,而字符串带引号。所以,如果我们希望保留精度,最好的方式就是使用字符串,这样前端可以用字符串来接收,然后换成相应的数字类型(如BigDecimal)。 因此,上述方案是常见的解决方案。 相关问题: 1. 如何自定义Jackson的序列化器和反序列化器? 2. 除了将BigDecimal序列化为字符串,还有哪些方法可以避免精度丢失? 3. Jackson在处理数值类型时还有哪些常见问题?</think>### Jackson BigDecimal 序列化精度丢失解决方案 当使用 `JacksonUtil.toJSONString` 序列化 BigDecimal 时出现精度丢失问题,核心原因是 Jackson 默认将 BigDecimal 序列化为浮点数(可能导致科学计数法或截断)。以下是完整解决方案: #### 解决方案 1:全局配置 ObjectMapper(推荐) ```java import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; // 创建自定义ObjectMapper ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); // 关键配置:将BigDecimal序列化为字符串 module.addSerializer(BigDecimal.class, ToStringSerializer.instance); module.addSerializer(BigInteger.class, ToStringSerializer.instance); // 同时处理BigInteger mapper.registerModule(module); // 使用配置后的mapper序列化 String json = mapper.writeValueAsString(yourObject); ``` #### 解决方案 2:注解方式(针对特定字段) ```java import com.fasterxml.jackson.annotation.JsonFormat; public class YourClass { @JsonFormat(shape = JsonFormat.Shape.STRING) private BigDecimal amount; // 序列化为字符串 "123.456" } ``` #### 解决方案 3:自定义序列化器(精细控制) ```java import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class BigDecimalSerializer extends JsonSerializer<BigDecimal> { @Override public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 保留原始精度输出 gen.writeString(value.toPlainString()); } } // 注册到ObjectMapper SimpleModule module = new SimpleModule(); module.addSerializer(BigDecimal.class, new BigDecimalSerializer()); ``` #### 关键注意事项 1. **Map 类型处理** 当使用 `resultType="map"` 时,必须显式配置 BigDecimal 序列化器[^1],否则全局配置可能失效 2. **反序列化兼容性** 序列化为字符串后,反序列化需使用: ```java @JsonCreator public static BigDecimal fromString(String value) { return new BigDecimal(value); } ``` 3. **性能影响** 字符串序列化会增加约 5-10% 的 JSON 体积,但可完全避免精度丢失 4. **JacksonUtil 集成** 若使用封装工具类,需修改其底层 ObjectMapper 配置: ```java public class JacksonUtil { private static final ObjectMapper mapper = new ObjectMapper(); static { SimpleModule module = new SimpleModule(); module.addSerializer(BigDecimal.class, ToStringSerializer.instance); mapper.registerModule(module); } public static String toJSONString(Object obj) { return mapper.writeValueAsString(obj); } } ``` > **精度丢失原理**:Jackson 默认通过 `numberNode(BigDecimal v)` 将 BigDecimal 为浮点数,IEEE 754 双精度浮点数最多只能精确表示 15-17 位十进制数[^2],超出部分会被截断或为科学计数法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值