第 7 章:数学运算——math、math/big、math/bits、math/cmplx
第 7 章:数学运算——math、math/big、math/bits、math/cmplx
数学是宇宙的语言,而 Go 的 math 包就是你和这门语言对话的翻译官。从简单的加减乘除到让人头秃的复数运算,Go 标准库提供了全套工具。本章将带你领略 Go 数学工具的魅力,顺便踩踩那些让人欲仙欲死的精度坑。
7.1 math 包解决什么问题
math 包是 Go 标准库中处理浮点数数学运算的核心包。它可不是什么"玩具库",而是实打实的工业级实现——基于 IEEE 754 标准,底层直接调用 CPU 浮点指令。换句话说,当你用 math 包算 sin(π) 的时候,实际上是 CPU 在给你打工。
7.1.1 基本的数学计算
加减乘除?不存在的。math 包专注的是"高级"运算——三角函数、对数、开方、幂运算。如果你只是想算 1+1,请左转 basic constants,请勿浪费 math 包的才华。
7.1.2 三角函数
math 包提供了完整的三角函数家族:Sin、 Cos、 Tan 及其反函数 Asin、 Acos、 Atan。还记得高数课本上那些令人窒息的公式吗?math 包帮你实现了。
7.1.3 对数
自然对数 Log、底数为 10 的 Log10、还有贴心的高精度版本 Log1p(计算 log(1+x),避免 x 接近 0 时的精度损失)。
7.1.4 开方
Sqrt(平方根)是使用最频繁的,毕竟勾股定理谁不爱。Cbrt(立方根)虽然用得少,但关键时刻也能救命。
7.1.5 幂运算
Pow(x, y) 计算 x 的 y 次方。看起来简单,但当你知道它内部做了多少优化的时候,你会感激 math 包的存在。
7.1.6 浮点数特殊值(无穷大、NaN)
math 包定义了三个"幽灵"值:正无穷大 +Inf、负无穷大 -Inf,以及"不是数字" NaN。它们是 IEEE 754 标准的一部分,理解它们是写出健壮数学代码的前提。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package main
import (
"fmt"
"math"
)
func main() {
// 三角函数:sin(π/2) = 1
fmt.Println(math.Sin(math.Pi / 2)) // 1
// 对数
fmt.Println(math.Log(math.E)) // 1
fmt.Println(math.Log10(100)) // 2
// 开方
fmt.Println(math.Sqrt(16)) // 4
fmt.Println(math.Cbrt(27)) // 3
// 幂运算
fmt.Println(math.Pow(2, 10)) // 1024
// 特殊值
fmt.Println(math.Inf(1)) // +Inf
fmt.Println(math.NaN()) // NaN
}
|
7.2 math 核心原理
IEEE 754 浮点数的固有限制
math 包基于 IEEE 754 标准实现浮点数运算。这意味着:浮点数的精度是有限的!你以为的 0.1 + 0.2 = 0.3 在计算机眼里可能是 0.30000000000000004。这不是 bug,是 feature(也是噩梦)。理解这一点,你就能理解为什么比较两个浮点数要用精度阈值,而不是直接用 ==。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package main
import (
"fmt"
"math"
)
func main() {
// 著名的 0.1 + 0.2 问题
a, b := 0.1, 0.2
sum := a + b
fmt.Printf("0.1 + 0.2 = %.20f\n", sum) // 0.30000000000000004441
// 正确比较方式:使用足够小的误差阈值
equal := math.Abs(sum-0.3) < 1e-10
fmt.Println("是否等于 0.3:", equal) // true
}
|
7.3 数学常数
math 包贴心地为你准备了一堆数学常数,都是预计算好的float64常量,省去你临时抱佛脚查数学书的麻烦。
7.3.1 math.Pi(π≈3.14159…)
圆周率 π,宇宙最重要的常数之一。计算圆面积?先问问 math.Pi。
7.3.2 math.E(e≈2.71828…)
自然常数 e,复利公式的核心人物。计算自然指数?找它准没错。
7.3.3 math.Sqrt2(√2)
√2 ≈ 1.41421356237,对角线长度计算必备。
7.3.4 math.Phi(黄金比例φ≈1.618…)
φ = (1+√5)/2,自然界最神秘的比例,建筑学和艺术家的最爱。
7.3.5 math.SqrtE
√e ≈ 1.64872127070,自然常数 e 的平方根。
7.3.6 math.Ln2
ln(2) ≈ 0.69314718055,自然对数的经典值。
7.3.7 math.Ln10
ln(10) ≈ 2.30258509299,用于对数换底。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("math.Pi = %.15f\n", math.Pi) // 3.141592653589793
fmt.Printf("math.E = %.15f\n", math.E) // 2.718281828459045
fmt.Printf("math.Sqrt2 = %.15f\n", math.Sqrt2) // 1.414213562373095
fmt.Printf("math.Phi = %.15f\n", math.Phi) // 1.618033988749895
fmt.Printf("math.SqrtE = %.15f\n", math.SqrtE) // 1.648721270700128
fmt.Printf("math.Ln2 = %.15f\n", math.Ln2) // 0.693147180559945
fmt.Printf("math.Ln10 = %.15f\n", math.Ln10) // 2.302585092994046
}
|
7.4 浮点数特殊值
浮点数的世界不只有普通数字,还有三个"幽灵":正无穷、负无穷、NaN。
7.4.1 math.Inf(sign)
返回无穷大。sign > 0 返回正无穷,sign < 0 返回负无穷,sign == 0 你猜?
7.4.2 math.NaN()
“Not a Number”,不是数字的数字。它是个"渣男"——既不等于自己,用 == 比较永远返回 false,连 math.NaN() == math.NaN() 都是 false!
7.4.3 math.IsInf()
检查一个浮点数是否是无穷大。还能顺便判断是正还是负无穷。
7.4.4 math.IsNaN()
检查一个浮点数是否是 NaN。这个函数终于能正确判断 NaN 了,因为 NaN != NaN。
7.4.5 正/负无穷大
无穷大不是终点,而是开始。当你除以一个极小的数时,它就会蹦出来。
7.4.6 不是数字
NaN 通常在 0.0/0.0 这样的"无效运算"中诞生。它代表了数学上未定义的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import (
"fmt"
"math"
)
func main() {
// 创建特殊值
posInf := math.Inf(1) // 正无穷
negInf := math.Inf(-1) // 负无穷
nan := math.NaN() // NaN
fmt.Println("正无穷:", posInf) // +Inf
fmt.Println("负无穷:", negInf) // -Inf
fmt.Println("NaN:", nan) // NaN
// 检测特殊值
fmt.Println("IsInf(posInf):", math.IsInf(posInf, 1)) // true
fmt.Println("IsNaN(nan):", math.IsNaN(nan)) // true
// NaN 的奇葩特性
fmt.Println("NaN == NaN:", nan == nan) // false(永远为 false!)
// 无穷大的运算
fmt.Println("1.0 / 0.0 =", 1.0/0.0) // +Inf
fmt.Println("-1.0 / 0.0 =", -1.0/0.0) // -Inf
fmt.Println("math.MaxFloat64 * 2 =", math.MaxFloat64*2) // +Inf
}
|
7.5 Inf 和 NaN 的产生
理解这两个"幽灵值"是怎么诞生的,是写出健壮代码的关键。
7.5.1 0.0/0.0 = NaN,1.0/0.0 = Inf
这些看似简单的运算,在浮点数世界里有着精确定义:
- 正数 / 0 = +Inf
- 负数 / 0 = -Inf
- 0 / 0 = NaN
- Inf - Inf = NaN
- Inf / Inf = NaN
- sqrt(负数) = NaN
7.5.2 了解它们的产生场景,才能调试相关 bug
当你的程序突然输出 NaN 或 Inf 时,不要慌张。顺着运算链往上找,看看是哪一步"越界"了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| package main
import (
"fmt"
"math"
)
func main() {
// NaN 的产生场景
nanOps := []float64{
0.0 / 0.0, // 0/0
math.Inf(0) - math.Inf(0), // Inf - Inf
math.Inf(0) / math.Inf(0), // Inf / Inf
math.Sqrt(-1), // 负数开方
math.Log(-1), // 负数取对数
}
for i, v := range nanOps {
fmt.Printf("NaN 场景 %d: %v, IsNaN: %v\n", i+1, v, math.IsNaN(v))
}
// Inf 的产生场景
infOps := []float64{
1.0 / 0.0, // 正数/0
-math.MaxFloat64 * 2, // 溢出
math.Log(0), // log(0) = -Inf
math.Exp(1000), // e^1000 溢出
}
for i, v := range infOps {
fmt.Printf("Inf 场景 %d: %v, IsInf: %v\n", i+1, v, math.IsInf(v, 0))
}
}
|
7.6 浮点数精度问题
这是每个 Go 程序员(或任何程序员)都必须面对的残酷现实。
7.6.1 0.1 + 0.2 ≠ 0.3
0.1 + 0.2 在浮点数里存储的是近似值,所以结果不等于精确的 0.3。这不是 bug,是 IEEE 754 的固有特性。
7.6.2 IEEE 754 双精度浮点数的尾数只有 52 位
float64(64位双精度)的组成:1位符号 + 11位指数 + 52位尾数。尾数决定了精度,所以 0.1 这样的十进制小数在二进制里是无限循环的,只能截断存储。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import (
"fmt"
"math"
)
func main() {
// 著名的 0.1 + 0.2 问题
a, b := 0.1, 0.2
c := 0.3
fmt.Printf("a = %.20f\n", a) // 0.10000000000000000555
fmt.Printf("b = %.20f\n", b) // 0.20000000000000001110
fmt.Printf("c = %.20f\n", c) // 0.29999999999999998890
sum := a + b
fmt.Printf("a + b = %.20f\n", sum) // 0.30000000000000004441
// 比较方式
fmt.Println("\n=== 正确的比较方式 ===")
fmt.Println("a + b == c:", sum == c) // false(不要用 ==)
fmt.Println("math.Abs(a+b-c) < 1e-10:", math.Abs(sum-c) < 1e-10) // true
// 大数吃小数
big, small := 1e10, 1e-10
fmt.Printf("\nbig + small = %.10f\n", big+small)
fmt.Println("small 被大数吃掉了!精度丢失是真实的。")
}
|
7.7 绝对值与取整
math 包提供了全套取整函数,每个都有自己的"性格"。
7.7.1 Abs(绝对值)
返回浮点数的绝对值。正数还是正数,负数变正数,0 还是 0。
7.7.2 Ceil(向上取整)
“天花板取整”——找到大于等于自己的最小整数。3.1 变 4,-3.1 变 -3。
7.7.3 Floor(向下取整)
“地板取整”——找到小于等于自己的最大整数。3.1 变 3,-3.1 变 -4。
7.7.4 Trunc(向零取整)
“截断取整”——直接砍掉小数部分。3.1 变 3,-3.1 变 -3。向零看齐,不偏不倚。
7.7.5 Round(四舍五入)
标准的四舍五入。3.5 变 4,-3.5 变 -3。注意 Go 1.20 之前的 Round 行为有点怪,现在统一了。
7.7.6 RoundToEven(银行家舍入)
“银行家舍入”——奇数舍入,偶数舍入。3.5 变 4,2.5 变 2。这种方式在金融计算中可以减少系统性偏差。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| package main
import (
"fmt"
"math"
)
func main() {
x := 3.7
y := -3.7
fmt.Println("=== Ceil(向上取整)===")
fmt.Printf("Ceil(%.1f) = %.1f\n", x, math.Ceil(x)) // 4.0
fmt.Printf("Ceil(%.1f) = %.1f\n", y, math.Ceil(y)) // -3.0
fmt.Println("\n=== Floor(向下取整)===")
fmt.Printf("Floor(%.1f) = %.1f\n", x, math.Floor(x)) // 3.0
fmt.Printf("Floor(%.1f) = %.1f\n", y, math.Floor(y)) // -4.0
fmt.Println("\n=== Trunc(向零取整)===")
fmt.Printf("Trunc(%.1f) = %.1f\n", x, math.Trunc(x)) // 3.0
fmt.Printf("Trunc(%.1f) = %.1f\n", y, math.Trunc(y)) // -3.0
fmt.Println("\n=== Round(四舍五入)===")
fmt.Printf("Round(3.5) = %.1f\n", math.Round(3.5)) // 4.0
fmt.Printf("Round(2.5) = %.1f\n", math.Round(2.5)) // 3.0
fmt.Printf("Round(-3.5) = %.1f\n", math.Round(-3.5)) // -3.0
fmt.Println("\n=== RoundToEven(银行家舍入)===")
fmt.Printf("RoundToEven(3.5) = %.1f\n", math.RoundToEven(3.5)) // 4.0
fmt.Printf("RoundToEven(2.5) = %.1f\n", math.RoundToEven(2.5)) // 2.0(银行家舍入)
fmt.Printf("RoundToEven(1.5) = %.1f\n", math.RoundToEven(1.5)) // 2.0
fmt.Println("\n=== Abs(绝对值)===")
fmt.Printf("Abs(%.1f) = %.1f\n", x, math.Abs(x)) // 3.7
fmt.Printf("Abs(%.1f) = %.1f\n", y, math.Abs(y)) // 3.7
}
|
7.8 三角函数
三角函数是数学里的"老熟人",在图形计算、物理模拟、信号处理等领域广泛应用。
7.8.1 Sin
正弦函数,输入弧度,返回 [-1, 1] 之间的值。
7.8.2 Cos
余弦函数,和正弦是"好基友",总是形影不离。
7.8.3 Tan
正切函数,cos 为零时结果是无穷大。
7.8.4 Asin
反正弦函数,Sin 的反函数。输入 [-1, 1],返回弧度。
7.8.5 Acos
反余弦函数,返回 [0, π] 之间的弧度。
7.8.6 Atan
反正切函数,返回 (-π/2, π/2) 之间的弧度。
7.8.7 Atan2
两个参数版本的反正切。atan(y/x) 的问题在于当 x 为负或 x=0 时会出错,atan2(y, x) 完美解决了这个问题,能正确计算任意象限的角度。
7.8.8 输入输出都是弧度制,不是角度制
重要的事情说三遍:math 包用的是弧度!弧度!弧度!90° = π/2 弧度,180° = π 弧度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| package main
import (
"fmt"
"math"
)
func main() {
// 角度转弧度
deg90 := 90.0
rad := deg90 * math.Pi / 180
fmt.Printf("%f° = %f 弧度\n", deg90, rad) // 1.570796 弧度
fmt.Println("\n=== 三角函数 ===")
// Sin, Cos, Tan(输入 π/4 弧度 = 45°)
angle := math.Pi / 4
fmt.Printf("Sin(π/4) = %.6f\n", math.Sin(angle)) // 0.707107
fmt.Printf("Cos(π/4) = %.6f\n", math.Cos(angle)) // 0.707107
fmt.Printf("Tan(π/4) = %.6f\n", math.Tan(angle)) // 1.000000
fmt.Println("\n=== 反三角函数 ===")
// Asin, Acos, Atan
fmt.Printf("Asin(0.5) = %.6f 弧度 (%.2f°)\n", math.Asin(0.5), math.Asin(0.5)*180/math.Pi)
fmt.Printf("Acos(0.5) = %.6f 弧度 (%.2f°)\n", math.Acos(0.5), math.Acos(0.5)*180/math.Pi)
fmt.Printf("Atan(1.0) = %.6f 弧度 (%.2f°)\n", math.Atan(1.0), math.Atan(1.0)*180/math.Pi)
fmt.Println("\n=== Atan2(推荐使用)===")
// Atan2 能正确处理所有象限
fmt.Printf("Atan2(1, 1) = %.6f 弧度 (%.2f°)\n", math.Atan2(1, 1), math.Atan2(1, 1)*180/math.Pi) // 45°
fmt.Printf("Atan2(1, -1) = %.6f 弧度 (%.2f°)\n", math.Atan2(1, -1), math.Atan2(1, -1)*180/math.Pi) // 135°
fmt.Printf("Atan2(-1, -1) = %.6f 弧度 (%.2f°)\n", math.Atan2(-1, -1), math.Atan2(-1, -1)*180/math.Pi) // -135°
fmt.Printf("Atan2(-1, 1) = %.6f 弧度 (%.2f°)\n", math.Atan2(-1, 1), math.Atan2(-1, 1)*180/math.Pi) // -45°
// 典型应用:计算向量角度
dx, dy := 3.0, 4.0
angleVec := math.Atan2(dy, dx)
fmt.Printf("向量(%.1f, %.1f) 与 X 轴夹角 = %.2f°\n", dx, dy, angleVec*180/math.Pi)
}
|
7.9 双曲函数
双曲函数是指数函数家族的"近亲",在神经网络、信号处理、热传导等领域有广泛应用。
7.9.1 Sinh
双曲正弦:sinh(x) = (e^x - e^(-x)) / 2
7.9.2 Cosh
双曲余弦:cosh(x) = (e^x + e^(-x)) / 2,图像是悬链线形状。
7.9.3 Tanh
双曲正切:tanh(x) = sinh(x) / cosh(x),在神经网络中是著名的激活函数。
7.9.4 工程计算(神经网络、信号处理)中常用
tanh 和 sigmoid 是早期神经网络的激活函数宠儿。虽然现在 ReLU 更流行,但双曲函数在 LSTM、GRU 等循环神经网络中仍是核心组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import (
"fmt"
"math"
)
func main() {
x := 1.0
fmt.Println("=== 双曲函数 ===")
fmt.Printf("Sinh(%.1f) = %.6f\n", x, math.Sinh(x))
fmt.Printf("Cosh(%.1f) = %.6f\n", x, math.Cosh(x))
fmt.Printf("Tanh(%.1f) = %.6f\n", x, math.Tanh(x))
// 验证双曲函数恒等式
fmt.Println("\n=== 验证恒等式 cosh²(x) - sinh²(x) = 1 ===")
sinhX := math.Sinh(x)
coshX := math.Cosh(x)
identity := coshX*coshX - sinhX*sinhX
fmt.Printf("cosh²(%.1f) - sinh²(%.1f) = %.6f (理论上应该等于 1)\n", x, x, identity)
// tanh 在神经网络中常用,因为输出在 (-1, 1) 之间且梯度衰减慢
fmt.Println("\n=== Tanh 在神经网络中 ===")
activations := []float64{-2, -1, 0, 1, 2}
for _, a := range activations {
fmt.Printf("Tanh(%.1f) = %.6f\n", a, math.Tanh(a))
}
}
|
7.10 对数与指数
对数和指数是数学中的"时间旅行者",能压缩大数也能膨胀小 数。
7.10.1 Log(自然对数)
底数为 e 的对数,ln(x)。数学中的"自然"在这里体现得淋漓尽致。
7.10.2 Log1p(log(1+x),精度更高)
当 x 很小时,直接算 log(1+x) 会有精度损失。Log1p 会先加 1 再取对数,精度更高。这不是吹牛,是数学上的最优解。
7.10.3 Log10(10为底)
以 10 为底的对数,用于 pH 值计算、分贝转换等场景。
7.10.4 Exp
指数函数 e^x,和 Log 是一对互逆运算。
7.10.5 Expm1(e^x-1,精度更高)
当 x 很小时,Exp(x)-1 会有精度损失。Expm1 直接算 e^x-1,精度更高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("=== 自然对数 Log ===")
fmt.Printf("Log(1) = %.6f (ln(1) = 0)\n", math.Log(1))
fmt.Printf("Log(math.E) = %.6f (ln(e) = 1)\n", math.Log(math.E))
fmt.Println("\n=== Log1p 高精度版本 ===")
// 当 x 很小时,Log1p 比 Log(1+x) 更精确
x := 1e-10
fmt.Printf("x = %.10f\n", x)
fmt.Printf("Log(1+x) = %.20f\n", math.Log(1+x))
fmt.Printf("Log1p(x) = %.20f\n", math.Log1p(x))
fmt.Printf("真实值(约) = %.20f\n", x) // ln(1+x) ≈ x 当 x 很小时
fmt.Println("\n=== Log10(10为底) ===")
fmt.Printf("Log10(100) = %.1f\n", math.Log10(100)) // 2
fmt.Printf("Log10(1000) = %.1f\n", math.Log10(1000)) // 3
fmt.Printf("Log10(1e-6) = %.1f\n", math.Log10(1e-6)) // -6
fmt.Println("\n=== Exp(指数) ===")
fmt.Printf("Exp(0) = %.1f\n", math.Exp(0)) // 1
fmt.Printf("Exp(1) = %.6f (≈ e)\n", math.Exp(1))
fmt.Println("\n=== Expm1 高精度版本 ===")
smallX := 1e-15
fmt.Printf("x = %.20f\n", smallX)
fmt.Printf("Exp(x)-1 = %.20f\n", math.Exp(smallX)-1)
fmt.Printf("Expm1(x) = %.20f\n", math.Expm1(smallX))
fmt.Printf("真实值(约) = %.20f\n", smallX)
fmt.Println("\n=== 对数换底公式 ===")
// log_a(b) = ln(b) / ln(a)
base := 2.0
value := 8.0
logBase := math.Log(value) / math.Log(base)
fmt.Printf("log₂(8) = %.1f\n", logBase) // 3
}
|
7.11 幂与开方
幂运算和开方是inverse关系,math包贴心地提供了全套工具。
7.11.1 Pow(x^y)
计算 x 的 y 次方。x^2 是平方,x^0.5 是开方,x^(-1) 是倒数。
7.11.2 Sqrt(√x)
平方根。接受任何非负数,负数会返回 NaN。
7.11.3 Cbrt(∛x)
立方根。接受所有实数(包括负数),不像 sqrt 那样矫情。
7.11.4 Pow10(10^n)
计算 10 的 n 次方,比 Pow(10, n) 更快更精确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("=== Pow(幂运算) ===")
fmt.Printf("Pow(2, 10) = %.0f (2¹⁰ = 1024)\n", math.Pow(2, 10))
fmt.Printf("Pow(9, 0.5) = %.1f (√9)\n", math.Pow(9, 0.5))
fmt.Printf("Pow(2, -1) = %.1f (2⁻¹ = 0.5)\n", math.Pow(2, -1))
fmt.Printf("Pow(-2, 3) = %.1f ((-2)³ = -8)\n", math.Pow(-2, 3))
fmt.Println("\n=== Sqrt(平方根) ===")
fmt.Printf("Sqrt(16) = %.1f\n", math.Sqrt(16))
fmt.Printf("Sqrt(2) = %.10f\n", math.Sqrt(2))
fmt.Printf("Sqrt(-1) = %.1f (NaN)\n", math.Sqrt(-1))
fmt.Println("\n=== Cbrt(立方根) ===")
fmt.Printf("Cbrt(27) = %.1f (∛27)\n", math.Cbrt(27))
fmt.Printf("Cbrt(-8) = %.1f (∛-8)\n", math.Cbrt(-8))
fmt.Printf("Cbrt(2) = %.10f (∛2)\n", math.Cbrt(2))
fmt.Println("\n=== Pow10(10的幂) ===")
fmt.Printf("Pow10(3) = %.0f (10³)\n", math.Pow10(3))
fmt.Printf("Pow10(-2) = %.2f (10⁻²)\n", math.Pow10(-2))
fmt.Printf("Pow10(15) = %.0e\n", math.Pow10(15))
fmt.Println("\n=== 勾股定理 ===")
// c = √(a² + b²)
a, b := 3.0, 4.0
c := math.Sqrt(math.Pow(a, 2) + math.Pow(b, 2))
fmt.Printf("直角三角形 a=%.1f, b=%.1f, 斜边 c=%.1f\n", a, b, c) // 5
}
|
7.12 Max、Min
找最大最小值,看似简单,实则暗藏玄机。
7.12.1 最值函数
math.Max(a, b) 和 math.Min(a, b) 接受两个 float64,返回较大/较小的那个。
7.12.2 注意 Max/Min 对 NaN 的处理
这是重点!如果参数中有 NaN,结果是 NaN。这和直觉不太一样——你可能期望忽略 NaN,但 Go 的设计是"有 NaN 就返回 NaN"。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("=== 基本用法 ===")
fmt.Printf("Max(3, 7) = %.1f\n", math.Max(3, 7))
fmt.Printf("Min(3, 7) = %.1f\n", math.Min(3, 7))
fmt.Printf("Max(-5, -2) = %.1f\n", math.Max(-5, -2))
// 注意:Max/Min 的参数顺序不影响结果
fmt.Printf("Max(1, 2, 3) 需要用其他方式\n")
fmt.Println("\n=== NaN 处理(重点!) ===")
nan := math.NaN()
fmt.Printf("Max(1.0, NaN) = %.1f\n", math.Max(1.0, nan)) // NaN
fmt.Printf("Min(NaN, 2.0) = %.1f\n", math.Min(nan, 2.0)) // NaN
fmt.Println("只要有 NaN,结果就是 NaN!")
fmt.Println("\n=== 实际应用 ===")
// 限制数值范围
value := 150.0
minVal, maxVal := 0.0, 100.0
clamped := math.Max(minVal, math.Min(maxVal, value))
fmt.Printf("将 %.1f 限制在 [%.1f, %.1f] 范围内 = %.1f\n", value, minVal, maxVal, clamped)
// 找出数组中的最大最小值(用循环)
values := []float64{3.14, 2.71, 1.41, 1.73, 0.57}
maxVal = values[0]
minVal = values[0]
for _, v := range values[1:] {
maxVal = math.Max(maxVal, v)
minVal = math.Min(minVal, v)
}
fmt.Printf("数组中最大值 = %.2f, 最小值 = %.2f\n", maxVal, minVal)
}
|
7.13 math/big 包解决什么问题
int64 的范围是 ±9×10¹⁸,超出这个范围的计算怎么办?比如计算 100!(100的阶乘)或者 2 的 1000 次方?math/big 就是来解决这个问题的——它提供了任意精度的大数运算,让你能计算"老天文学家才能算出来"的数字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import (
"fmt"
"math/big"
)
func main() {
// int64 的范围限制
var i64 int64 = 1<<63 - 1
fmt.Printf("int64 最大值: %d\n", i64)
// 试试 100!
fmt.Println("\n=== 100! (阶乘) ===")
// 用 math/big
result := new(big.Int)
result.SetString("1", 10)
for i := 2; i <= 100; i++ {
result.Mul(result, big.NewInt(int64(i)))
}
fmt.Printf("100! = %d\n", result)
fmt.Printf("位数: %d 位\n", len(result.String()))
// 2 的 1000 次方
fmt.Println("\n=== 2^1000 ===")
powResult := new(big.Int)
powResult.Exp(big.NewInt(2), big.NewInt(1000), nil)
fmt.Printf("2^1000 = %d\n", powResult)
}
|
7.14 math/big 核心原理
big 包提供了三种大数类型,各有专长。
7.14.1 big.Int(整数精确)
整数运算,永远精确。没有精度问题,没有舍入误差。整数界的"完美主义者"。
7.14.2 big.Float(浮点可设精度)
浮点数运算,但精度可以自己设定。精度越高越精确,但运算越慢、占用内存越多。这是速度与精度的权衡。
7.14.3 big.Rat(分数精确)
有理数,分子分母分别存储。1/3 就是 1/3,不会变成 0.333333…。数学老师的最爱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| package main
import (
"fmt"
"math/big"
)
func main() {
fmt.Println("=== 三种大数类型 ===")
// big.Int: 整数精确运算
i := new(big.Int)
i.SetString("123456789012345678901234567890", 10)
fmt.Printf("big.Int: %s\n", i)
// big.Float: 浮点可设精度
f := new(big.Float).SetPrec(100) // 100 位精度
f.SetString("3.14159265358979323846264338327950288419716939937510")
fmt.Printf("big.Float (100位精度): %.50f...\n", f)
// big.Rat: 分数精确
r := new(big.Rat)
r.SetString("1/3")
fmt.Printf("big.Rat: %s (精确!不是 0.333...)\n", r)
fmt.Printf("big.Rat to Float: %.20f\n", r.Float64())
// 对比:1/3 在 float64 中丢失精度
var f64 float64 = 1.0 / 3.0
fmt.Printf("float64: %.20f (精度丢失)\n", f64)
}
|
7.15 big.Int 的创建
big.Int 的创建方式有多种,最常用的是 NewInt 和 SetString。
7.15.1 NewInt
从 int64 创建一个 big.Int。简单直接。
7.15.2 Add
加法。big.Int 是immutable的,Add 返回一个新的 big.Int。
7.15.3 Sub
减法。
7.15.4 Mul
乘法。
7.15.5 Div
除法。整数除法,不保留小数部分。
7.15.6 基本算术运算,返回新对象,原对象不变
重要特性!big.Int 是 immutable 的。所有运算都返回新的 big.Int,原始对象不变。这和 strings.Builder 的模式类似。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| package main
import (
"fmt"
"math/big"
)
func main() {
// 创建 big.Int
a := big.NewInt(123)
b := big.NewInt(456)
fmt.Println("=== 基本算术运算 ===")
// 注意:所有运算都返回新的 big.Int
sum := new(big.Int).Add(a, b)
diff := new(big.Int).Sub(a, b)
prod := new(big.Int).Mul(a, b)
quot := new(big.Int).Div(a, b)
rem := new(big.Int).Rem(a, b)
fmt.Printf("%d + %d = %d\n", a, b, sum)
fmt.Printf("%d - %d = %d\n", a, b, diff)
fmt.Printf("%d × %d = %d\n", a, b, prod)
fmt.Printf("%d ÷ %d = %d, 余数 = %d\n", a, b, quot, rem)
fmt.Println("\n=== 大数运算 ===")
// 计算 Fibonacci 数列的超大值
fib := make([]*big.Int, 100)
fib[0] = big.NewInt(0)
fib[1] = big.NewInt(1)
for i := 2; i < 100; i++ {
fib[i] = new(big.Int).Add(fib[i-1], fib[i-2])
}
fmt.Printf("F(99) = %d\n", fib[99])
fmt.Printf("F(99) 的位数: %d 位\n", len(fib[99].String()))
}
|
7.16 big.Int 的比较
big.Int 的比较使用的是 Cmp 方法,而不是 ==。
7.16.1 Cmp
Compare 方法。返回值有三 种情况。
7.16.2 返回 -1
当第一个数小于第二个数时。
7.16.3 返回 0
当两个数相等时。
7.16.4 返回 1
当第一个数大于第二个数时。类似 strings.Compare。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(100)
b := big.NewInt(50)
c := big.NewInt(100)
fmt.Println("=== Cmp 比较 ===")
fmt.Printf("%d vs %d: Cmp = %d\n", a, b, a.Cmp(b)) // 1 (a > b)
fmt.Printf("%d vs %d: Cmp = %d\n", b, a, b.Cmp(a)) // -1 (b < a)
fmt.Printf("%d vs %d: Cmp = %d\n", a, c, a.Cmp(c)) // 0 (a == c)
fmt.Println("\n=== 封装成易用的比较函数 ===")
isLess := func(x, y *big.Int) bool { return x.Cmp(y) < 0 }
isGreater := func(x, y *big.Int) bool { return x.Cmp(y) > 0 }
isEqual := func(x, y *big.Int) bool { return x.Cmp(y) == 0 }
fmt.Printf("%d < %d: %v\n", b, a, isLess(b, a))
fmt.Printf("%d > %d: %v\n", a, b, isGreater(a, b))
fmt.Printf("%d == %d: %v\n", a, c, isEqual(a, c))
}
|
7.17 big.Int 的位操作
big.Int 虽然是"大数",但同样支持位运算。这在密码学和算法优化中很有用。
7.17.1 And
按位与。
7.17.2 Or
按位或。
7.17.3 Xor
按位异或。
7.17.4 Not
按位取反。
7.17.5 Lsh
左移,相当于乘以 2^n。
7.17.6 Rsh
右移,相当于除以 2^n(向下取整)。
7.17.7 位运算,用于大数算法
位运算在大数算法(如 RSA 加密)中经常用到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| package main
import (
"fmt"
"math/big"
)
func main() {
// 创建两个 big.Int 进行位运算
x := big.NewInt(0b110101) // 53 in decimal
y := big.NewInt(0b101011) // 43 in decimal
fmt.Printf("x = %d (二进制: %s)\n", x, fmt.Sprintf("%b", x.Int64()))
fmt.Printf("y = %d (二进制: %s)\n", y, fmt.Sprintf("%b", y.Int64()))
fmt.Println("\n=== 位运算 ===")
and := new(big.Int).And(x, y)
or := new(big.Int).Or(x, y)
xor := new(big.Int).Xor(x, y)
not := new(big.Int).Not(x)
fmt.Printf("x & y = %d\n", and) // 001001 = 9
fmt.Printf("x | y = %d\n", or) // 111111 = 63
fmt.Printf("x ^ y = %d\n", xor) // 110110 = 54
fmt.Printf("^x = %d\n", not) // 反码
fmt.Println("\n=== 移位运算 ===")
lsh := new(big.Int).Lsh(x, 3) // x * 2^3
rsh := new(big.Int).Rsh(x, 2) // x / 2^2
fmt.Printf("x << 3 = %d (原值 × 8)\n", lsh)
fmt.Printf("x >> 2 = %d (原值 ÷ 4)\n", rsh)
}
|
7.18 big.Int 的字符串转换
big.Int 和字符串之间的转换是大数处理的基础。
7.18.1 SetString
从字符串解析 big.Int。第二个参数指定进制(2-36)。
7.18.2 从字符串解析,支持不同进制(第二个参数指定进制)
进制支持从 2(二进制)到 36(数字+字母)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| package main
import (
"fmt"
"math/big"
)
func main() {
fmt.Println("=== 字符串解析 ===")
// 二进制
bin := new(big.Int)
bin.SetString("1010", 2)
fmt.Printf("二进制 1010 = %d\n", bin)
// 八进制
oct := new(big.Int)
oct.SetString("777", 8)
fmt.Printf("八进制 777 = %d\n", oct)
// 十六进制
hex := new(big.Int)
hex.SetString("FF", 16)
fmt.Printf("十六进制 FF = %d\n", hex)
// 36 进制(最大支持)
base36 := new(big.Int)
base36.SetString("Z", 36)
fmt.Printf("36进制 Z = %d\n", base36) // 35
fmt.Println("\n=== 常用进制转换 ===")
num := big.NewInt(255)
// Text 方法:将大整数转为指定进制的字符串
fmt.Printf("十进制: %s\n", num.Text(10))
fmt.Printf("二进制: %s\n", num.Text(2))
fmt.Printf("十六进制: %s\n", num.Text(16))
}
|
7.19 big.Float
big.Float 提供了任意精度的浮点数运算。
7.19.1 精度控制
big.Float 的精度是可配置的,通过 SetPrec 方法设置。
7.19.2 SetPrec 设置精度,精度越高结果越精确但运算越慢
精度以二进制位为单位。float64 对应 53 位精度。精度越高,计算越慢、内存占用越大。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| package main
import (
"fmt"
"math/big"
)
func main() {
fmt.Println("=== big.Float 精度控制 ===")
// 默认精度(53 位,约 16 位十进制)
f1 := new(big.Float).SetPrec(53)
f1.SetString("3.141592653589793238462643383279")
fmt.Printf("53 位精度: %.20f\n", f1)
// 高精度(100 位)
f2 := new(big.Float).SetPrec(100)
f2.SetString("3.141592653589793238462643383279502884197169399375105820974944592307")
fmt.Printf("100 位精度: %.50f\n", f2)
// 对比 float64
var f64 float64 = 3.141592653589793
fmt.Printf("float64: %.20f\n", f64)
fmt.Println("\n=== 精度影响示例 ===")
// 计算 0.1 + 0.2
bf := new(big.Float).SetPrec(100)
bf.SetString("0.1")
bf.Add(bf, new(big.Float).SetPrec(100).SetString("0.2"))
fmt.Printf("big.Float: 0.1 + 0.2 = %.20f\n", bf)
fmt.Printf("float64: 0.1 + 0.2 = %.20f\n", 0.1+0.2)
}
|
7.20 big.Float 的方法
big.Float 的算术方法与 big.Int 类似,但处理浮点数。
7.20.1 Add
浮点数加法。
7.20.2 Sub
浮点数减法。
7.20.3 Mul
浮点数乘法。
7.20.4 Div
浮点数除法。
7.20.5 Sqrt
平方根。
7.20.6 和 Int 类似,但处理浮点数
同样的 immutable 模式,同样的方法链风格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| package main
import (
"fmt"
"math/big"
)
func main() {
// 创建 big.Float
a := new(big.Float).SetPrec(100).SetString("10.5")
b := new(big.Float).SetPrec(100).SetString("3.2")
fmt.Println("=== big.Float 算术运算 ===")
sum := new(big.Float).Add(a, b)
diff := new(big.Float).Sub(a, b)
prod := new(big.Float).Mul(a, b)
quot := new(big.Float).Quo(a, b)
fmt.Printf("%.2f + %.2f = %.4f\n", a, b, sum)
fmt.Printf("%.2f - %.2f = %.4f\n", a, b, diff)
fmt.Printf("%.2f × %.2f = %.4f\n", a, b, prod)
fmt.Printf("%.2f ÷ %.2f = %.4f\n", a, b, quot)
fmt.Println("\n=== Sqrt ===")
c := new(big.Float).SetPrec(100).SetString("2")
sqrtC := new(big.Float).Sqrt(c)
fmt.Printf("√2 = %.50f\n", sqrtC)
}
|
7.21 big.Rat
big.Rat 存储精确的有理数,永远不会丢失精度。
7.21.1 有理数
分子分母分别存储,1/3 就是 1/3,不是 0.333…
7.21.2 分子分母分别存储,1/3 不会丢失精度,SetString 解析 “3/7” 这样的字符串
你可以直接用字符串 “3/7” 创建一个有理数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| package main
import (
"fmt"
"math/big"
)
func main() {
fmt.Println("=== big.Rat 有理数 ===")
// 从字符串创建
r1 := new(big.Rat).SetString("1/3")
r2 := new(big.Rat).SetString("2/7")
fmt.Printf("r1 = %s\n", r1)
fmt.Printf("r2 = %s\n", r2)
// 加法
sum := new(big.Rat).Add(r1, r2)
fmt.Printf("%s + %s = %s\n", r1, r2, sum)
// 乘法
prod := new(big.Rat).Mul(r1, r2)
fmt.Printf("%s × %s = %s\n", r1, r2, prod)
// 转为浮点数
f, _ := sum.Float64()
fmt.Printf("和的浮点值: %.10f\n", f)
fmt.Println("\n=== 对比 float64 ===")
var f1, f2 float64 = 1.0/3.0, 2.0/7.0
sumFloat := f1 + f2
prodFloat := f1 * f2
fmt.Printf("float64: 1/3 + 2/7 = %.10f\n", sumFloat)
fmt.Printf("float64: 1/3 × 2/7 = %.10f\n", prodFloat)
// 精确值应该是多少?
fmt.Println("\n精确结果 1/3 + 2/7 = 13/21 ≈ 0.619047619...")
}
|
7.22 big.Int 的进制转换
big.Int 提供了 Text 方法将大整数转换为各种进制的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| package main
import (
"fmt"
"math/big"
)
func main() {
// 创建一个很大的整数
n := new(big.Int)
// 2^100
n.Exp(big.NewInt(2), big.NewInt(100), nil)
fmt.Printf("2^100 = %d\n", n)
fmt.Printf("十进制: %s\n", n.Text(10))
fmt.Printf("十六进制: %s\n", n.Text(16))
fmt.Printf("二进制长度: %d 位\n", n.BitLen())
// 解析十六进制字符串
hexStr := "FFFFFFFFFFFFFFFF"
n2 := new(big.Int)
n2.SetString(hexStr, 16)
fmt.Printf("\n十六进制 %s = %d\n", hexStr, n2)
}
|
7.23 math/bits 包解决什么问题
math/bits 提供了 CPU 指令级别的位运算函数,是算法优化的利器。
7.23.1 位运算是算法优化的基础
位运算在底层无处不在:排序、搜索、加密、压缩……掌握 bits 包,让你的算法飞起来。
7.23.2 PopCount(统计1的个数)
统计一个数字的二进制表示中有多少个 1。这是 CPU 指令级别的操作,O(1) 复杂度。
7.23.3 Len(最高位位置)
返回最高有效位的位置。对于算法中的缩放和分区很有用。
7.23.4 RotateLeft(循环移位)
数据循环移入移出,在密码学和图像处理中常用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| package main
import (
"fmt"
"math/bits"
)
func main() {
// PopCount: 统计 1 的个数
x := 0b10101010 // 170
fmt.Printf("%d (二进制 %s) 有 %d 个 1\n", x, fmt.Sprintf("%b", x), bits.PopCount(uint(x)))
// Len: 最高有效位的位置
fmt.Printf("Len(%d) = %d (因为 %d = %s)\n", 16, bits.Len(16), 16, fmt.Sprintf("%b", 16))
// RotateLeft: 循环左移
y := uint8(0b00011111)
rotated := bits.RotateLeft8(y, 2)
fmt.Printf("RotateLeft8(%08b, 2) = %08b\n", y, rotated)
}
|
7.24 math/bits 核心原理
CPU 指令级别的位运算
math/bits 的函数直接对应 CPU 指令,O(1) 时间复杂度。PopCount 在某些 CPU 上是一条指令(POPCNT),Len 是 BSR 指令。这意味着用 bits 包比自己写循环快 10-100 倍。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| package main
import (
"fmt"
"math/bits"
"time"
)
func main() {
// 对比 bits 包 vs 循环实现
n := uint(0xFFFFFFFF)
// bits.PopCount
start := time.Now()
for i := 0; i < 1e7; i++ {
bits.PopCount(n)
}
bitsTime := time.Since(start)
// 循环实现
start = time.Now()
for i := 0; i < 1e7; i++ {
count := 0
for ; n > 0; n >>= 1 {
count += int(n & 1)
}
}
loopTime := time.Since(start)
fmt.Printf("bits.PopCount 耗时: %v\n", bitsTime)
fmt.Printf("循环实现 耗时: %v\n", loopTime)
fmt.Printf("bits 快约 %d 倍\n", loopTime/bitsTime+1)
}
|
7.25 OnesCount 系列
统计二进制中 1 的个数。
7.25.1 PopCount
最常用的函数。统计 uint 中 1 的个数。
7.25.2 计算二进制中有多少个 1,是 CPU 指令,O(1)
一条 POPCNT 指令就搞定了,比任何循环都快。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| package main
import (
"fmt"
"math/bits"
)
func main() {
testCases := []uint{
0, // 0000
1, // 0001
0xFF, // 11111111
0x5555, // 0101010101010101
0xAAAA, // 1010101010101010
0xFFFFFFFF, // 32 个 1
}
fmt.Println("=== PopCount ===")
for _, n := range testCases {
fmt.Printf("PopCount(%032b) = %d\n", n, bits.PopCount(n))
}
// 奇偶性判断:PopCount 的应用
fmt.Println("\n=== 应用:判断奇偶 ===")
for _, n := range []uint{1, 2, 3, 4} {
if bits.PopCount(n)%2 == 0 {
fmt.Printf("%d 有偶数个 1(偶数)\n", n)
} else {
fmt.Printf("%d 有奇数个 1(奇数)\n", n)
}
}
}
|
7.26 Len 系列
返回最高有效位的位置。
7.26.1 Len
返回最高有效位的位置(从 1 开始)。
7.26.2 返回最高有效位的位置,Len(16) = 5,因为 16=10000b
注意:Len 返回的是位置(从1开始),不是索引(从0开始)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| package main
import (
"fmt"
"math/bits"
)
func main() {
fmt.Println("=== Len 系列 ===")
testCases := []uint{1, 2, 3, 4, 7, 8, 15, 16, 17, 255, 256}
for _, n := range testCases {
fmt.Printf("Len(%d) = %d (%s)\n", n, bits.Len(n), fmt.Sprintf("%b", n))
}
fmt.Println("\n=== 应用 ===")
// 快速估算数字需要的位数
n := uint(1000)
fmt.Printf("数字 %d 需要 %d 位二进制表示\n", n, bits.Len(n))
// 快速幂运算优化:知道指数的位数可以提前分配空间
exp := 100
fmt.Printf("2^%d 需要大约 %d 位二进制\n", exp, bits.Len(1<<exp))
}
|
7.27 RotateLeft
循环移位,数据从一端移出又从另一端移入。
7.27.1 循环移位
所有移出的位从另一端移入,形成"循环"。
7.27.2 数据循环移入移出
在密码学(如 DES)和图像处理(如位图旋转)中常用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package main
import (
"fmt"
"math/bits"
)
func main() {
fmt.Println("=== RotateLeft ===")
// 8 位循环左移
for _, shift := range []int{0, 1, 2, 4, 8} {
x := uint8(0b10110011)
rotated := bits.RotateLeft8(x, shift)
fmt.Printf("RotateLeft8(%08b, %2d) = %08b\n", x, shift, rotated)
}
fmt.Println("\n=== RotateRight ===")
x := uint8(0b10110011)
for _, shift := range []int{1, 2, 4} {
rotated := bits.RotateLeft8(x, -shift) // 负数是右移
fmt.Printf("RotateRight8(%08b, %d) = %08b\n", x, shift, rotated)
}
fmt.Println("\n=== 16位和32位版本 ===")
y16 := uint16(0b1111000011110000)
y32 := uint32(0b11110000111100001111000011110000)
fmt.Printf("RotateLeft16(%016b, 4) = %016b\n", y16, bits.RotateLeft16(y16, 4))
fmt.Printf("RotateLeft32(%032b, 8) = %032b\n", y32, bits.RotateLeft32(y32, 8))
}
|
7.28 Reverse 系列
比特位反转。
7.28.1 比特位反转
将字节或字的位顺序反转。00000001 变成 10000000。
7.28.2 字节内位序反转,用于 FFT 等算法
FFT(快速傅里叶变换)需要位反转索引。bit reversal 是 FFT 的基础步骤。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| package main
import (
"fmt"
"math/bits"
)
func main() {
fmt.Println("=== Reverse 系列 ===")
testCases := []uint8{1, 2, 3, 4, 5, 10, 128, 255}
for _, n := range testCases {
rev := bits.Reverse8(n)
fmt.Printf("Reverse8(%08b) = %08b\n", n, rev)
}
fmt.Println("\n=== 16位和32位 ===")
x16 := uint16(0x00FF)
x32 := uint32(0x00FF00FF)
fmt.Printf("Reverse16(%016b) = %016b\n", x16, bits.Reverse16(x16))
fmt.Printf("Reverse32(%032b) = %032b\n", x32, bits.Reverse32(x32))
// FFT 位反转示例
fmt.Println("\n=== FFT 位反转应用 ===")
N := 8 // 8 点 FFT
for i := 0; i < N; i++ {
rev := bits.Reverse8(uint8(i)) & 7 // 只取低 3 位(&7 等价于保留后3位)
fmt.Printf("FFT 索引 %d -> 位反转 %d\n", i, rev)
}
}
|
7.29 Add、Sub、Mul、Div
位级算术运算。
7.29.1 加减乘除的位级实现
这些函数返回进位或余数,适合底层算法实现。
7.29.2 返回进位或余数
Add 返回进位,Div 返回商和余数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| package main
import (
"fmt"
"math/bits"
)
func main() {
fmt.Println("=== Add ===")
a, b := uint(10), uint(20)
sum, carry := bits.Add(a, b)
fmt.Printf("%d + %d = %d, 进位 = %d\n", a, b, sum, carry)
// 溢出示例
max := uint(^uint(0)) // 最大 uint
sumOverflow, carryOverflow := bits.Add(max, 1)
fmt.Printf("%d + 1 = %d, 进位 = %d (溢出检测)\n", max, sumOverflow, carryOverflow)
fmt.Println("\n=== Sub ===")
diff, borrow := bits.Sub(20, 8)
fmt.Printf("20 - 8 = %d, 借位 = %d\n", diff, borrow)
// 借位示例(20 - 30)
diffBorrow, borrowBorrow := bits.Sub(20, 30)
fmt.Printf("20 - 30 = %d, 借位 = %d (负数结果)\n", diffBorrow, borrowBorrow)
fmt.Println("\n=== Mul ===")
hi, lo := bits.Mul(6, 7)
result := (uint64(hi) << 64) | uint64(lo)
fmt.Printf("6 × 7 = %d (hi=%d, lo=%d)\n", result, hi, lo)
// 大数乘法
bigA := uint64(0xFFFFFFFF)
bigB := uint64(0xFFFFFFFF)
hiBig, loBig := bits.Mul(bigA, bigB)
fmt.Printf("0xFFFFFFFF × 0xFFFFFFFF = 0x%X%X\n", hiBig, loBig)
fmt.Println("\n=== Div ===")
quo, rem := bits.Div(100, 7)
fmt.Printf("100 ÷ 7 = %d, 余数 = %d\n", quo, rem)
}
|
7.30 LeadingZeros、TrailingZeros
前导零和末尾零计数。
7.30.1 前导零和末尾零计数
LeadingZeros 计算前面有多少个 0,TrailingZeros 计算末尾有多少个 0。
7.30.2 快速找到第一个/最后一个 1
这在算法中很有用,比如快速找到二进制表示中最左/最右的 1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| package main
import (
"fmt"
"math/bits"
)
func main() {
fmt.Println("=== LeadingZeros(前导零)===")
testCases := []uint{1, 2, 4, 128, 255, 0}
for _, n := range testCases {
lz := bits.LeadingZeros(n)
fmt.Printf("LeadingZeros(%d) = %d (32-%d=%d位有效位)\n", n, lz, lz, 32-lz)
}
fmt.Println("\n=== TrailingZeros(末尾零)===")
tzCases := []uint{8, 16, 24, 32, 0}
for _, n := range tzCases {
tz := bits.TrailingZeros(n)
fmt.Printf("TrailingZeros(%d) = %d (能被 2^%d 整除)\n", n, tz, tz)
}
fmt.Println("\n=== 应用 ===")
// 快速判断是否是 2 的幂
isPowerOf2 := func(n uint) bool {
return n != 0 && bits.TrailingZeros(n) == bits.Len(n)-1
}
for _, n := range []uint{1, 2, 3, 4, 8, 16, 15} {
fmt.Printf("%d 是 2 的幂: %v\n", n, isPowerOf2(n))
}
}
|
7.31 Add32、Add64、Sub32、Sub64、Mul32、Mul64:带溢出的加减乘
这些函数是 Add、Sub、Div 的变体,操作特定宽度的整数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| package main
import (
"fmt"
"math/bits"
)
func main() {
fmt.Println("=== 32位运算 ===")
// Add32
sum, carry := bits.Add32(1, 2, 0)
fmt.Printf("Add32(1, 2, 0) = %d, 进位 = %d\n", sum, carry)
// Sub32
diff, borrow := bits.Sub32(5, 3, 0)
fmt.Printf("Sub32(5, 3, 0) = %d, 借位 = %d\n", diff, borrow)
// Mul32
hi, lo := bits.Mul32(12345, 6789)
fmt.Printf("Mul32(12345, 6789) = hi=%d, lo=%d\n", hi, lo)
fmt.Println("\n=== 溢出示例 ===")
// 计算 0xFFFFFFFF + 1
sumOverflow, carryOverflow := bits.Add32(0xFFFFFFFF, 1, 0)
fmt.Printf("Add32(0xFFFFFFFF, 1, 0) = %d (0x%08X), 进位 = %d\n", sumOverflow, sumOverflow, carryOverflow)
// 验证
fmt.Printf("实际结果: 0x%08X (溢出!)\n", uint32(sumOverflow))
}
|
7.32 math/cmplx 包解决什么问题
math/cmplx 提供了复数运算支持。
7.32.1 复数运算
Go 原生支持 complex 类型,math/cmplx 提供了复数版本的数学函数。
7.32.2 复数的加减乘除,三角函数
复数也有自己的 sin、cos、log、pow。
7.32.3 对数
复数对数是多值函数,cmplx.Log 返回主值。
7.32.4 幂
复数幂运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| package main
import (
"fmt"
"math/cmplx"
)
func main() {
// 创建复数 3 + 4i
c := complex(3, 4)
fmt.Printf("复数: %v\n", c)
fmt.Printf("模长: %.1f\n", cmplx.Abs(c))
// 复数运算
c1 := complex(1, 2)
c2 := complex(3, 4)
fmt.Println("\n=== 复数基本运算 ===")
fmt.Printf("%v + %v = %v\n", c1, c2, c1+c2)
fmt.Printf("%v - %v = %v\n", c1, c2, c1-c2)
fmt.Printf("%v × %v = %v\n", c1, c2, c1*c2)
fmt.Printf("%v ÷ %v = %v\n", c1, c2, c1/c2)
// 复数三角函数
fmt.Println("\n=== 复数三角函数 ===")
z := complex(0, 0)
fmt.Printf("Sin(%v) = %v\n", z, cmplx.Sin(z))
fmt.Printf("Cos(%v) = %v\n", z, cmplx.Cos(z))
// 复数对数
fmt.Println("\n=== 复数对数 ===")
fmt.Printf("Log(1+0i) = %v\n", cmplx.Log(complex(1, 0)))
fmt.Printf("Log(i) = %v\n", cmplx.Log(complex(0, 1)))
}
|
7.33 math/cmplx 核心原理
complex128 与 complex64
Go 支持两种复数类型:complex64(实部虚部各32位)和 complex128(实部虚部各64位)。complex 函数从实部和虚部构造复数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| package main
import (
"fmt"
"math/cmplx"
)
func main() {
// 创建复数
c64 := complex(1, 2) // 自动推断为 complex128
c128 := complex(1.5, 2.5)
fmt.Printf("c64: %T = %v\n", c64, c64)
fmt.Printf("c128: %T = %v\n", c128, c128)
// 从实部虚部构造
realPart := 3.0
imagPart := 4.0
c := cmplx.Rect(5, 0.9273) // 模长 5,角度约 53.13° ≈ arctan(4/3)
fmt.Printf("构造的复数: %v (模长=%.1f, 角度=%.2f°)\n", c, cmplx.Abs(c), cmplx.Phase(c)*180/cmplx.Abs(cmplx.Exp(complex(0, 1))))
}
|
7.34 real、imag:提取复数的实部和虚部
复数由实部和虚部组成,可以单独提取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package main
import (
"fmt"
"math/cmplx"
)
func main() {
c := complex(3, 4)
fmt.Printf("复数: %v\n", c)
fmt.Printf("实部 (real): %.1f\n", real(c))
fmt.Printf("虚部 (imag): %.1f\n", imag(c))
// 也可用 cmplx 包的版本
fmt.Printf("cmplx.Real: %.1f\n", cmplx.Real(c))
fmt.Printf("cmplx.Imag: %.1f\n", cmplx.Imag(c))
// 验证:a + bi = complex(a, b)
a, b := real(c), imag(c)
c2 := complex(a, b)
fmt.Printf("\n重新构造: complex(%.1f, %.1f) = %v\n", a, b, c2)
}
|
7.35 基本运算
复数的加减乘除和共轭。
7.35.1 加减乘除
Go 的复数运算符原生支持这些运算。
7.35.2 共轭(Conj)
复数的共轭是把虚部取反。a + bi 的共轭是 a - bi。
7.35.3 复数的共轭是把虚部取反
共轭复数在复变函数论和信号处理中很重要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| package main
import (
"fmt"
"math/cmplx"
)
func main() {
c := complex(3, 4)
fmt.Printf("复数: %v\n", c)
fmt.Printf("共轭 (Conj): %v\n", cmplx.Conj(c))
// 验证:z * Conj(z) = |z|²
product := c * cmplx.Conj(c)
absSquared := cmplx.Abs(c) * cmplx.Abs(c)
fmt.Printf("z × Conj(z) = %v\n", product)
fmt.Printf("|z|² = %.1f\n", absSquared)
fmt.Printf("相等: %v\n", product == complex(absSquared, 0))
}
|
7.36 Abs、Phase、Polar
复数的模长、相角和极坐标转换。
7.36.1 模长
复数的模长是从原点到该点的距离。
7.36.2 相角
复数与实轴正方向的夹角。
7.36.3 极坐标转换
复数可以用笛卡尔坐标 (a, b) 或极坐标 (r, θ) 表示。
7.36.4 复数可以用模长和相角表示
z = r(cos θ + i sin θ) = r·e^(iθ)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| package main
import (
"fmt"
"math"
"math/cmplx"
)
func main() {
c := complex(3, 4)
fmt.Printf("复数: %v\n", c)
// 模长
abs := cmplx.Abs(c)
fmt.Printf("模长 (Abs): %.1f\n", abs)
// 相角(弧度)
phase := cmplx.Phase(c)
fmt.Printf("相角 (Phase): %.4f 弧度\n", phase)
fmt.Printf("相角: %.2f°\n", phase*180/math.Pi)
// 极坐标
r, theta := cmplx.Polar(c)
fmt.Printf("极坐标: r=%.1f, θ=%.4f rad\n", r, theta)
// 从极坐标恢复笛卡尔坐标
cartesian := cmplx.Rect(r, theta)
fmt.Printf("从极坐标恢复: %v\n", cartesian)
// 验证欧拉公式
euler := cmplx.Exp(complex(0, phase)) * r
fmt.Printf("欧拉公式验证: r×e^(iθ) = %v\n", euler)
}
|
7.37 指数、对数、幂、三角函数
复数版本的 math 函数。
7.37.1 复数版本的 math 函数
math 包的所有数学函数都有复数版本。
7.37.2 Sin
复数正弦。sin(z) = sin(x)cosh(y) + i cos(x)sinh(y)
7.37.3 Log
复数对数。Log(z) = ln|z| + i Arg(z)
7.37.4 Pow 等
复数幂运算。Pow(z1, z2) = exp(z2 × Log(z1))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| package main
import (
"fmt"
"math/cmplx"
)
func main() {
z := complex(1, 1)
fmt.Printf("复数 z = %v\n", z)
fmt.Println("\n=== 指数和对数 ===")
fmt.Printf("Exp(z) = %v\n", cmplx.Exp(z))
fmt.Printf("Log(z) = %v\n", cmplx.Log(z))
fmt.Println("\n=== 三角函数 ===")
fmt.Printf("Sin(z) = %v\n", cmplx.Sin(z))
fmt.Printf("Cos(z) = %v\n", cmplx.Cos(z))
fmt.Printf("Tan(z) = %v\n", cmplx.Tan(z))
fmt.Println("\n=== 反三角函数 ===")
fmt.Printf("Asin(z) = %v\n", cmplx.Asin(z))
fmt.Printf("Acos(z) = %v\n", cmplx.Acos(z))
fmt.Printf("Atan(z) = %v\n", cmplx.Atan(z))
fmt.Println("\n=== 双曲函数 ===")
fmt.Printf("Sinh(z) = %v\n", cmplx.Sinh(z))
fmt.Printf("Cosh(z) = %v\n", cmplx.Cosh(z))
fmt.Printf("Tanh(z) = %v\n", cmplx.Tanh(z))
fmt.Println("\n=== 幂运算 ===")
// z^2
fmt.Printf("z² = %v\n", cmplx.Pow(z, 2))
// 2^z
fmt.Printf("2^z = %v\n", cmplx.Pow(2, z))
// z 的 z 次方
fmt.Printf("z^z = %v\n", cmplx.Pow(z, z))
fmt.Println("\n=== 特殊值 ===")
// i 的 i 次方(结果是实数!)
iPowerI := cmplx.Pow(complex(0, 1), complex(0, 1))
fmt.Printf("i^i = %v (是实数!约等于 %.4f)\n", iPowerI, real(iPowerI))
}
|
本章小结
本章探索了 Go 标准库中的数学工具家族:
| 包 | 用途 | 关键类型 |
|---|
| math | 基础浮点数学运算 | float64 |
| math/big | 任意精度大数运算 | big.Int, big.Float, big.Rat |
| math/bits | CPU 级别位运算 | uint, 无类型整数 |
| math/cmplx | 复数运算 | complex64, complex128 |
核心要点
浮点数精度问题:0.1 + 0.2 ≠ 0.3 是 IEEE 754 的固有特性,不是 bug。比较浮点数要用误差阈值。
特殊值:Inf 和 NaN 是浮点数的"幽灵",理解它们的产生场景是调试数学代码的基础。
大数运算:当 int64 不够用时,big.Int 提供精确整数运算,big.Float 提供可配置精度的浮点运算,big.Rat 提供精确有理数运算。
位运算优化:math/bits 包的函数直接对应 CPU 指令,O(1) 复杂度,是算法优化的利器。
复数运算:math/cmplx 提供了完整的复数数学函数库,从三角函数到指数对数,应有尽有。
数学第一法则:永远不要用 == 比较两个浮点数,除非你确定它们是同一个值直接比较的结果。