Skip to content

Operations

num.zig provides a comprehensive set of element-wise mathematical operations that work efficiently on arrays of any shape.

Arithmetic Operations

Basic Arithmetic

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    var a = try num.NDArray(f64).arange(allocator, 1.0, 6.0, 1.0);
    defer a.deinit(allocator);
    
    var b = try num.NDArray(f64).full(allocator, &.{5}, 2.0);
    defer b.deinit(allocator);
    
    try a.print();
    try b.print();
}

Output:

a: [1.0, 2.0, 3.0, 4.0, 5.0]
b: [2.0, 2.0, 2.0, 2.0, 2.0]

Addition (add)

zig
var result = try num.ops.add(allocator, f64, a, b);
defer result.deinit(allocator);
try result.print();
// Output: [3.0, 4.0, 5.0, 6.0, 7.0]

Subtraction (sub)

zig
var result = try num.ops.sub(allocator, f64, a, b);
defer result.deinit(allocator);
try result.print();
// Output: [-1.0, 0.0, 1.0, 2.0, 3.0]

Multiplication (mul)

zig
var result = try num.ops.mul(allocator, f64, a, b);
defer result.deinit(allocator);
try result.print();
// Output: [2.0, 4.0, 6.0, 8.0, 10.0]

Division (div)

zig
var result = try num.ops.div(allocator, f64, a, b);
defer result.deinit(allocator);
try result.print();
// Output: [0.5, 1.0, 1.5, 2.0, 2.5]

Power and Root Operations

Power (pow)

zig
var arr = try num.NDArray(f64).arange(allocator, 1.0, 6.0, 1.0);
defer arr.deinit(allocator);

var result = try num.ops.pow(allocator, f64, arr, 2.0);
defer result.deinit(allocator);
try result.print();
// Output: [1.0, 4.0, 9.0, 16.0, 25.0]

// Cube each element
var cubed = try num.ops.pow(allocator, f64, arr, 3.0);
defer cubed.deinit(allocator);
try cubed.print();
// Output: [1.0, 8.0, 27.0, 64.0, 125.0]

Square Root (sqrt)

zig
var arr = try num.NDArray(f64).arange(allocator, 0.0, 10.0, 1.0);
defer arr.deinit(allocator);

