关于浮点数的精度问题

最近在调试 Jsoncpp 输出 Json 时候的浮点数问题,起因还是 Jsoncpp 输出精度的问题。又想起之前 java 开发同事竟然信心满满的告诉我说 double 不存在精度问题,真是感慨非C程序员对底层技术的掌握甚少。

关于浮点数

一般我们把数字称为整数、小数等,但是在计算机里面我们却称呼小数为浮点数,而非小数,那么为什么叫浮点数,这就涉及到小数在计算机内的存储了。

早期计算机存储小数采用的是固定小数点(Fixed Point Number)的方式,比如规定前多少bit是整数部分,剩余bit是小数部分,但是这样的表示方法非常有限,于是后来出现了 IEEE浮点数算术标准,也就是小数点浮动变化,并不固定,这就是所谓的浮点数(Float Point Number)。

在计算机存储中,一般采用科学计数法存储,即用一个有效数字。一个基数(Base)、一个指数(Exponent)以及一个表示正负的符号来表达实数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。从逻辑上讲,其结构大概如下(原理参考IEEE标准)

S(符号位) | E(指数位) | M(有效数字位)

由于存储全部是2进制方式,因此M位有效数字位分别表达的值是 1/2、1/4、1/8、1/16、… 、1/(2^N),也就是说计算机存储的值只能是各个位值的总和,这也就造就了浮点数注定不可能全部精确。

简单的,你可以使用任何语言进行DEBUG下面的代码,其他语言自行修改

float f = 0.0001;
if(f == 0.0001){
...
}

调试你会发现条件不相等,你会看到变量 f 的值类似为 9.99999975e-05,然后当你将 float 修改为 double 发现条件又能通过了。

开发关注点

我想 Java 开发者应该要明白,对浮点数有比较高需求的地方请尽量使用 BigDecimal 类型。那么C/C++语言只提供了单精度的float 和双精度的 double。基本大家都清楚的是的是单精度是4个字节,双精度是8个字节,主要的区别如下

类型 float double
大小 4字节 8字节
精度位 6 15
取值 -3.40E+38~3.40E+38 -1.79E+308~-1.79E+308

上面的参数可参考浮点数头文件 <float.h>

既然浮点数存储我们控制不了,在使用中基本就是两个方面,一个是浮点数的比较,需要注意精度问题,特别是默认的常量值的浮点数都是 double 类型,float 类型要加 f 后缀。在某些情况下的浮点数比较,可能还需要借助大小范围比较的方式确定。

另一个就是输出了,以C\C++的格式化控制符为例

%f %lf %e %g
float类型 double类型输出 科学计数法输出 %f和%e较短的输出

这其中,还可以通过添加长度控制整数和小数部分的精度,比如 %.3f 表示小数点后输出控制在3位等,这都是C\C++的基础知识了,不再赘述。

应用举例

Jsoncpp 库里面输出浮点类型的时候,采用了 %.17g,这个意思就是精度控制17位,但是这样基本输出很容易就变成了科学计数法了或者输出了不精确的值,仅仅一个 0.1 就输出科学计数法不太好,根据双精度浮点的小数位有效精度15位,根据我们的业务需要修改为15位,基本就不会输出科学计数法了,当然也可以直接修改为 %lf,只不过这样对第三方源码改动较大。

最近的文章

Poco插入大量数据到数据库的优化

最近在调试 Poco 操作数据库的性能问题,确实发现一些有意思的地方,不仅有事务方面的批量,还有容器实现的内部批量,当然先以最基础的事务模式作为开头,来进插入性能的对比测试 事务操作一般插入数据没什么好说的,但是在数据量比较多的时候,总体的插入就会很慢,这个时候最基本的操作就是批量操作,也就是靠事务 …

技术 继续阅读
更早的文章

Hexo引用显示本地图片

使用Hexo的同学都知道,Hexo的博客使用本地图片真的是非常麻烦,经常显示不出来,虽然本站最开始都固定死位置的,即本地位置和上传到服务器后位置不一样,文档里面地址适配具体位置,时间长了根目录下面的图片文件夹图片越来越多,也不好管理。鉴于博客本身生成后产生了一个目录,应该是可以使用的。 使用的问题先 …

技术 继续阅读