Skip to content
zig 版本:0.13.0

数值类型

数值类型是语言运行时的基本类型,当它编译为机器码时,其中包含着许多的 CPU运算器 的操作指令。

整数

类型

在 zig 中,对整数的类型划分很详细,以下是类型表格:

类型对应C类型描述
i8int8_t有符号8位整数
u8uint8_t无符号8位整数
i16int16_t有符号16位整数
u16uint16_t无符号16位整数
i32int32_t有符号32位整数
u32uint32_t无符号32位整数
i64int64_t有符号64位整数
u64uint64_t无符号64位整数
i128__int128有符号128位整数
u128unsigned __int128无符号128位整数
isizeintptr_t有符号指针大小的整数
usizeuintptr_t size_t无符号指针大小的整数
comptime_int编译期的整数,整数字面量的类型
zig
// 下划线可以放在数字之间作为视觉分隔符
const one_billion = 1_000_000_000;
const binary_mask = 0b1_1111_1111;
const permissions = 0o7_5_5;
const big_address = 0xFF80_0000_0000_0000;

同时 zig 支持任意位宽的整数,使用 u 或者 i 后面加数字即可,例如 i7 代表有符号的7位整数,整数类型允许的最大位宽为65535

🅿️ 提示

usizeisize 这两种类型的的大小取决于,运行程序的目标计算机 CPU 的类型:32 位 CPU 则两个类型均为 32 位,64 位同理。

不同进制

你可以使用以下方式书写字面量:

字面量示例
十进制98222
十六进制0xff
八进制0o755
二进制0b11110000

除零

zig 编译器对于除零的处理是分别在编译期和运行时(除 ReleaseSmall 构建模式外)进行检测,编译时检测出错误则直接停止编译,运行时如果出错会给出完整的堆栈跟踪。

小细节

这里的“除零”包括了 除法求余 两种操作!

编译期:

zig
comptime {
    const a: i32 = 1;
    const b: i32 = 0;
    const c = a / b;
    _ = c;
}
sh
$ zig test test_comptime_division_by_zero.zig
docgen_tmp/test_comptime_division_by_zero.zig:4:19: error: division by zero here causes undefined behavior
    const c = a / b;
                  ^

运行时:

zig
const std = @import("std");

pub fn main() void {
    var a: u32 = 1;
    var b: u32 = 0;
    var c = a / b;
    std.debug.print("value: {}\n", .{c});
}
sh
$ zig build-exe runtime_division_by_zero.zig
$ ./runtime_division_by_zero
thread 2456131 panic: division by zero
/home/ci/actions-runner/_work/zig-bootstrap/zig/docgen_tmp/runtime_division_by_zero.zig:6:15: 0x21e83e in main (runtime_division_by_zero)
    var c = a / b;
              ^
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:564:22: 0x21e082 in posixCallMainAndExit (runtime_division_by_zero)
            root.main();
                     ^
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:243:5: 0x21dbd1 in _start (runtime_division_by_zero)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

溢出

zig 中,有以下默认操作可以导致溢出:

还有在标准库 @import("std").math 中的函数可能导致溢出发生。

在编译期和运行时也分别有类似“除零”操作的检测和堆栈跟踪。

处理溢出有两种方式,一种是使用内置的溢出处理函数,一种是环绕操作符。

内置溢出处理函数:

这些内建函数返回一个元组,其中包含是否存在溢出(作为 u1)以及操作中可能溢出的位.

环绕操作符:

  • +%(加法环绕)
  • -%(减法环绕)
  • -%(取反环绕)
  • *%(乘法环绕)

这些操作符保证了环绕语义(它们会取计算后溢出的值)。

浮点数

浮点数就是表示带有小数点的数字。在 zig 中,浮点数有 f16f32f64f80f128c_longdouble(对应C ABI的 long double )。

值得注意的是,comptime_float 具有 f128 的精度和运算。

