• 金融行业是如何丢失1分钱的


    问题:

    不知道大家是否有遇到过这种情况,有时候明明两个准确的小数相加或者相减可以得到准确的值,但是不知道为什么会得到一个近似值,比如例1

     1 System.out.println("0.05 + 0.01: " + (0.05 + 0.01));
     2 System.out.println("1.0 - 0.9: " + (1.0 - 0.9));
     3 System.out.println("64.6 * 100: " + (64.6 * 100));
     4 System.out.println("1.0 - 0.42: " + (1.0 - 0.42));
     5 System.out.println("123.3 / 100: " + (123.3 / 100));
     6 System.out.println("0.06 - 0.01: " + (0.06 - 0.01));
     7 
     8 输出:
     9 
    10 0.05 + 0.01: 0.060000000000000005
    11 1.0 - 0.9: 0.09999999999999998
    12 64.6 * 100: 6459.999999999999
    13 1.0 - 0.42: 0.5800000000000001
    14 123.3 / 100: 1.2329999999999999
    15 0.06 - 0.01: 0.049999999999999996

    上面都是浮点类型的数据计算就会出现这种情况,这种情况我们称为 “精度丢失” 。

    如果用上面的方法进行计算的话呢,有可能我有1块钱,我买了0.9元的商品,再想买0.1元的商品就买不成功了。

    原因:

    这是怎么回事呢?

    是因为计算机采用的是二进制进行运算,运算前需要将十进制的数值转换成二进制。但是有些数值是无法转成二进制的,所以只能用近似值的二进制进行运算,所以得到的结果也只能是近似值,这就导致了精度丢失。比如说 0.05 就是无法转成精确的二进制的:

     1 0.05 * 2 = 0.1 取 0
     2 0.1 * 2 = 0.2  取 0
     3 0.2 * 2 = 0.4  取 0
     4 0.4 * 2 = 0.8  取 0
     5 0.8 * 2 = 1.6  取 1
     6 0.6 * 2 = 1.2  取 1
     7 0.2 * 2 = 0.4  取 0
     8 0.4 * 2 = 0.8  取 0
     9 0.8 * 2 = 1.6  取 1
    10 0.6 * 2 = 1.2  取 1
    11 0.2 * 2 = 0.4  取 0
    12 0.4 * 2 = 0.8  取 0
    13 0.8 * 2 = 1.6  取 1
    14 0.6 * 2 = 1.2  取 1
    15 0.2 * 2 = 0.4  取 0
    16 0.4 * 2 = 0.8  取 0
    17 0.8 * 2 = 1.6  取 1
    18 0.6 * 2 = 1.2  取 1

    从上面可以看出0.05的二进制只能是0.000011001100110011……无限循环 0011 下去,所以一直取不到精确的二进制,所以计算机只能取一个近似值进行运算。

    解决办法: 

    在JAVA中有BigDecimal类,我们可以用它来定义小数的变量进行计算。首先我们介绍一下BigDecimal这个类:

    常用构造函数:

    BigDecimal(int val)        -- 创建一个参数为整数的对象
    BigDecimal(double val)     -- 创建一个参数为双精度的对象
    BigDecimal(long val)       -- 创建一个参数为长整型数值的对象
    BigDecimal(String val)     -- 创建一个参数为字符串的对象    

     常用方法:

    public BigDecimal add(BigDecimal augend)     -- BigDecimal对象中的值相加,并返回这个对象                      
    public BigDecimal subtract(BigDecimal subtrahend)  -- BigDecimal对象中的值相减,并返回这个对象    
    public BigDecimal multiply(BigDecimal multiplicand)   -- BigDecimal对象中的值相乘,并返回这个对象    
    public BigDecimal divide(BigDecimal divisor)    -- BigDecimal对象中的值相除,并返回这个对象    
    public BigDecimal remainder(BigDecimal divisor)  -- BigDecimal对象中的值取余,并返回这个对象    
    public BigDecimal[] divideAndRemainder(BigDecimal divisor)  -- BigDecimal对象中的值相除并取余,并返回这个对象数组,[0]是商,[1]是余数    
    public static BigDecimal valueOf(double val)   -- 参数为double类型的值转换成BigDecimal类型的对象,并返回转换后的对象
    public static BigDecimal valueOf(long val)     -- 参数为long类型的值转换成BigDecimal类型的对象,并返回转换后的对象
    public long longValue()    -- 参数为BigDecimal类型的值转换成long类型的值,并返回转换后的值 
    public int intValue()    -- 参数为BigDecimal类型的值转换成int类型的值,并返回转换后的值 
    public double doubleValue()  -- 参数为BigDecimal类型的值转换成double类型的值,并返回转换后的值

     因为BigDecimal 的构造函数有很多,我们先用 BigDecimal(double val) 这个来初始化对象。例2:

     1 double d1 = 0.01;
     2 double d2 = 0.05;
     3 BigDecimal b9 = new BigDecimal(d1);
     4 BigDecimal b10 = new BigDecimal(d2);
     5 System.out.println("d1: " + d1);
     6 System.out.println("d2: " + d2);
     7 System.out.println("b9: " + b9);
     8 System.out.println("b10: " + b10);
     9 System.out.println("b9.add(b10): " + b9.add(b10));
    10 
    11 输出结果:
    12 
    13 d1: 0.01
    14 d2: 0.05
    15 b9: 0.01000000000000000020816681711721685132943093776702880859375
    16 b10: 0.05000000000000000277555756156289135105907917022705078125
    17 b9.add(b10): 0.06000000000000000298372437868010820238851010799407958984375

    发现用BigDecimal (double val) 实例化出来的对象还是近似值,我们看看构造函数的源码注释:

     1 <b>Notes:</b>
     2 <ol>
     3 <li>
     4 The results of this constructor can be somewhat unpredictable.
     5 One might assume that writing {@code new BigDecimal(0.1)} in
     6 Java creates a {@code BigDecimal} which is exactly equal to
     7 0.1 (an unscaled value of 1, with a scale of 1), but it is
     8 actually equal to
     9 0.1000000000000000055511151231257827021181583404541015625.
    10 This is because 0.1 cannot be represented exactly as a
    11 {@code double} (or, for that matter, as a binary fraction of
    12 any finite length).  Thus, the value that is being passed
    13 <i>in</i> to the constructor is not exactly equal to 0.1,
    14 appearances notwithstanding.
    15 
    16 <li>
    17 The {@code String} constructor, on the other hand, is
    18 perfectly predictable: writing {@code new BigDecimal("0.1")}
    19 creates a {@code BigDecimal} which is <i>exactly</i> equal to
    20 0.1, as one would expect.  Therefore, it is generally
    21 recommended that the {@linkplain #BigDecimal(String)
    22 <tt>String</tt> constructor} be used in preference to this one.
    23 
    24 <li>

    注释的第一部分主要是说了因为0.1无法准确的用double形式表示并且也不能用有限长度的二进制表示,所以只能取近似值,因此这个0.1值浮点类型只能用0.1000000000000000055511151231257827021181583404541015625来表示;

    注释的第二部分说的是因为BigDecimal(String val)构造函数能如人们所料的精确的表示0.1的值,所以通常更推荐使用该字符串为参数的构造函数来初始化对象。

    所以接下来我们用BigDecimal(String val)来初始化对象并运算,例3

     1 String s1 = "0.01";
     2 String s2 = "0.05";
     3 BigDecimal b13 = new BigDecimal(s1);
     4 BigDecimal b14 = new BigDecimal(s2);
     5 System.out.println("s1: " + s1);
     6 System.out.println("s2: " + s2);
     7 System.out.println("b13: " + b13);
     8 System.out.println("b14: " + b14);
     9 System.out.println("b13.add(b14): " + b13.add(b14));
    10 
    11 输出结果:
    12 
    13 s1: 0.01
    14 s2: 0.05
    15 b13: 0.01
    16 b14: 0.05
    17 b13.add(b14): 0.06

    由上面的结果我们可以看到,用BigDecimal(String val)是可以解决丢失精度的问题的。所以为了防止丢失进度问题,我们应该使用BigDecimal(String val)的构造函数来实例化对象。

    但是可能有人会说,可能其他系统提供的类型不是String类型的怎么办?那我们就用上面列举的常用方法 valueOf(val) 进行转换,初始值是double类型的,例4

     1 double d1 = 0.01;
     2 double d2 = 0.05;
     3 BigDecimal b9 = BigDecimal.valueOf(d1);
     4 BigDecimal b10 = BigDecimal.valueOf(d2);
     5 System.out.println("d1: " + d1);
     6 System.out.println("d2: " + d2);
     7 System.out.println("b9: " + b9);
     8 System.out.println("b10: " + b10);
     9 System.out.println("b9.add(b10): " + b9.add(b10));
    10 
    11 输出结果:
    12 
    13 d1: 0.01
    14 d2: 0.05
    15 b9: 0.01
    16 b10: 0.05
    17 b9.add(b10): 0.06

    其实例4也就是由例2改了红色标记的部分,但是得到的结果就是精确到值。

    总结:

    1. 我们遇到浮点型变量则使用BigDecimal(String val)进行初始化之后再运算;
    2. 如果原值不是字符串则使用public static BigDecimal valueOf(val) 进行转换得到BigDecimal 类型的对象;

    欢迎大家指教和点评!

  • 相关阅读:
    DbHelper数据操作类
    获取cpu序列号,硬盘ID,网卡MAC地址
    用户必备资料 103个Windows XP运行命令
    在Web.config配置文件中自定义配置节点
    Microsoft.NET PetShop4架构与技术分析
    数字转英文(货币)大写(vb)
    如何计算dba_tables中的avg_row_len.
    行选移与行链接的介绍
    如何使用动态SQL
    如何导致全表扫描原因
  • 原文地址:https://www.cnblogs.com/sunshine6/p/12549825.html
Copyright © 2020-2023  润新知