printf 如何舍入浮点数
flag
mode_edit
神秘的浮点数舍入
printf
输出浮点数的时候,究竟是怎么舍入的呢?
有一种比较常见的误解,是认为它使用了四舍五入。
考虑下面的代码的输出:
#include <bits/stdc++.h>
using namespace std;
void round_to_1(double a) {
printf("%lf => %.1lf\n", a, a);
}
void round_to_0(double a) {
printf("%lf => %.0lf\n", a, a);
}
int main () {
for (int i = 1; i < 9; ++i) {
round_to_0(1 * i + 0.5);
}
std::cout << "===========" << std::endl;
for (int i = 1; i < 9; ++i) {
round_to_1(0.1 * i + 0.05);
}
}
输出(删去了多余的0):
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6
7.5 => 8
8.5 => 8
===========
0.15 => 0.2
0.25 => 0.2
0.35 => 0.4
0.45 => 0.5
0.55 => 0.6
0.65 => 0.7
0.75 => 0.8
0.85 => 0.9
太玄妙了。
向偶数取整/奇进偶舍/四舍六入五成双
首先来研究等号以上的部分。这时候使用的舍入方法并非常见的四舍五入。而是一种比较奇妙的舍入方式:
也称银行家舍入法。按照一般的规则进行四舍六入,如果下一位正好为 5,并且之后没有更多的位数,则舍入至双数。例如,3.5 舍入成 4,而 6.5 舍入成 6。在四舍五入的数据比较多的情况下,可以避免平均数等统计数据出现较大误差。
好,那么我们来继续看等号以下的部分:好像不大对劲!
如果按照奇进偶舍的规律,0.45 应该舍入到 0.4,而 0.65 应该舍入到 0.6,而 0.85 应该是 0.8 才对!
浮点误差
实际上浮点数能精确表示的值并不多。正如能表示成有限位十进制小数的有理数 $a$ 都具有 $a = \dfrac{p}{2^{q_1}\cdot5^{q_2}}$ 的形式一样,有限位二进制小数能准确表示的有理数都具有 $a=\dfrac p{2^q}$ 的形式。我们来以 0.45 为例,看一下究竟是怎么回事:
0.45 表示为 32 位的浮点数时,是 $2^{-2}\cdot 1.7999999523162841796875\approx0.4499999880791$,而表示为 64 位的浮点数时,是 $2^{-2}\cdot 1.8000000000000000444089209850062616169452 \geq\dfrac{1.8}4=0.45$, 因此 0.45 实际上是大于 0.45 的,因此应用奇进偶舍的时候时会变成 0.5.
考虑到 0.45 的奇妙性质,因此下面的代码的输出是 0.4 0.5
printf("%.1f %.1lf", (float)0.45, 0.45)
Reference
- IEEE-754 Analysis - https://babbage.cs.qc.cuny.edu/IEEE-754/
- Rounding - https://en.wikipedia.org/wiki/Rounding
navigate_before
《CS:APP》 data lab
二项堆与斐波那契堆
navigate_next