浮点字面量可以隐式转换为 任意浮点类型,如果没有小数部分的话还能够隐式转换为 任意整数类型

浮点运算时遵循 Strict 模式,但是可以使用 @setFloatMode(.Optimized) 切换到 Optimized 模式,有关浮点运算的模式,详见 @setFloatMode

🅿️ 提示

zig 并未像其他语言那样默认提供了 NaN、无穷大、负无穷大这些语法,如果需要使用它们,请使用标准库:

zig
const std = @import("std");

const inf = std.math.inf(f32);
const negative_inf = -std.math.inf(f64);
const nan = std.math.nan(f128);
注意浮点数陷阱
  1. 由于计算机是二进制的特性,导致浮点数往往是以近似值的方式存储(受制于浮点精度,例如有些分数无法用小数表示)。

  2. 浮点数在某些操作上是反直觉的,这也是精度问题导致的,来看个例子:

zig
const std = @import("std");

pub fn main() void {
    // assert用于断言,常用于单元测试和调试
    std.debug.assert(0.1 + 0.2 == 0.3);
}

你一定以为这个可以通过断言,0.1 + 0.2 很明显就应该是 0.3 嘛,但实际上在运行时会直接崩溃!

运算

常规的运算有等于(==),不等于(!=),大于(>),小于(<),大于等于(>=),小于等于(<=),加减乘除(+, -, *, /),左移右移(<<,>>),与或非(and, or, !),按位与(&),按位或(|),按位异或(^),按位非(~),

常见的加减乘除我们就不聊了,聊点 zig 中独具特色的小玩意。

  • +|:饱和加法,这涉及到对等类型解析,你现在只需要知道加法结果最多只是该类型的极限即可,例如 u8 类型的 255 + 1 后还是 255 。
  • -|:饱和减法,和上面一样,减法结果最小为该类型的极限。
  • *|:饱和乘法,同上,乘法结果最大或最小为该类型的极限。
  • <<|:饱和左移,同之前,结果为该类型的极限。
  • ++:矩阵(数组)串联,需要两个矩阵(数组)内元素类型相同。
  • **:矩阵乘(数组)法,需要在编译期已知矩阵(数组)的大小(长度)和乘的倍数。

运算的优先级:

zig
// 以下有一部分运算符你没见过不要紧,后续会讲解
x() x[] x.y x.* x.?
a!b
x{}
!x -x -%x ~x &x ?x
* / % ** *% *| ||
+ - ++ +% -% +| -|
<< >> <<|
& ^ | orelse catch
== != < > <= >=
and
or
= *= *%= *|= /= %= += +%= +|= -= -%= -|= <<= <<|= >>= &= ^= |=

🅿️ 提示

如果你有使用复数的需求,可以使用标准库中的 std.math.Complex

zig
const Complex = std.math.Complex(f64);
const i = Complex.init(0, 1);

// 虚数单位的平方
const z1 = i.mul(i);
print("i * i = ({d:.1},{d:.1})\n", .{ z1.re, z1.im });
// i * i = (-1.0,0.0)

// 使用常见函数
const z2 = std.math.complex.pow(i, Complex.init(2, 0));
print("pow(i, 2) = ({d:.1},{d:.1})\n", .{ z2.re, z2.im });
// pow(i, 2) = (-1.0,0.0)

// 欧拉公式
const z3 = std.math.complex.exp(i.mul(Complex.init(std.math.pi, 0)));
print("exp(i, pi) = ({d:.1},{d:.1})\n", .{ z3.re, z3.im });
// exp(i, pi) = (-1.0,0.0)

// 共轭复数
const z4 = Complex.init(1, 2).mul(Complex.init(1, -2));
print("(1 + 2i) * (1 - 2i) = ({d:.1},{d:.1})\n", .{ z4.re, z4.im });
// (1 + 2i) * (1 - 2i) = (5.0,0.0)