Added comparison operation to Scalar and Vector

This commit is contained in:
adrien 2026-04-22 12:26:17 +02:00
parent 86841318f2
commit 4c910319d5
2 changed files with 290 additions and 6 deletions

View File

@ -8,12 +8,6 @@ const Dimensions = @import("Dimensions.zig");
const Dimension = Dimensions.Dimension;
// TODO: Add those operation:
// - eq: Equal
// - ne: Not equal
// - gt: Greather than
// - gte: Greather than or equal
// - lt: Less than
// - lte Less than or equal
// - abs: Absolut value
// - pow: Scalar power another
// - log: Scalar log another
@ -156,6 +150,94 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
}
}
/// Compares two Scalar for exact equality.
/// Dimensions must match compile error otherwise. Scales are auto-resolved.
pub inline fn eq(self: Self, rhs: anytype) bool {
if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
if (comptime @TypeOf(rhs) == Self)
return self.value == rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
return lhs_val == rhs_val;
}
/// Compares two quantities for inequality.
/// Dimensions must match compile error otherwise. Scales are auto-resolved.
pub inline fn ne(self: Self, rhs: anytype) bool {
if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
if (comptime @TypeOf(rhs) == Self)
return self.value != rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
return lhs_val != rhs_val;
}
/// Returns true if this quantity is strictly greater than the right-hand side.
/// Dimensions must match compile error otherwise. Scales are auto-resolved.
pub inline fn gt(self: Self, rhs: anytype) bool {
if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
if (comptime @TypeOf(rhs) == Self)
return self.value > rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
return lhs_val > rhs_val;
}
/// Returns true if this quantity is greater than or equal to the right-hand side.
/// Dimensions must match compile error otherwise. Scales are auto-resolved.
pub inline fn gte(self: Self, rhs: anytype) bool {
if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
if (comptime @TypeOf(rhs) == Self)
return self.value >= rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
return lhs_val >= rhs_val;
}
/// Returns true if this quantity is strictly less than the right-hand side.
/// Dimensions must match compile error otherwise. Scales are auto-resolved.
pub inline fn lt(self: Self, rhs: anytype) bool {
if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
if (comptime @TypeOf(rhs) == Self)
return self.value < rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
return lhs_val < rhs_val;
}
/// Returns true if this quantity is less than or equal to the right-hand side.
/// Dimensions must match compile error otherwise. Scales are auto-resolved.
pub inline fn lte(self: Self, rhs: anytype) bool {
if (comptime !dims.eql(@TypeOf(rhs).dims))
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
if (comptime @TypeOf(rhs) == Self)
return self.value <= rhs.value;
const TargetType = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
const lhs_val = if (comptime @TypeOf(self) == TargetType) self.value else self.to(TargetType).value;
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
return lhs_val <= rhs_val;
}
/// Return a `Vector(len, Self)` type.
pub fn Vec(_: Self, comptime len: comptime_int) type {
return Vector(len, Self);
@ -219,6 +301,32 @@ test "Generate quantity" {
try std.testing.expectEqual(2, time.value);
}
test "Comparisons (eq, ne, gt, gte, lt, lte)" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const m1000 = Meter{ .value = 1000 };
const km1 = KiloMeter{ .value = 1 };
const km2 = KiloMeter{ .value = 2 };
// Equal / Not Equal
try std.testing.expect(m1000.eq(km1));
try std.testing.expect(km1.eq(m1000));
try std.testing.expect(km2.ne(m1000));
// Greater Than / Greater Than or Equal
try std.testing.expect(km2.gt(m1000));
try std.testing.expect(km2.gt(km1));
try std.testing.expect(km1.gte(m1000));
try std.testing.expect(km2.gte(m1000));
// Less Than / Less Than or Equal
try std.testing.expect(m1000.lt(km2));
try std.testing.expect(km1.lt(km2));
try std.testing.expect(km1.lte(m1000));
try std.testing.expect(m1000.lte(km2));
}
test "Add" {
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));

View File

