{ }codelper

学习 › C

进阶 · 20

整数与浮点数

为什么 0.1 + 0.2 不等于 0.3,固定小数位输出,舍入规则,以及整数溢出。

数有两种:整数和浮点数。
整数精确但有上限;浮点数能保存小数,但会有极小的误差。
下面的例子把除法结果输出到小数点后两位,再依次演示截断、向下取整、四舍五入和大数加一。

#include <stdio.h>#include <math.h> int main(void) {    int a, b;    double x, h;    long long big;    scanf("%d %d %lf %lf %lld", &a, &b, &x, &h, &big);     printf("%.2f\n", (double)a / b);         /* a / b만 쓰면 0 */    double s = 0.1 + 0.2;                     /* 0.30000000000000004 */    printf("%s\n", fabs(s - 0.3) < 1e-9 ? "close" : "far");    printf("%d %d\n", (int)x, (int)floor(x)); /* -3 -4 */    printf("%.0f\n", round(h));               /* round는 0에서 먼 쪽 */    printf("%lld\n", big + 1);                /* int였다면 넘침(정의되지 않은 동작) */    return 0;}
stdin
2 3
-3.7
2.5
2147483647
stdout
0.67
close
-3 -4
3
2147483648

要点

  • 整数(int、long)精确地保存整数值。
    浮点数(double)用二进制存储,所以会产生极小的误差。
  • 0.1 + 0.2 在所有语言里都是 0.30000000000000004。
    所有语言的 double 都是同一种 IEEE 754 双精度,所以误差也完全一样。
  • 正好一半(2.5)往哪边舍入,各语言不同。
    有三种规则:向偶数(银行家舍入)、向 +∞、远离 0。
  • 把浮点数转换成整数时,小数部分向 0 截断。
    所以 -3.7 变成 -3,而向下取整的 floor(-3.7) 是 -4。
  • 整数也有上限。
    32 位 int 最大是 2147483647(约 21 亿),更大的数要放进 64 位整数。

各语言的写法

Pythonf"{x:.2f}" — round(2.5) 是 2(向偶数)。
int(x) 是截断。
整数没有大小限制,不会溢出
JavaScriptx.toFixed(2) — Math.round 向 +∞(2.5 → 3,-2.5 → -2),截断用 Math.trunc(x)。
只有一种数(双精度浮点数),整数只在 2^53(Number.MAX_SAFE_INTEGER)以内精确,更大的用 BigInt(123n)
TypeScript和 JavaScript 一样
JavaString.format("%.2f", x) — Math.round 向 +∞(-2.5 → -2)。
int 溢出时不报错,直接绕回 -2147483648。
大数用 long
C#x.ToString("F2") — Math.Round(2.5) 是 2(向偶数),可以改用 Math.Round(x, MidpointRounding.AwayFromZero)。
int 溢出会绕回,放在 checked 里则抛出 OverflowException
C++std::fixed << std::setprecision(2) — 写一次就一直生效,之后这个流上的浮点输出都会受影响。
round 远离 0(-2.5 → -3)。
有符号整数溢出是未定义行为,大数用 long long
Cprintf("%.2f", x) — 舍入和溢出与 C++ 相同。
long 在 Windows 上只有 32 位,所以 long long 更稳妥
Craykon字符.保留小数(x, 2) — 数学.四舍五入 远离 0(-2.5 → -3)。
数学.商(x, 1) 是截断,数学.向下取整(x) 是向下取整。
只有一种数,整数只在 2^53 以内精确,也没有 BigInt

常见错误

两个整数相除,小数没了Java · C# · C · C++

int a = 7, b = 2;
double r = a / b;

→ r 是 3.0:先做了整数除法,然后才变成浮点数

先把一边转成浮点数:(double)a / b。

int 溢出了Java · C# · C · C++

int n = 2147483647;
n = n + 1;

→ Java 和 C# 不报错,得到 -2147483648。C/C++ 中是未定义行为

大数放进 long(C/C++ 用 long long)。

Craykon 输出看起来是 0.3Craykon

控制台.输出(0.1 + 0.2);
控制台.输出(0.1 + 0.2 == 0.3);

→ 0.3 和 false — 只是在小数点后第九位四舍五入后显示,值本身是 0.30000000000000004

不要相信显示的值,要看差是否足够小。

各语言的差异
不要用 == 比较两个浮点数。
应该看差的绝对值是否足够小(abs(a - b) < 1e-9);像金额这种必须精确的值,就乘以 100,用整数(分)来处理。

练习题