Skip to content

Floating Point Numbers

Floating point numbers are numbers with fractional parts.

Floating point numbers are numbers with fractional parts.

Examples:

const price = 19.99;
const temperature = -3.5;
const pi = 3.14159;

Integers store whole numbers. Floating point numbers store approximate real numbers.

Use integers for counts:

const files: u32 = 12;

Use floating point numbers for measurements:

const height: f64 = 1.75;
const weight: f64 = 68.5;

The main floating point types

Zig provides several floating point types:

TypeSizeCommon use
f1616 bitscompact numeric data, graphics, machine learning
f3232 bitsgames, graphics, large numeric arrays
f6464 bitsgeneral decimal calculations
f8080 bitsextended precision on some targets
f128128 bitshigh precision calculations

For beginners, the most important types are:

f32
f64

Use f64 when you want a safe default for ordinary numeric code. Use f32 when memory size and speed matter, especially when storing many numbers.

Declaring floating point values

You can write the type explicitly:

const pi: f64 = 3.14159;
const speed: f32 = 12.5;

Zig can also infer the type in many local contexts:

const x = 3.14;

However, for public APIs, struct fields, and important calculations, writing the type explicitly is often clearer.

const Circle = struct {
    radius: f64,
};

This tells the reader exactly what kind of number the program stores.

Basic arithmetic

Floating point arithmetic uses the familiar operators:

const a: f64 = 10.0;
const b: f64 = 4.0;

const sum = a + b;
const difference = a - b;
const product = a * b;
const quotient = a / b;

Here, quotient is 2.5.

Unlike integer division, floating point division keeps the fractional part.

const x: f64 = 10.0 / 4.0; // 2.5

Floating point numbers are approximate

This is the most important beginner lesson.

Many decimal numbers cannot be represented exactly in binary floating point.

For example, the decimal number 0.1 looks simple to humans, but it does not have an exact finite representation in binary. The computer stores a nearby value.

So this can surprise you:

const std = @import("std");

pub fn main() void {
    const x: f64 = 0.1 + 0.2;

    std.debug.print("{d}\n", .{x});
}

You may expect exactly:

0.3

But internally, the value is only approximately 0.3.

This does not mean floating point is broken. It means floating point is designed for approximate numeric computation, not exact decimal accounting.

Do not compare floats carelessly

Because floats are approximate, direct equality can be dangerous.

This may not behave as you expect:

const x: f64 = 0.1 + 0.2;

if (x == 0.3) {
    // maybe not reached
}

A better approach is to compare with a small tolerance.

const std = @import("std");

fn nearlyEqual(a: f64, b: f64, tolerance: f64) bool {
    return @abs(a - b) < tolerance;
}

pub fn main() void {
    const x: f64 = 0.1 + 0.2;

    if (nearlyEqual(x, 0.3, 0.000001)) {
        std.debug.print("close enough\n", .{});
    }
}

This asks whether two numbers are close enough, instead of asking whether their binary representations are exactly the same.

Special floating point values

Floating point types can represent some special values.

One is infinity.

const inf = std.math.inf(f64);

Another is NaN, which means “not a number.”

const nan = std.math.nan(f64);

NaN can appear in invalid numeric operations, such as some forms of undefined mathematical calculation.

A key property of NaN is that it is not equal to anything, including itself.

const std = @import("std");

pub fn main() void {
    const x = std.math.nan(f64);

    std.debug.print("{}\n", .{x == x}); // false
}

This is unusual, but it follows floating point rules.

Printing floating point numbers

Use std.debug.print as usual.

const std = @import("std");

pub fn main() void {
    const pi: f64 = 3.14159;
    const temperature: f32 = 22.5;

    std.debug.print("pi = {d}\n", .{pi});
    std.debug.print("temperature = {d}\n", .{temperature});
}

Output:

pi = 3.14159
temperature = 22.5

The {d} formatter prints a decimal value.

Scientific notation

You can write large or small floating point numbers using scientific notation.

const large: f64 = 1.5e6;   // 1,500,000
const small: f64 = 2.5e-3;  // 0.0025

The e means “times ten to the power of.”

1.5e6 means 1.5 * 10^6
2.5e-3 means 2.5 * 10^-3

Scientific notation is common in physics, graphics, simulations, and numerical code.

Converting between integers and floats

Zig does not silently convert integers and floats in many situations. You usually make the conversion explicit.

To convert an integer to a float, use @floatFromInt:

const count: u32 = 10;
const count_float: f64 = @floatFromInt(count);

To convert a float to an integer, use @intFromFloat:

const value: f64 = 42.9;
const whole: i32 = @intFromFloat(value);

This discards the fractional part.

42.9 becomes 42

Be careful. If the float value cannot fit into the integer type, that is a problem.

const value: f64 = 1_000_000_000_000.0;
const small: i32 = @intFromFloat(value); // too large for i32

Zig makes this conversion explicit because it can lose information.

Float size matters

An f32 uses less memory than an f64.

If you store one number, this usually does not matter.

If you store millions of numbers, it can matter a lot.

const PointF32 = struct {
    x: f32,
    y: f32,
};

const PointF64 = struct {
    x: f64,
    y: f64,
};

A PointF32 stores two 32-bit floats.

A PointF64 stores two 64-bit floats.

For a huge array of points, f32 may use half the memory. But f64 gives more precision.

This is a common tradeoff in systems programming.

Floating point and money

Do not use binary floating point for exact money calculations.

This is risky:

const price: f64 = 19.99;

It may look fine for printing, but internally it is approximate.

For money, prefer storing the smallest unit as an integer.

const price_cents: i64 = 1999;

This means $19.99 stored as 1999 cents.

Then addition and subtraction are exact:

const a: i64 = 1999;
const b: i64 = 250;

const total_cents = a + b; // 2249

This is a good general rule: use floating point for measurements, not exact accounting.

A complete example

const std = @import("std");

fn circleArea(radius: f64) f64 {
    const pi: f64 = 3.141592653589793;
    return pi * radius * radius;
}

pub fn main() void {
    const radius: f64 = 5.0;
    const area = circleArea(radius);

    std.debug.print("radius = {d}\n", .{radius});
    std.debug.print("area = {d}\n", .{area});
}

Output:

radius = 5
area = 78.53981633974483

The function takes an f64 and returns an f64.

fn circleArea(radius: f64) f64

That means the input and output are both floating point numbers.

The main idea

Floating point numbers are for approximate numeric values.

They are useful for measurements, graphics, simulations, geometry, physics, and many scientific calculations. They are not ideal for exact decimal values such as money.

For beginner Zig code, use f64 when you need a floating point number. Use f32 when memory size matters or an API specifically expects it.