• BigDecimal精度丢失问题


    浅谈BigDecimal精度丢失问题


    一. 简介

    ​ Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。

    二. 错误使用

    在我们的日常开发中,对于金钱的处理我们都会使用BigDecimal进行处理,但是如果我们不熟悉使用的话,很容易出现如下的错误使用方式。
    代码演示:

    
    import java.math.BigDecimal;
    
    public class DecimalTest {
        public static void main(String[] args) {
            BigDecimal bd1 = new BigDecimal(0.1);
            System.out.println("bd1的值:"+bd1);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    从上面的执行可以看出,java中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行价格计算的时候,就会出现问题。

    三. 原因分析

    计算机组成原理里面都有,它们的编码决定了这样的结果。
    long可以准确存储19位数字,而double只能准确存储16位数字。

    double由于有exp位,可以存16位以上的数字,但是需要以低位的不精确作为代价。如果需要高于19位数字的精确存储,则必须用BigInteger来保存,当然会牺牲一些性能。

    源码注释解读:

     /**
         * Translates a {@code double} into a {@code BigDecimal} which
         * is the exact decimal representation of the {@code double}'s
         * binary floating-point value.  The scale of the returned
         * {@code BigDecimal} is the smallest value such that
         * (10scale × val) is an integer.
         * 

    * Notes: *

      *
    1. * The results of this constructor can be somewhat unpredictable. * One might assume that writing {@code new BigDecimal(0.1)} in * Java creates a {@code BigDecimal} which is exactly equal to * 0.1 (an unscaled value of 1, with a scale of 1), but it is * actually equal to * 0.1000000000000000055511151231257827021181583404541015625. * This is because 0.1 cannot be represented exactly as a * {@code double} (or, for that matter, as a binary fraction of * any finite length). Thus, the value that is being passed * in to the constructor is not exactly equal to 0.1, * appearances notwithstanding. * *
    2. * The {@code String} constructor, on the other hand, is * perfectly predictable: writing {@code new BigDecimal("0.1")} * creates a {@code BigDecimal} which is exactly equal to * 0.1, as one would expect. Therefore, it is generally * recommended that the {@linkplain #BigDecimal(String) * String constructor} be used in preference to this one. * *
    3. * When a {@code double} must be used as a source for a * {@code BigDecimal}, note that this constructor provides an * exact conversion; it does not give the same result as * converting the {@code double} to a {@code String} using the * {@link Double#toString(double)} method and then using the * {@link #BigDecimal(String)} constructor. To get that result, * use the {@code static} {@link #valueOf(double)} method. *
    * * @param val {@code double} value to be converted to * {@code BigDecimal}. * @throws NumberFormatException if {@code val} is infinite or NaN. */
    public BigDecimal(double val) { this(val,MathContext.UNLIMITED); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 第一段也说的很清楚它只能计算的无限接近这个数,但是无法精确到这个数。
    • 第二段则说,如果要想准确计算这个值,那么需要把double类型的参数转化为String类型的。并且使用BigDecimal(String)这个构造方法进行构造。去获取结果。

    四. 正确使用

    我们一般使用BigDecimal来解决商业运算上丢失精度的问题的时候,声明BigDecimal对象的时候一定要使用它构造参数为String的类型的构造器。

    代码演示:

    import java.math.BigDecimal;
    
    public class DecimalTest {
        public static void main(String[] args) {
            // 错误使用方式
            BigDecimal bd1 = new BigDecimal(0.1);
            System.out.println("bd1的值:"+bd1);
            // 正确使用方式
            BigDecimal bd2 = new BigDecimal("0.1");
            System.out.println("bd2的值:"+bd2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    另外,BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。

  • 相关阅读:
    线性dp,271. 杨老师的照相排列
    基于SpringBoot框架的网上购物商城系统的设计与实现
    lottie 如何停留最后一帧
    【PAT甲级 - C++题解】1101 Quick Sort
    pcl--第十二节 2D和3D融合和手眼标定
    VMware ESXi 7.0 Update 3e SLIC 2.6 & macOS Unlocker (2022.07 更新)
    【华为机试真题 JAVA】字符统计及重排-100
    安科瑞导轨式多功能电能表DTSD1352指导性技术要求-Susie 周
    MapReduce综合应用案例 — 招聘数据清洗
    STM32 CAN使用记录:bxCAN基础通讯
  • 原文地址:https://blog.csdn.net/qq_44936392/article/details/127943681