Integer Operators

Let’s take a look at the operators for integers.

Comparison operators

const std = @import("std");
const expect = std.testing.expect;

test "comparison operators" {
    try expect(1 == 1);
    try expect(1 != 2);
    try expect(1 >= 1 and 2 >= 1);
    try expect(2 > 1);
    try expect(1 <= 1 and 1 <= 2);
    try expect(1 < 2);
}
$ zig test comparison.zig
All 1 tests passed.

Simple arithmetic

const std = @import("std");
const expect = std.testing.expect;

test "+ - and *" {
    try expect(1 + 2 == 3);
    try expect(1 - 2 == -1);
    try expect(2 * 3 == 6);
}
$ zig test arithmetic.zig 
All 1 tests passed.

Division

const std = @import("std");
const expect = std.testing.expect;

test "/ for non-negative integers" {
    try expect(4 / 2 == 2);
    try expect(3 / 2 == 1); // round down
    try expect(2 / 2 == 1);
}

test "/ for negative integers, no remainder" {
    try expect(-4 / 2 == -2);
    try expect(4 / -2 == -2);
    try expect(-4 / -2 == 2);
}
$ zig test division.zig 
All 2 tests passed.

The tricky case is when the result is negative and not a whole number. Zig takes an unusal route here: unless the compiler can prove this doesn’t happen, you can’t use the / operator.

Instead, there’s @divTrunc, @divFloor, and @divExact:

const std = @import("std");
const expect = std.testing.expect;

test "@divTrunc rounds towards zero" {
    try expect(@divTrunc(3, 2) == 1);
    try expect(@divTrunc(-3, 2) == -1);
}

test "@divFloor rounds down" {
    try expect(@divFloor(3, 2) == 1);
    try expect(@divFloor(-3, 2) == -2);
}

test "@divExact is only valid if there’s no remainder" {
    try expect(@divExact(6, 2) == 3);
    try expect(@divExact(-6, 2) == -3);
}
$ zig test division-functions.zig
All 3 tests passed.

Assignment operators

+= and friends combine an operator with assignment:

const std = @import("std");
const expect = std.testing.expect;

test "+= -= *= /=" {
    var a: u64 = 3;
    a += 1;
    try expect(a == 4);
    a -= 2;
    try expect(a == 2);
    a *= 3;
    try expect(a == 6);
    a /= 2;
    try expect(a == 3);
}
$ zig test combined.zig
All 1 tests passed.

Wrapping and saturating operators

Overflow – getting a result outside the range of the data type – is illegal behavior with regular arithmetic operators, but there are wrapping and saturating operators that allow it:

const std = @import("std");
const expect = std.testing.expect;

test "wrapping operators" {
    // Results wrap around to the type’s smallest value.
    const a: i8 = 127;
    const b: i8 = 2;
    try expect(a +% b == -127);
    try expect(a *% b == -2);

    // Or the largest.
    const c: u8 = 0;
    const d: u8 = 2;
    try expect(c -% d == 254);
}

test "saturating operators" {
    // The result is the type’s largest value.
    const a: i8 = 127;
    const b: i8 = 2;
    try expect(a +| b == 127);
    try expect(a *| b == 127);

    // Or the smallest.
    const c: u8 = 0;
    const d: u8 = 2;
    try expect(c -| d == 0);
}
$ zig test overflow.zig 
All 2 tests passed.

Bitwise operators

const std = @import("std");
const expect = std.testing.expect;

test "bitwise operators" {
    const a: u8 = 0b00001111;
    const b: u8 = 0b00111100;
    try expect(a & b == 0b00001100);
    try expect(a | b == 0b00111111);
    try expect(a ^ b == 0b00110011);
    try expect(~a == 0b11110000);
    try expect(b >> 2 == 0b00001111);
    try expect(b << 2 == 0b11110000);
    try expect(b << 4 == 0b11000000);
    try expect(b <<| 4 == 0b11111111);
}
$ zig test bitwise.zig 
All 1 tests passed.

Next example: Floating-point numbers.