Skip to content

Using Operator Overloading for Math

danieltan1517 edited this page Jan 20, 2026 · 22 revisions

When programming, there are many occasions where one wants to define addition/multiplication and different kinds of operations for a mathematical object. For example, one might define a vector or matrix and some addition/subtraction/multiplication functions to go with it.

Vector3 Struct Definition

One can define a Vec3 in the following way:

Vec3 :: struct {
    x: float;
    y: float;
    z: float;
}

We use x, y, z to represent the 3D coordinates.

Vector Addition

Given the above definition, one can overload the addition operator as follows:

operator + :: (a: Vec3, b: Vec3) -> Vec3 {
    c: Vec3;
    c.x = a.x + b.x;
    c.y = a.y + b.y;
    c.z = a.z + b.z;
    return c;
}

This is a short example demonstrating vector addition:

a := Vec3.{1, 2, 3};
b := Vec3.{3, 4, 5};
c := a + b;
print("c = %\n", c);

Vector Subtraction

Here is how one can overload the subtraction operator for Vec3.

operator - :: (a: Vec3, b: Vec3) -> Vec3 {
    c: Vec3;
    c.x = a.x - b.x;
    c.y = a.y - b.y;
    c.z = a.z - b.z;
    return c;
}

This is a short example demonstrating vector subtraction:

a := Vec3.{1, 2, 3};
b := Vec3.{3, 4, 5};
c := a - b;
print("c = %\n", c);

Vector Negation

Here is how one can overload the negation operator for Vec3.

operator - :: (a: Vec3) -> Vec3 {
    b: Vec3;
    b.x = -a.x;
    b.y = -a.y;
    b.z = -a.z;
    return b;
}

This is a short example demonstrating vector negation:

a := Vec3.{1, 2, 3};
b := -a;
print("b = %\n", b);

Vector Scalar Multiplication

One can overload the multiplication operator so that Vec3 can support scalar multiplication. We can attach the #symmetric keyword to the function so that the scalar float value is swappable with the Vec3; in this way, we do not need to define two different functions to represent scalar multiplication.

operator * :: (a: Vec3, b: float) -> Vec3 #symmetric {
    c: Vec3 = a;
    c.x *= b;
    c.y *= b;
    c.z *= b;
    return c;
}

When we compile the example below, the ordering of the Vec3 and scalar float value does not need matter.

a: Vec3 = Vec3.{1, 2, 3};
b: float = 3.0;
c := a * b; 
d := b * a; // <- perform commutative scalar multiplication 
print("c = %\n", c);
print("d = %\n", d);

Vector Dot Product

Dot Product for Vec3 can be written as follows:

dot :: (a: Vec3, b: Vec3) -> float {
    c := (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
    return c;
}

This is a short example demonstrating dot product:

a := Vec3.{1, 2, 3};
b := Vec3.{2, 4, 6};
c := dot(a, b);
print("c = %\n", c); // <- answer should be 'c = 28.0'

Vector4 Dot Product

We can write a dot product using assembly language for a Vector4 in the following way.

#import "Basic";
Vector4 :: struct {
    x: float;
    y: float;
    z: float;
    w: float;
}

dot_asm :: (a: *Vector4, b: *Vector4) -> float {
    result : float;
    #asm {
        xmm0: vec;
        xmm1: vec;
        movaps.x xmm0, [a];
        movaps.x xmm1, [b];
        mulps.x  xmm0, xmm1;
        haddps.x xmm0, xmm0;
        haddps.x xmm0, xmm0;
        movd  result, xmm0;
    }

    return result;
}

main :: () {
    v1 := Vector4.{1, 2, 3, 4} #align 16;
    v2 := Vector4.{5, 6, 7, 8} #align 16;
    print("dot_asm(v1,v2) = %\n", dot_asm(*v1, *v2)); // 70
}

Matrix Struct Definition

There are many possible implementations of a matrix that have many pros and cons. This is one possible implementation of a matrix.

