对浮点数精度丢失的一点研究
什么叫精度丢失
计算不准确
我们看看如下代码:
1 |
|
上述代码中f的结果本应该是10.000000,但是却输出10.000002,如下图所示。这就是浮点数计算不准确的现象,即精度丢失。
我们打印小数点后更多位数,能看得更清楚。printf("%.20f\n",f);
,得到的结果如下:
我们通过gdb调试看看存储在内存中的数据是什么样子的:
我们将其转化为二进制是0100 0001 0010 0000 0000 0000 0000 0010
。
将这个IEEE745标准的二进制浮点数转化为十进制为:10.000001907348633。
为什么会出现这种情况呢?这就要涉及到我们下一个知识点,保存不准确。
保存不准确
我们看看如下代码:
1 |
|
上述代码中打印结果如下所示:
我们来看看内存中如何表示这个数据的:
将这个二进制数转化为十进制数为:
可以看到某些浮点数在写入内存的过程中就存在误差,当然这是无法避免的。这个跟十进制中的无穷小数是一个道理。
浮点数判断大小或者排序
浮点数相等
当判断两个浮点数是否相等时,我们应该使用如下语句:
1 |
|
其中EPSILON为精度,例如0.000001。
float可以作为map中的key吗(Go语言)
从语法上看,Go语言中只要是可比较的类型都可以作为key。除开slice,map,functions这几种类型,其他类型都是OK的。具体包括:布尔值、数字、字符串、指针、通道、接口类型、结构体、只包含上述类型的数组。这些类型的共同特征是支持 ==
和 !=
操作符,k1 == k2
时,可认为 k1 和 k2 是同一个 key。当然,任何类型都可以作为value,包括map类型。
我们来看如下例子:
1 |
|
程序输出如下:
1 |
|
我们发现2.4000000000000000000000001存在value。当用float作为key的时候,先要将其转成uint64类型,再插入key中。通过以下函数实现:
1 |
|
也就是我们上述所说的IEEE 754规定的格式。
我们再来输出点东西:
1 |
|
输出结果如下:
1 |
|
转成十六进制如下:
1 |
|
由此可见,由于精度受限,2.4和2.4000000000000000000000001经过 math.Float64bits()
函数转换后的结果是一样的。自然,二者在 map 看来,就是同一个 key 了。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!