2024-03-13
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/3963291841

答案

在处理金融计算和表示金额时,是不能使用浮点数的(如 floatdouble),主要原因是涉及到浮点数的精度问题。

在计算机中所有的数据都要转换为二进制才能被计算机处理,但很遗憾,很多十进制小数在转换为二进制表示时是无限循环小数,这就导致了精确度的损失。所以,浮点数只是近似值,并不是精确值,不能用来表示金额,因为会有精度丢失。

扩展

浮点精度丢失

先看如下程序:

    @Test
    public void test01() {
        float f1 = 6.6f;
        float f2 = 1.3f;

        System.out.println(f1 + f2);
    }
// 执行结果
7.8999996

得到的答案并不是我们预想中的 0.9。再看一个:

    @Test
    public void test02() {
        float sum = 0;
        for (int i = 0 ; i < 10 ; i++) {
            sum += 0.1;
            System.out.println(sum);
        }
    }

执行结果:

0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001
1.0000001

结果也与我们预想中的不一样,为什么会出现这种情况呢?主要原因有如下几个:

  1. 基于二进制的表示法
  2. 有限的表示空间
  3. 舍入误差的积累

基于二进制的表示法

计算机内部使用二进制来存储和处理所有数据,包括浮点数,而我们常用的十进制数必须转换为二进制形式才能被计算机处理。一个十进制数转换二进制数分为整数部分和小数部分,最后通过拼接的方式来表示一个完整的十进制数。

十进制整数转换为二进制

对于正整数和 0 ,直接转换为二进制形式。而对于负整数,使用补码进行表示。

正整数转换为二进制步骤如下:

  1. 将整数除以 2。
  2. 记录余数。
  3. 将商作为新的数值重复步骤 1 和 2,直到商为 0。
  4. 将所有余数倒序排列,即为该整数的二进制表示。

以 6 为例

除以 2 余数
6 / 2 3 0
3 / 2 1 1
1 / 2 0 1

所以,6 的二进制表示为 0110。按照同样的方式 3 的二进制表示为 0011

负整数转换为二进制步骤如下:

  1. 先将对应的正整数转换为二进制。
  2. 然后将所有位取反(0变为1,1变为0)。
  3. 最后,给结果加1。

比如计算 -6 的二进制表示:

  1. 6 的二进制表示为 0110
  2. 取反:1001
  3. +1,则为 1010

十进制小数转换为二进制

整数采取除以 2 的模式来转换为二进制,小数部分则是通过乘以2的方法转换:

  1. 将小数部分乘以2。
  2. 记录结果的整数部分(0或1)作为二进制位。
  3. 取结果的小数部分作为新的数值重复步骤1和2,直到结果为0或达到所需的精度。
  4. 将步骤2中记录的所有二进制位按顺序排列,即为小数部分的二进制表示。

我们用 0.6 来演示下:

乘以 2 整数部分 小数部分
0.6 * 2 1 0.2
0.2 * 2 0 0.4
0.4 * 2 0 0.8
0.8 * 2 1 0.6
0.6 * 2 1 0.2
......

进入循环,所以 0.6 的二进制表示为 1001100110...。按照同样的方式,可以得到 0.3 的二进制表示为0100110011...

所以这里 6.6 和 3.3 的二进制表示如下:

  • 6.6:110.1001100110...
  • 3.3:11.0100110011...

这两个无效循环小数相加肯定不能得到一个精确的小数值。

有限的表示空间

为了解决小数转换为二进制无限循环而导致无法精确表示的情况,于是就有了 IEEE 754 标准。IEEE 754 规定了浮点数的存储方式、算术格式等方法。

IEEE 754 标准中,一个浮点数有 3 部分组成:符号(sign)、指数(exponent)、尾数(或称为有效数,mantissa),具体表现形式如下:

  • 符号(sign):1位,0表示正数,1表示负数
  • 指数(exponent):对于单精度浮点数,使用8位;对于双精度,使用11位。指数字段存储的值是实际指数加上一个偏移量(bias)。例如,在单精度浮点数中,偏移量是127,所以指数值123实际上表示 2^-4
  • 尾数(mantissa):在单精度中,23位;在双精度中,52位。尾数部分代表了数值的精确部分,它前面隐含一个1(对于非零数值),这意味着实际的尾数是1.xxxx形式,其中xxxx是存储在尾数字段中的值

由于尾数部分的位数是有限的,这意味着只能表示有限数量的小数位。因此,即使某些十进制小数可以精确转换为二进制形式,如果这些小数的二进制表示超出了尾数部分可以表示的位数,就必须对其进行四舍五入,这就会导致精度丢失。

阅读全文