整数:补码的世界
Go 的 int32 能表示 −2,147,483,648 到 2,147,483,647。为什么负数比正数多一个?因为 补码 的设计——用最高位作符号位,0 表示正数,1 表示负数,但 10000...0 这个特殊值被分给了最小负数,所以负数的绝对值范围比正数大 1。
package main
import (
"fmt"
"math"
)
func main() {
// 整数溢出:不报错,只截断
var x int32 = math.MaxInt32 // 2147483647
x++
fmt.Println(x) // -2147483648 ← 溢出回绕
// 补码:负数的二进制表示
var n int8 = -1
fmt.Printf("%08b\n", uint8(n)) // 11111111
// 补码规律:正数按位取反+1 = 其负数
// -1 的补码: 00000001 → 取反 11111110 → +1 = 11111111 ✓
}
浮点数:IEEE 754
浮点数本质是二进制科学计数法:(-1)^s × 1.fraction × 2^(exponent-127)。float32 用 1 位符号 + 8 位指数 + 23 位尾数,能表示的实数是稀疏分布的——越靠近 0 越密集,越大越稀疏。
package main
import (
"fmt"
"math"
"math/bits"
_ "unsafe"
)
func float64Bits(f float64) uint64 {
return math.Float64bits(f)
}
func main() {
// 0.1 在二进制里是无限循环小数
fmt.Printf("0.1 bits: %064b\n", float64Bits(0.1))
fmt.Println(0.1 + 0.2) // 0.30000000000000004
fmt.Println(0.1 + 0.2 == 0.3) // false
// 正确比较浮点数:用误差范围
const eps = 1e-9
a, b := 0.1+0.2, 0.3
fmt.Println(math.Abs(a-b) < eps) // true
// 特殊值
inf := math.Inf(1)
nan := math.NaN()
fmt.Println(inf, nan, math.IsNaN(nan))
_ = bits.OnesCount64(0) // 引入 bits 包
}
精度陷阱:为什么不用 float 存钱
package main
import (
"fmt"
"math/big"
)
func main() {
// 错误做法:浮点累加
total := 0.0
for i := 0; i < 1000; i++ {
total += 0.1
}
fmt.Println(total) // 99.9999999999986 ← 差了 0.0000000000014
// 正确做法 1:用整数(分/厘作单位)
totalCents := 0
for i := 0; i < 1000; i++ {
totalCents += 10 // 0.1 元 = 10 分
}
fmt.Println(float64(totalCents) / 100) // 100
// 正确做法 2:math/big.Rat 精确有理数
rat := new(big.Rat)
tenth := new(big.Rat).SetFrac(big.NewInt(1), big.NewInt(10))
for i := 0; i < 1000; i++ {
rat.Add(rat, tenth)
}
fmt.Println(rat.FloatString(2)) // 100.00
}
数的范围速查
| 类型 | 位数 | 最小值 | 最大值 | 精度 |
|---|---|---|---|---|
| int8 | 8 | -128 | 127 | 整数,精确 |
| int32 | 32 | -2,147,483,648 | 2,147,483,647 | 整数,精确 |
| int64 | 64 | -9.2×10¹⁸ | 9.2×10¹⁸ | 整数,精确 |
| float32 | 32 | 1.2×10⁻³⁸ | 3.4×10³⁸ | 约 7 位有效数字 |
| float64 | 64 | 2.2×10⁻³⁰⁸ | 1.8×10³⁰⁸ | 约 15 位有效数字 |
口诀金融用整数,科学用 float64,天文/加密用 math/big。
✓推荐做法
- 金融金额用整数(分为单位)或
shopspring/decimal - 浮点比较用
math.Abs(a-b) < epsilon - 循环累加时考虑 Kahan 求和算法减少误差
✗不推荐
- 用
float64存储货币金额 - 用
==比较两个浮点数 - 忽略 int 溢出——尤其是 32 位平台上的 int
⚠常见误区
- int 在 64 位系统是 64 位,在 32 位系统是 32 位——跨平台代码要用 int64
判断标准:能解释 0.1+0.2≠0.3 是因为尾数截断 → 掌握本章。
数学本质:数系的层次
数学上,自然数 ⊂ 整数 ⊂ 有理数 ⊂ 实数 ⊂ 复数。计算机实现了这个层次的有限离散近似:uint 近似自然数,int 近似整数,float64 近似实数(但只是稀疏采样)。理解这个层次关系,你就能预测任何类型转换的行为。
计算机里没有实数,只有有限精度的近似。工程师的职责是控制这个近似的误差。
— 数值分析教材