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
类。
精度为何是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位十进制数
时可能会出现不准确的情况。