浮点型的精度探讨

2024-11-07

什么是浮点数的精度问题?

System.out.println(0.1 + 0.2); //0.30000000000000004

以上是一个Java代码,小数相加默认精度是不足,因为0.1和0.2被系统识别为浮点型

浮点型的精度

任何编程语言都无法避开浮点型精度不足的问题。

因为浮点型本来就是为了存储更大的数据,而牺牲精度。

比如4位比特1010,本来最多存储$2^4$个数字(无符号数),$2^3-1$(有符号数,之所以-1,与补码的设计有关)。

现在,我把第一位设计成符号位,第二位设计成指数位,即$2^x$(注意,这里不是$10^x$,精度不准主要是它的锅,随着指数上升,数字是不可控,尾数不是都是0),其他位设计成正常的位数,这样就可以表示更多的数。

比如,1110代表了负数(1),指数位($2^1$),尾数位(二进制1.10,需要加上隐含的1在左侧)

尾数位二进制1.10转成十进制

$$ 1\times2^0+1\times2^{-1}+0\times2^2=1.5 $$

所以根据浮点数计算规则"$符号位\times指数位\times尾数位$ ",就是结果,因此1010比特的结果是-3

$$-1 \times 2^1 \times 1.5 = -3$$

可以想象,如果位数变多,为了代表尽可能多的位数,必然要舍弃一些精度,因为已经不是用数字本身来表示它自身了。

解决方案

浮点类型float、double的数据不适合在不容许舍入误差的金融计算领域。如果需要精确数字计算或保留指定位数的精度,Java语言可以使用BigDecimal类。

32位浮点数-float

精度为何是6-7位?

32位浮点数(即单精度浮点数)在IEEE 754标准下被定义为:

1位符号位

8位指数位

23位尾数(也称为小数部分或有效位)

尾数部分实际上表示一个24位的二进制数,因为在标准化形式下隐含了一个前导的1。因此,23位的尾数加上隐含的1位,实际是24位的二进制有效位。

有效精度计算

二进制数的24位相当于十进制的几位呢?我们需要将二进制的24位转换成十进制的位数。这个转换并不是直接的位数转换,而是需要考虑数的范围和精度。

一个n位的二进制数可以表示 ($2^n$) 个不同的数。因此,24位的二进制数可以表示 ($2^{24}$) 个不同的数。为了找到其等效的十进制位数,我们需要找到一个m,使得 ($10^m \approx 2^{24}$)。

利用对数来转换: $$[ m \cdot \log_{10}(10) \approx 24 \cdot \log_{10}(2) ]$$

因为 $(\log_{10}(10) = 1)$,所以: $$[ m \approx 24 \cdot \log_{10}(2) ]$$

已知 $(\log_{10}(2) \approx 0.3010)$,代入计算: $$[ m \approx 24 \cdot 0.3010 ]$$ $$[ m \approx 7.224 ]$$

因此,24位二进制数相当于大约7.224位的十进制数。这就是为什么32位浮点数(单精度浮点数)的有效精度大约是6-7位十进制数的原因。

误差和精度

在实际计算中,由于二进制浮点数只能表示有限精度的数值,因此在进行数值计算时可能会出现舍入误差。这些误差会导致计算结果的有效精度约为6到7位。这也意味着在使用单精度浮点数进行计算时,结果通常在第6到第7位十进制数时可能会出现不准确的情况。