@ -130,6 +130,133 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
return res;
}
/// Returns true only if all components are equal after scale resolution.
pub inline fn eqAll(self: Self, rhs: anytype) bool {
const Tr = @TypeOf(rhs);
if (comptime !dims.eql(Tr.dims))
@compileError("Dimension mismatch in eq: " ++ dims.str() ++ " vs " ++ Tr.dims.str());
inline for (self.data, 0..) |v, i| {
const lhs_q = Q{ .value = v };
const rhs_q = Tr.ScalarType{ .value = rhs.data[i] };
if (!lhs_q.eq(rhs_q)) return false;
}
return true;
}
/// Returns true if any component differs after scale resolution.
pub inline fn neAll(self: Self, rhs: anytype) bool {
return !self.eqAll(rhs);
}
/// Element-wise "Equal". Returns an array of booleans.
pub inline fn eq(self: Self, rhs: anytype) [len]bool {
const Tr = @TypeOf(rhs);
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).eq(Tr.ScalarType{ .value = rhs.data[i] });
return res;
}
/// Element-wise "Not Equal". Returns an array of booleans.
pub inline fn ne(self: Self, rhs: anytype) [len]bool {
const Tr = @TypeOf(rhs);
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).ne(Tr.ScalarType{ .value = rhs.data[i] });
return res;
}
/// Element-wise "Greater Than". Returns an array of booleans.
pub inline fn gt(self: Self, rhs: anytype) [len]bool {
const Tr = @TypeOf(rhs);
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).gt(Tr.ScalarType{ .value = rhs.data[i] });
return res;
}
/// Element-wise "Greater Than or Equal". Returns an array of booleans.
pub inline fn gte(self: Self, rhs: anytype) [len]bool {
const Tr = @TypeOf(rhs);
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).gte(Tr.ScalarType{ .value = rhs.data[i] });
return res;
}
/// Element-wise "Less Than". Returns an array of booleans.
pub inline fn lt(self: Self, rhs: anytype) [len]bool {
const Tr = @TypeOf(rhs);
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).lt(Tr.ScalarType{ .value = rhs.data[i] });
return res;
}
/// Element-wise "Less Than or Equal". Returns an array of booleans.
pub inline fn lte(self: Self, rhs: anytype) [len]bool {
const Tr = @TypeOf(rhs);
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).lte(Tr.ScalarType{ .value = rhs.data[i] });
return res;
}
/// Compares every element in the vector to a single scalar for equality.
/// Returns an array of booleans [len]bool. Dimensions must match; scales are auto-resolved.
pub inline fn eqScalar(self: Self, scalar: anytype) [len]bool {
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).eq(scalar);
return res;
}
/// Compares every element in the vector to a single scalar for inequality.
/// Returns an array of booleans [len]bool. Dimensions must match; scales are auto-resolved.
pub inline fn neScalar(self: Self, scalar: anytype) [len]bool {
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).ne(scalar);
return res;
}
/// Checks if each element in the vector is strictly greater than the given scalar.
/// Returns an array of booleans [len]bool.
pub inline fn gtScalar(self: Self, scalar: anytype) [len]bool {
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).gt(scalar);
return res;
}
/// Checks if each element in the vector is greater than or equal to the given scalar.
/// Returns an array of booleans [len]bool.
pub inline fn gteScalar(self: Self, scalar: anytype) [len]bool {
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).gte(scalar);
return res;
}
/// Checks if each element in the vector is strictly less than the given scalar.
/// Returns an array of booleans [len]bool.
pub inline fn ltScalar(self: Self, scalar: anytype) [len]bool {
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).lt(scalar);
return res;
}
/// Checks if each element in the vector is less than or equal to the given scalar.
/// Returns an array of booleans [len]bool.
pub inline fn lteScalar(self: Self, scalar: anytype) [len]bool {
var res: [len]bool = undefined;
inline for (self.data, 0..) |v, i|
res[i] = (Q{ .value = v }).lte(scalar);
return res;
}
/// Negate all components. Dimensions are preserved.
pub fn negate(self: Self) Self {
var res: Self = undefined;
@ -344,3 +471,52 @@ test "VecX Length" {
try std.testing.expectApproxEqAbs(@as(f32, 25.0), v_float.lengthSqr(), 1e-4);
try std.testing.expectApproxEqAbs(@as(f32, 5.0), v_float.length(), 1e-4);
}
test "Vector Comparisons" {
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const v1 = Meter.Vec3{ .data = .{ 1000.0, 500.0, 0.0 } };
const v2 = KiloMeter.Vec3{ .data = .{ 1.0, 0.5, 0.0 } };
const v3 = KiloMeter.Vec3{ .data = .{ 1.0, 0.6, 0.0 } };
// 1. Equality (Whole vector)
try std.testing.expect(v1.eqAll(v2));
try std.testing.expect(v1.neAll(v3));
// 2. Element-wise Ordered Comparison
const higher = v3.gt(v1); // compares 1km, 0.6km, 0km vs 1000m, 500m, 0m
try std.testing.expectEqual(false, higher[0]); // 1km == 1000m
try std.testing.expectEqual(true, higher[1]); // 0.6km > 500m
try std.testing.expectEqual(false, higher[2]); // 0 == 0
// 3. Element-wise Equal Comparison
const equal = v3.eq(v1); // compares 1km, 0.6km, 0km vs 1000m, 500m, 0m
try std.testing.expectEqual(true, equal[0]); // 1km == 1000m
try std.testing.expectEqual(false, equal[1]); // 0.6km > 500m
try std.testing.expectEqual(true, equal[2]); // 0 == 0
// 3. Less than or equal
const low_eq = v1.lte(v3);
try std.testing.expect(low_eq[0] and low_eq[1] and low_eq[2]);
}
test "Vector vs Scalar Comparisons" {
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
const KiloMeter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
const positions = Meter.Vec3{ .data = .{ 500.0, 1200.0, 3000.0 } };
const threshold = KiloMeter{ .value = 1.0 }; // 1km (1000m)
// Check which axes exceed the 1km threshold
const exceeded = positions.gtScalar(threshold);
try std.testing.expectEqual(false, exceeded[0]); // 500m > 1km is false
try std.testing.expectEqual(true, exceeded[1]); // 1200m > 1km is true
try std.testing.expectEqual(true, exceeded[2]); // 3000m > 1km is true
// Check for equality (broadcasted)
const exact_match = positions.eqScalar(Meter{ .value = 500.0 });
try std.testing.expect(exact_match[0] == true);
try std.testing.expect(exact_match[1] == false);
}