C++ 中的舍入

本文讨论的内容适用于 C++ 的绝大多数实现。(但是如果您使用的实现真的怪到家了,那谁也帮不了你)

(如果哪里出现了事实性错误请指出)

前置知识:浮点数

C++ 的基础类型里有三种浮点数类型,分别是 floatdoublelong double。浮点数使用 IEEE 754 标准,由于不是本文的重点内容,不做过多介绍。对于本文,你只需要知道:浮点数能表示的全都是形如 a×2ba\times 2^b 的数,其中 a,ba,b 都是整数,且范围有限制。浮点数不是实数,不能表示所有的数。

参见(维基百科):IEEE 754

前置知识:舍入模式

舍入模式是浮点环境的一部分,它影响着 C++ 程序中如何进行舍入。

通常情况下舍入模式有四种,分别对应头文件 <cfenv> 内定义的四个宏:

  • FE_DOWNWARD,向负无穷大舍入
  • FE_TONEAREST,向最接近可表示值舍入
  • FE_TOWARDZERO,向零舍入
  • FE_UPWARD,向正无穷大舍入

可以用 fegetround 函数获取舍入模式,fesetround 设置舍入模式,同样位于 <cfenv> 中。默认的舍入模式是 FE_NEAREST,也就是向最接近可表示值舍入。如果遇到中值,通常向偶数舍入。

参见:舍入模式浮点环境

从实数到浮点数的转换

浮点算术运算符和数学函数得到的结果是实数,可能不能精确地表示成浮点数,此时需要舍入。

运行时的算术运算符和数学函数,按照当前舍入模式舍入到一个可以表示的值。

算术运算符、平方根 sqrt、浮点求余 fmod、融合乘加 fma 及它们的类似运算,通常是保证准确的。也就是说,它们的行为相当于做无限精度的实数运算,最后再按照舍入模式转换到浮点数。

除此之外的各种数学函数牺牲了精度而加速了运行效率,因此精度误差可能会更大,不过仍然受舍入模式影响。不过影响偏向于瞎影响,甚至会发生向下舍比向上入还大的诡异情况

就算是这些保证准确的运算,也可能会受到缩略等优化的影响而得到预期以外的结果。

编译期的算术运算符和数学函数,和上面类似,但是它们不受浮点运行环境影响,永远表现为默认舍入模式。

参见:算术运算符常用数学函数

从字符串到浮点数的转换

浮点数能表示的全都是形如 a×2ba\times 2^b 的数,所以就连 0.10.1 都表示不了。

如果你在程序里写下了类似 0.1 这种字面量,就要被转换成浮点数。编译期的字面量转换和舍入模式无关,一律表现为默认舍入模式。

scanfcinatofstrtof 等需要将字符串转化为浮点数的时候,按照当前舍入模式舍入到一个可以表示的值。

从浮点数到字符串的转换

printfcoutto_string 等需要将浮点数转化为字符串的时候,一律向最接近舍入,如果遇到中值,向偶数舍入。

从浮点数到整数的转换

floorceiltrun 系列函数做它们字面意思上的事情。

round 系列函数不受舍入模式影响。当遇到两个整数的中值时,它向远离零的方向取整。

nearbyintrint 系列函数受到舍入模式影响。如果舍入模式是 FE_TONEAREST,当遇到两个整数的中值时,它们向偶数取整。对于其他几个模式,行为上分别等同于 floorceiltrun

输出为同级别浮点数的取整函数绝不会上溢或下溢。不过,输出为整数的取整函数(例如 ll 开头的),或者把输出浮点数转化为整数,可能会超出整数类型的范围。

浮点数向整数的类型转换永远向零舍入,类似 trun

从浮点数到浮点数的转换

如果把精度更高的浮点数转换为精度更低的浮点数,例如 double 转换为 float,可能出现低精度浮点数无法表示的情况,此时按照当前舍入模式进行舍入。

从整数到浮点数的转换

类似地,如果把精度更高的整数转换为精度更低的浮点数,例如 long long 转换为 double,也会出现类似的问题,此时按照当前舍入模式进行舍入。

舍入到偶数是个啥?

关于舍入到偶数的情况:例如舍入之前的值完全精确地表示为二进制小数 XXX.XXXY1,但是由于种种原因最后的 1 必须被舍入掉。假如 Y 是 1,那么就会入,进位到 XXX.ZZZ0(ZZZ0 是 XXX1 加一进位的结果)。如果 Y 是 0,那么最后的 1 舍去,变成 XXX.XXX0。舍入后新的最后一位永远是 0。

十进制数也是一样,舍入之前的值完全精确地表示为十进制小数 XXX.XXXY5,最后的 5 必须要舍入掉。如果这个 Y 是奇数就入,否则就舍。舍入后的最后一位也永远是偶数。

注意是完全精确,如果你尝试 printf("%.1lf", 1.05);,那么会输出 1.1,不满足舍入到偶数,因为 1.05 要转换成浮点数会首先被舍入为十进制数 1.0500000000000000444089209850062616169452667236328125,并不是完全精确地表示为十进制数 1.05。