var result = try num.ops.sqrt(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [0.0, 1.0, 1.414, 1.732, 2.0, 2.236, 2.449, 2.646, 2.828, 3.0]

Exponential and Logarithmic

Exponential (exp)

zig
var arr = try num.NDArray(f64).arange(allocator, 0.0, 5.0, 1.0);
defer arr.deinit(allocator);

var result = try num.ops.exp(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [1.0, 2.718, 7.389, 20.086, 54.598]

Natural Logarithm (log)

zig
var arr = try num.NDArray(f64).arange(allocator, 1.0, 6.0, 1.0);
defer arr.deinit(allocator);

var result = try num.ops.log(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [0.0, 0.693, 1.099, 1.386, 1.609]

Logarithm Base 10 (log10)

zig
var arr = try num.NDArray(f64).full(allocator, &.{5}, 100.0);
defer arr.deinit(allocator);
for (0..5) |i| arr.data[i] = std.math.pow(f64, 10.0, @floatFromInt(i));

var result = try num.ops.log10(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [0.0, 1.0, 2.0, 3.0, 4.0]

Logarithm Base 2 (log2)

zig
var arr = try num.NDArray(f64).full(allocator, &.{5}, 1.0);
defer arr.deinit(allocator);
for (0..5) |i| arr.data[i] = std.math.pow(f64, 2.0, @floatFromInt(i));

var result = try num.ops.log2(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [0.0, 1.0, 2.0, 3.0, 4.0]

Trigonometric Functions

Sine (sin)

zig
const pi = std.math.pi;
var angles = try num.NDArray(f64).linspace(allocator, 0.0, 2.0 * pi, 5);
defer angles.deinit(allocator);

var result = try num.ops.sin(allocator, f64, angles);
defer result.deinit(allocator);
try result.print();
// Output: [0.0, 1.0, 0.0, -1.0, 0.0]

Cosine (cos)

zig
const pi = std.math.pi;
var angles = try num.NDArray(f64).linspace(allocator, 0.0, 2.0 * pi, 5);
defer angles.deinit(allocator);

var result = try num.ops.cos(allocator, f64, angles);
defer result.deinit(allocator);
try result.print();
// Output: [1.0, 0.0, -1.0, 0.0, 1.0]

Tangent (tan)

zig
const pi = std.math.pi;
var angles = try num.NDArray(f64).full(allocator, &.{4}, 0.0);
defer angles.deinit(allocator);
angles.data[0] = 0.0;
angles.data[1] = pi / 4.0;
angles.data[2] = pi / 2.0;
angles.data[3] = 3.0 * pi / 4.0;

var result = try num.ops.tan(allocator, f64, angles);
defer result.deinit(allocator);
try result.print();
// Output: [0.0, 1.0, ∞, -1.0]

Inverse Trigonometric Functions

zig
// arcsin
var arr = try num.NDArray(f64).linspace(allocator, -1.0, 1.0, 5);
defer arr.deinit(allocator);
var result = try num.ops.arcsin(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [-π/2, -π/4, 0.0, π/4, π/2]

// arccos
var result2 = try num.ops.arccos(allocator, f64, arr);
defer result2.deinit(allocator);
try result2.print();
// Output: [π, 3π/4, π/2, π/4, 0.0]

// arctan
var arr2 = try num.NDArray(f64).arange(allocator, -2.0, 3.0, 1.0);
defer arr2.deinit(allocator);
var result3 = try num.ops.arctan(allocator, f64, arr2);
defer result3.deinit(allocator);
try result3.print();
// Output: [-1.107, -0.785, 0.0, 0.785, 1.107]

Hyperbolic Functions

Sinh, Cosh, Tanh

zig
var arr = try num.NDArray(f64).arange(allocator, -2.0, 3.0, 1.0);
defer arr.deinit(allocator);

// Hyperbolic sine
var sinh_result = try num.ops.sinh(allocator, f64, arr);
defer sinh_result.deinit(allocator);
try sinh_result.print();
// Output: [-3.627, -1.175, 0.0, 1.175, 3.627]

// Hyperbolic cosine
var cosh_result = try num.ops.cosh(allocator, f64, arr);
defer cosh_result.deinit(allocator);
try cosh_result.print();
// Output: [3.762, 1.543, 1.0, 1.543, 3.762]

// Hyperbolic tangent
var tanh_result = try num.ops.tanh(allocator, f64, arr);
defer tanh_result.deinit(allocator);
try tanh_result.print();
// Output: [-0.964, -0.762, 0.0, 0.762, 0.964]

Rounding and Absolute Value

Floor

zig
var arr = try num.NDArray(f64).linspace(allocator, -2.5, 2.5, 6);
defer arr.deinit(allocator);
try arr.print();
// Input: [-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]

var result = try num.ops.floor(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0]

Ceiling

zig
var arr = try num.NDArray(f64).linspace(allocator, -2.5, 2.5, 6);
defer arr.deinit(allocator);

var result = try num.ops.ceil(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [-2.0, -1.0, 0.0, 1.0, 2.0, 3.0]

Round

zig
var arr = try num.NDArray(f64).linspace(allocator, -2.7, 2.7, 7);
defer arr.deinit(allocator);

var result = try num.ops.round(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]

Absolute Value

zig
var arr = try num.NDArray(f64).arange(allocator, -5.0, 6.0, 2.0);
defer arr.deinit(allocator);
try arr.print();
// Input: [-5.0, -3.0, -1.0, 1.0, 3.0, 5.0]

var result = try num.ops.abs(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [5.0, 3.0, 1.0, 1.0, 3.0, 5.0]

Sign and Clipping

Sign

zig
var arr = try num.NDArray(f64).arange(allocator, -3.0, 4.0, 1.0);
defer arr.deinit(allocator);
try arr.print();
// Input: [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]

var result = try num.ops.sign(allocator, f64, arr);
defer result.deinit(allocator);
try result.print();
// Output: [-1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0]

Clip

zig
var arr = try num.NDArray(f64).arange(allocator, 0.0, 11.0, 1.0);
defer arr.deinit(allocator);

var result = try num.ops.clip(allocator, f64, arr, 2.0, 8.0);
defer result.deinit(allocator);
try result.print();
// Input:  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Output: [2, 2, 2, 3, 4, 5, 6, 7, 8, 8, 8]

Comparison Operations

Maximum and Minimum (Element-wise)

zig
var a = try num.NDArray(f64).arange(allocator, 0.0, 5.0, 1.0);
defer a.deinit(allocator);

var b = try num.NDArray(f64).full(allocator, &.{5}, 2.5);
defer b.deinit(allocator);

// Element-wise maximum
var max_result = try num.ops.maximum(allocator, f64, a, b);
defer max_result.deinit(allocator);
try max_result.print();
// a:      [0.0, 1.0, 2.0, 3.0, 4.0]
// b:      [2.5, 2.5, 2.5, 2.5, 2.5]
// Output: [2.5, 2.5, 2.5, 3.0, 4.0]

// Element-wise minimum
var min_result = try num.ops.minimum(allocator, f64, a, b);
defer min_result.deinit(allocator);
try min_result.print();
// Output: [0.0, 1.0, 2.0, 2.5, 2.5]

Practical Example: Data Normalization

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    // Sample data
    var data = try num.NDArray(f64).arange(allocator, 0.0, 100.0, 10.0);
    defer data.deinit(allocator);
    try data.print();
    // Input: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
    
    // Compute statistics
    const mean = try num.stats.mean(allocator, f64, data);
    const std_dev = try num.stats.stdDev(allocator, f64, data);
    
    std.debug.print("Mean: {d}, Std: {d}\n", .{mean, std_dev});
    // Output: Mean: 45.0, Std: 28.723
    
    // Normalize: (x - mean) / std
    var centered = try num.ops.sub_scalar(allocator, f64, data, mean);
    defer centered.deinit(allocator);
    
    var normalized = try num.ops.div_scalar(allocator, f64, centered, std_dev);
    defer normalized.deinit(allocator);
    
    try normalized.print();
    // Output: [-1.566, -1.218, -0.871, -0.523, -0.175, 
    //          0.175, 0.523, 0.871, 1.218, 1.566]
}

Performance Considerations

All operations in num.zig are:

  • Vectorized: Use SIMD instructions when available
  • Memory-efficient: Work with views when possible
  • Type-safe: Compile-time type checking
  • Cache-friendly: Contiguous memory access patterns
zig
// Efficient chaining
var result = try num.ops.exp(
    allocator,
    f64,
    try num.ops.mul(
        allocator,
        f64,
        a,
        b
    )
);
// Computes: exp(a * b)

Released under the Apache 2.0 License.