bigywater.tech
Go · 37

常量与 iota:无类型常量的精度和溢出规则

常量是 Go 里最“数学”的一块:无类型常量按任意精度计算,只有在赋给变量或带类型时才受范围和精度限制。这篇用真实输出验证 iota 的各种写法,然后用对照实验展示精确计算的效果,再列出常见的编译错误并测出无类型整数常量的实际上限。

0.1 + 0.2 == 0.3
作为常量是 true,作为 float64 变量是 false
1 << 511
无类型整数常量能编译的最大移位(实测,二分法);1 << 512 报 constant shift overflow
1.5 * time.Second
编译错误:time.Second 是带类型的整数常量,要写 1500 * time.Millisecond

iota

实测 · 本机执行输出
  Weekday:Sunday = 0、Monday = 1、Tuesday = 2、Thursday = 4(用 _ 跳过了 3)
  ByteSize:KB = 1024、MB = 1.048576e+06、GB = 1.073741824e+09;%T = main.ByteSize
  一行多个:A, B = 0, 0;C, D = 1, 10;E, F = 2, 20
  位标志:FlagRead = 1、FlagWrite = 2、FlagExec = 4、FlagAll = 7
  隐式重复与 iota:X = 0、Y = "字符串"、Z = "字符串"(沿用上一行的表达式)、W = 3(iota 仍按行号递增)
  另一个 const 块里 iota 重新从 0 开始:Z2 = 0

iota 是 const 块里的行号(从 0 开始,每个块重置)。省略表达式的行会沿用上一行的表达式,所以 Z 得到上一行的字符串、而 W = iota 重新用行号 3。一行里多个名字可以各用一次 iota。1 << iota 是位标志的惯用写法,1 << (10 * iota) 加上带类型的 float64 就是 KB/MB/GB。

精确的常量运算

实测 · 本机执行输出
  const big = 1 << 100;big >> 98 = 4(int 装不下 big,但常量表达式可以)
  const third = 1.0 / 3;third * 3 == 1 → true;float64 变量:x := 1.0 / 3.0; x*3 == 1 → true
  常量 0.1 + 0.2 == 0.3 → true;变量 x + y == 0.3 → false;x + y = 0.30000000000000004
  整数常量除法 1/2 = 0;1/2.0 = 0.5;7/2 = 3;-7/2 = -3;7.0/2 = 3.5
  math.Pi 的常量精度:float64(math.Pi) = 3.14159265358979311600;math.Pi*1e30/1e30 == math.Pi → true
  默认类型:int float64 int32 string complex128
  var f32 float32 = 16777217 → 1.6777216e+07(float32 只能精确表示到 2^24 = 16777216)

无类型常量的运算是精确的:0.1 + 0.2 作为常量就是精确的 0.3,所以和 0.3 相等;一旦落到 float64 变量,0.1 与 0.2 各自先取近似,和就变成 0.30000000000000004。同样,1 << 100 可以作为中间量参与常量运算,只要最后的结果能放进目标类型。整数常量相除是整数除法(1/2 = 0),只要有一个操作数是浮点常量就按浮点算。

带类型的常量

实测 · 本机执行输出
  const timeout = 5 * time.Second → 5s;1500 * time.Millisecond → 1.5s(整数倍的常量乘法)
  n := 3;time.Duration(n) * time.Second → 3s
  len("abcd") 是常量,可以作数组长度:[len("abcd")]int 占 32 字节

time.Second 是 time.Duration(底层 int64)类型的常量,所以 5 * time.Second 没问题,但 1.5 * time.Second 要把 1.5 转成 int64,编译器报“truncated”,正确写法是 1500 * time.Millisecond。变量与常量不同,n * time.Second 里 n 是 int,类型不匹配,必须先转换成 time.Duration。

错误信息与上限

实测 · 本机执行输出
  var x int8 = 200:
    main.go:3:14: cannot use 200 (untyped int constant) as int8 value in variable declaration (overflows)
  const c int32 = 1 << 31:
    main.go:3:17: cannot use 1 << 31 (untyped int constant 2147483648) as int32 value in constant declaration (overflows)
  const half = 1.5 * time.Second(time.Second 是带类型的整数常量):
    main.go:5:14: 1.5 (untyped float constant) truncated to int64
  const huge = 1 << 1000 是合法的常量,但赋给 int 变量:
    main.go:3:16: constant shift overflow
  const huge = 1 << 1000;huge >> 995 = ?:
    main.go:5:16: constant shift overflow
  n := 3; n * time.Second(类型不匹配):
    main.go:5:27: invalid operation: n * time.Second (mismatched types int and time.Duration)
  var s uint = 1; var f float64 = 1 << s(非常量移位):
    main.go:3:47: invalid operation: shifted operand 1 (type float64) must be integer
  常量整数除以 0:
    main.go:3:23: invalid operation: division by zero
  x := 10; var f float64 = x(变量不能隐式转换):
    main.go:3:40: cannot use x (variable of type int) as float64 value in variable declaration
  const 用函数结果初始化:
    main.go:5:13: time.Now() (value of struct type time.Time) is not constant
  二分法找无类型整数常量的上限:const c = 1 << k 能编译并算出 c >> (k-2) 的最大 k = 511(1 << 511 约 6.704e+153);k = 512 报 constant shift overflow;k = 511 时 c >> 509 的运行输出 = 4

常量溢出的编译错误会直接写“overflows”。无类型整数常量并不是真的无限:二分法实测 1 << 511 可以、1 << 512 报 constant shift overflow,与编译器用约 512 位表示常量一致(这个“512 位”是对实验结果的解释,没有读源码)。此外,非常量的移位在需要浮点结果时是错误,time.Now() 这样的函数调用不能初始化 const。

分步演示

常量与 iota
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。所有输出来自真实运行与 go build 的原样错误信息;上限用二分法反复编译 const c = 1 << k 得到
    • 没有验证:无类型常量内部的表示与精度(512 位是推断)、复数常量的运算细节、unsafe.Sizeof 等返回常量的内建函数、go/constant 包的行为、不同 GOARCH 上 int 的位数对常量转换的影响
    京ICP备2026067502号-1京公网安备11010502063487号