float和double的爱恨情仇
float和double的爱恨情仇
| 特性 | float (单精度) | double (双精度) |
|---|---|---|
| 内存占用 | 4 字节 (32 bit) | 8 字节 (64 bit) |
| 有效数字 | 约 6 ~ 7 位 | 约 15 ~ 16 位 |
| 精度评价 | 够用,但容易有误差 | 非常准,默认推荐 |
| C语言写法 | float x = 1.23f; |
double x = 1.23; |
| 输入/输出 | %f |
%lf (输入时必须用, 输出可用%f) |
一、为什么是“约 6~7 位”?
C 语言很严谨,但“二进制”和“十进制”无法完美互译。
底层逻辑:
float在计算机里有 23 个二进制位 用来存有效数字。- 2 的 23 次方是
8,388,608。 - 你看,
838万这是一个 7 位数。这意味着float能把大部分 7 位以内的整数分得清清楚楚。 - 但是,一旦超过这个范围,或者涉及到某些特定的小数,二进制位就不够用了。
比喻: 这就好比用“美元”去换“人民币”。
- 你有 1.00 美元。
- 汇率算下来可能是 7.234567… 元。
- 你能精确对应的只有前几位,后面总是会有换算产生的“毛边”。
所以,教科书严谨地说“约 6~7 位”,意思就是:“前 6 位十进制数我是敢打包票准的,第 7 位能不能准看运气,第 8 位以后就是瞎蒙了。”
二、int,float,double 的大小比较
1. 比“物理体重” (占用内存大小)
这是指它们在计算机内存条里占了几个小格子(字节)。
- int:通常 4 字节 (32 bit)
- float:4 字节 (32 bit)
- double:8 字节 (64 bit)
结论: double > float = int
(注:float 和 int 虽然类型不同,但在现代 PC 上占用的空间通常是一样大的!)
2. 比“肚量” (数值范围)
这是指它们最大能存多大的数。这里有巨大的反转!
- int (4字节):
- 最大约 21 亿 ($2 \times 10^9$)
- 超过这个数就会溢出(变成负数)。
- float (4字节):
- 最大约 $3.4 \times 10^{38}$
- 这是 34 后面跟 37 个零!是一个天文数字。
- double (8字节):
- 最大约 $1.7 \times 10^{308}$
- 这是一个宇宙级别的数字,宇宙原子总数才 $10^{80}$ 左右。
结论:
double>>>float>>>int
3. 灵魂发问:为什么 int 和 float 都是 4 字节,float 却能装那么多?
这就像同样大的两个箱子(4字节):
- int 箱子: 实打实地把每个豆子都装进去。装满了就是 21 亿颗。
- float 箱子: 它耍了小聪明(科学计数法)。它只装“大概数量”和“后面有几个零”。
- 比如存
2000...000,它只存2和指数。
- 比如存
代价是什么?
- int 虽然存的小,但是一颗都不差,精确度 100%。
- float 虽然存的大,但是有空隙,精度低
三、float精度低是什么意思?
想象你有两把尺子:
int (整数) 的尺子:
这是一把极其精密的短尺子。上面的刻度是 1, 2, 3, 4… 每一个整数刻度都有。
- 优点: 只要在范围内,多少就是多少,绝不会错。
- 缺点: 尺子比较短(只能量到21亿)。
float (浮点数) 的尺子:
这是一把可以伸缩的橡皮尺子。
- 当你量小物体的时(数字小): 橡皮筋没拉开,刻度很密,可以精确到
0.000001。 - 当你量大物体时(数字大): 橡皮筋被狠狠拉长了!
关键点来了:
当数字非常大时(比如 $10^{20}$),尺子上的刻度被拉得非常稀疏。刻度之间出现了巨大的空隙!
可能上一个刻度是 100000000000000000000。
下一个刻度直接就是 100000000000000000005。
中间的 …001, …002, …003 哪里去了?
因为尺子刻度不够密,这些数存不进去! 它们掉进了“精度的缝隙”里。如果你非要存 …003,它只能被迫吸附到最近的刻度 …000 或者 …005 上。
- 当你量小物体的时(数字小): 橡皮筋没拉开,刻度很密,可以精确到
代码实测(震撼一下):
这叫“大数吃小数”现象。
1 | |
如果运行了,你会发现:
在 float 的世界里,2000万 + 1 依然等于 2000万。
那个 1 因为太小,掉进大数刻度的缝隙里,直接被忽略了。
这就是“精度低”的含义:
虽然我也能存大数,但我存了大数,就看不清小数了。我想存“银河系的直径(大数)+ 1毫米(小数)”,float 会直接把那 1 毫米扔掉,因为它记不住那么精细的差别。
而 double 呢?
double 的尺子刻度比 float 密得多。在这个例子里,用 double 就能清晰地分辨出 20000000 和 20000001。
同理,double在非常大的时候也会出现精度问题~
四、给float赋值的时候一定要加f吗?
语法上不是必须的,但为了“资深”和“性能”,强烈建议加。
原因: 在 C 语言里,任何写在代码里的“裸”小数(比如 123.456),默认被当作 double(双精度) 处理。
如果你写 float x = 123.456;:
- 编译器先在内存里生成一个巨大的 8 字节
double值123.456。 - 然后发现你要把它塞进一个 4 字节的
float变量里。 - 编译器不得不执行一次“截断”操作(砍掉一半精度),再赋值过去。
如果你写 float x = 123.456f;:
- 编译器一看有个
f,直接生成一个 4 字节的float值。 - 直接塞进变量,干净利落。
资深程序员怎么看?
- 不加
f可能会触发编译警告(Warning: conversion from ‘double’ to ‘float’ may alter its value)。 - 消灭 Warning 是资深程序员的洁癖。所以,加上
f是好习惯。
五、printf打印出来是几位?
不管你的变量在肚子里(内存里)有多精确,也不管你是 float 还是 double,只要你使用了 %f(或 %lf)且不指定位数,printf 就会机械地执行命令:“给我凑够 6 位小数打印出来!”
番外
%g是 C 语言中最“聪明”、最“懂事”的打印格式。你可以把它理解为 General(通用) 格式,或者 Smart(智能) 格式。它的核心逻辑是:“怎么短,怎么打印;怎么方便阅读,怎么打印。”
它自动在
%f(普通小数)和%e(科学计数法)之间反复横跳,只为你提供最清晰的显示结果。
1.
%g的两大神技神技一:自动去尾(强迫症福音)
这是大家最爱它的地方。
- 如果你用
%f打印1.5,它非要打印1.500000。- 如果你用
%g打印1.5,它就打印1.5。它会自动把后面没用的0全删掉,小数点如果没用也会删掉。神技二:自动切换科学计数法
当数字特别大或者特别小(比如小于 0.0001)时,
%f会变得很丑或者显示不出精度,这时候%g会自动变身。举例对比:
你的数字 (double) %f (死板模式) %g (智能模式) 评价 12.5 12.50000012.5%g帮你把废 0 删了,清爽!100.0 100.000000100%g连小数点都帮你省了。0.00000123 0.0000011.23e-06%f精度丢了,%g自动切成科学计数法,保住了精度。123456789000 123456789000.0000001.23457e+11数字太大, %g觉得你数不过来 0,帮你切成科学计数法。
2. 什么时候 不要 用
%g?虽然它很聪明,但有时候我们需要“笨”一点:
❌ 场景一:需要对齐打印报表时
如果你要打印一列账单或数据表:
Plaintext
1
2
312.5
3.14159
100用
%g打印出来的长度参差不齐,小数点忽左忽右,非常难看。 这种时候必须用%f(甚至%.2f),强制大家都是两位小数,整整齐齐。❌ 场景二:金钱相关
显示金额时,通常习惯强制保留两位小数(比如
¥10.00),而不是¥10。这时候用%.2f更符合人类习惯。
总结
%f:老实人。由于太死板(由于默认 6 位),适合做排版对齐。%g:聪明人。适合看数据、调试、日常显示,因为它会自动根据数字的大小“看人下菜碟”,怎么舒服怎么来。