Floating-point Numbers

Let’s take a look at floating-point numbers:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    var a: f64 = 1.2; // 64-bit float
    a /= 3.0;
    print("a = {}\n", .{a});
}
$ zig run float.zig
a = 0.39999999999999997

Formatted output

We can get nicer formatted output by setting the precision:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    const a: f64 = 1.2 / 3.0;
    print("a = {:.4}\n", .{a});
}
$ zig run formatted.zig 
a = 1.2000

Printing with {e} gives us scientific notation:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    const me = 5.9722e24;
    print("mass of the earth ≈ {} kg\n", .{me});
    print("mass of the earth ≈ {e} kg\n", .{me});
}
$ zig run scientific.zig 
mass of the earth ≈ 5972200000000000000000000 kg
mass of the earth ≈ 5.9722e24 kg

Functions

std.math has functions for nan (“not a number”) and inf (“infinity”).

const std = @import("std");
const print = std.debug.print;
const math = std.math;

pub fn main() void {
    const nan = math.nan(f64);
    const inf = math.inf(f64);
    print("{} + {} = {}\n", .{ nan, 1.0, nan + 1.0 });
    print("{} + {} = {}\n", .{ nan, nan, nan + nan });
    print("{} - {} = {}\n", .{ inf, 1.0, inf - 1.0 });
    print("{} - {} = {}\n", .{ inf, inf, inf - inf });
}
$ zig run special.zig 
nan + 1 = nan
nan + nan = nan
inf - 1 = inf
inf - inf = -nan

Some common mathematical functions are available as builtin functions:

const std = @import("std");
const print = std.debug.print;
const math = std.math;

pub fn main() void {
    print("sin pi = {:7.4}\n", .{@sin(math.pi)});
    print("cos pi = {:7.4}\n", .{@cos(math.pi)});
    print("log e  = {:7.4}\n", .{@log(math.e)});
}
$ zig run functions.zig 
sin pi =  0.0000
cos pi = -1.0000
log e  =  1.0000

There are also builtin functions to convert floats to integers:

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

test "float-to-int conversion" {
    try expect(@floor(1.9) == 1);   // round down
    try expect(@floor(-1.1) == -2);

    try expect(@ceil(1.1) == 2);    // round up
    try expect(@ceil(-1.9) == -1);

    try expect(@trunc(1.9) == 1);   // round towards
    try expect(@trunc(-1.9) == -1); // zero

    try expect(@round(1.5) == 2);   // round to the
    try expect(@round(-1.5) == -2); // closest integer
}
$ zig test convert.zig 
All 1 tests passed.

Floating-point types

f64 is a good default choice, but Zig has four more floating-point types:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    const x16: f16 = 1.0 / 3.0;
    const x32: f32 = 1.0 / 3.0;
    const x64: f64 = 1.0 / 3.0;
    const x80: f80 = 1.0 / 3.0;
    const x128: f128 = 1.0 / 3.0;
    print(" 16 bit: 1/3 = {}\n", .{x16});
    print(" 32 bit: 1/3 = {}\n", .{x32});
    print(" 64 bit: 1/3 = {}\n", .{x64});
    print(" 80 bit: 1/3 = {}\n", .{x80});
    print("128 bit: 1/3 = {}\n", .{x128});
}
$ zig run types.zig
 16 bit: 1/3 = 0.3333
 32 bit: 1/3 = 0.33333334
 64 bit: 1/3 = 0.3333333333333333
 80 bit: 1/3 = 0.33333333333333333334
128 bit: 1/3 = 0.3333333333333333333333333333333333

Comparing floating-point numbers

There’s also the usual comparison operators.

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

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

== and != are usually not the right choice for floating-point numbers. The standard library has functions we can use instead:

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

test "approximate comparison operators" {
    const a: f64 = 0.00123;
    const b: f64 = 0.001234;

    // comparison with tolerance
    try expect(math.approxEqAbs(f64, a, b, 1e-5));
    try expect(!math.approxEqAbs(f64, a, b, 1e-6));

    // comparison with relative tolerance
    try expect(math.approxEqRel(f64, a, b, 0.01));
    try expect(!math.approxEqRel(f64, a, b, 0.001));

    // wrapper functions in std.testing
    try std.testing.expectApproxEqAbs(a, b, 1e-5);
    try std.testing.expectApproxEqRel(a, b, 0.01);
}
$ zig test approx.zig 
All 1 tests passed.

Next example: Functions.