最近在调试 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; |
调试你会发现条件不相等,你会看到变量 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
,只不过这样对第三方源码改动较大。