-
Notifications
You must be signed in to change notification settings - Fork 0
Using Operator Overloading for Math
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.
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.
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);
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);
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);
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);
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'
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.
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;
}
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;
}
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;
}