Matrix :: struct(M: int, N: int) {
    data: [M][N] float;
}

In this definition, a matrix is a 2D array of data, which takes M and N as a parameter for the struct for the rows and columns of the matrix, respectively.

Matrix Addition

Given the definition, one can implement matrix addition by adding up the corresponding elements between matrix A and matrix B to get matrix C.

operator + :: (a: Matrix($M, $N), b: Matrix(M, N)) -> Matrix(M, N) {
    c: Matrix(M, N);
    for i : 0..(M-1) {
        for j : 0..(N-1) {
            c.data[i][j] = a.data[i][j] + b.data[i][j];
        }
    }
    return c;
}

Matrix Subtraction

Given the definition, one can implement matrix subtraction by subtracting the corresponding elements between matrix A and matrix B to get matrix C.

operator - :: (a: Matrix($M, $N), b: Matrix(M, N)) -> Matrix(M, N) {
    c: Matrix(M, N);
    for i : 0..(M-1) {
        for j : 0..(N-1) {
            c.data[i][j] = a.data[i][j] - b.data[i][j];
        }
    }
    return c;
}

Matrix Multiplication

Given the definition, one can implement matrix multiplication of two matrices in the following way.

operator * :: (a: Matrix($M, $X), b: Matrix(X, $N)) -> Matrix(M, N) {
    c: Matrix(M, N);
    for i : 0..(M-1) {
        for j : 0..(N-1) {
            value: float = 0.0;
            for k : 0..(X-1) {
                value += a.data[i][k] * b.data[k][j];
            }
            c.data[i][j] = value;
        }
    }
    return c;
}

Matrix Scalar Multiplication

One can overload the multiplication operator so that Matrix can support scalar multiplication. We can attach the #symmetric keyword to the function so that the scalar float value is swappable with the Matrix; in this way, we do not need to define two different functions to represent scalar multiplication.

operator * :: (a: Matrix($M, $N), b: float) -> Matrix(M, N) #symmetric {
    c: Matrix(M, N);
    for i : 0..(M-1) {
        for j : 0..(N-1) {
            c.data[i][j] = a.data[i][j] * b;
        }
    }
    return c;
}

Matrix Transpose

The transpose of a matrix is obtained by flipping it over its diagonal, which means switching its rows with its columns.

transpose :: (matrix: Matrix($M, $N)) -> Matrix(N, M) {
    answer: Matrix(N, M);
    for i : 0..M-1 {
        for j : 0..N-1 {
            answer.data[j][i] = matrix.data[i][j];
        }
    }
    return answer;
}

Complex Numbers

A complex number is a number that combines a real part and an imaginary part. It is expressed in the form: a + bi where:

  • a is the real part
  • b is the imaginary part
  • i is the imaginary unit, defined as the square root of -1

We can define complex numbers using the following struct:

Complex :: struct {
    real: float;
    imaginary: float;
}

Complex Number Addition

We can define complex number addition using operator overloading. Add the corresponding real and imaginary member fields together.

operator + :: (a: Complex, b: Complex) -> Complex {
    c: Complex;
    c.real = a.real + b.real;
    c.imaginary = a.imaginary + b.imaginary;
    return c;
}

Complex Number Subtraction

We can define complex number subtraction using operator overloading. Subtract the corresponding real and imaginary member fields together.

operator - :: (a: Complex, b: Complex) -> Complex {
    c: Complex;
    c.real = a.real - b.real;
    c.imaginary = a.imaginary - b.imaginary;
    return c;
}

Complex Number Multiplication

We can define complex number multiplication using operator overloading. Calculate the multiplication of the square roots of -1 and the multiplication of real and imaginary values when multiplying.

operator * :: (a: Complex, b: Complex) -> Complex {
    c: Complex;
    c.real = (a.real * b.real) - (a.imaginary * b.imaginary);
    c.imaginary = (a.real * b.imaginary) + (a.imaginary * b.real);
    return c;
}

Clone this wiki locally