diff --git a/src/Dimensions.zig b/src/Dimensions.zig index 4e48412..92e3531 100644 --- a/src/Dimensions.zig +++ b/src/Dimensions.zig @@ -68,7 +68,6 @@ pub fn add(comptime a: Self, comptime b: Self) Self { /// Subtract exponents component-wise. Used internally by `divBy`. pub fn sub(comptime a: Self, comptime b: Self) Self { - @setEvalBranchQuota(10_000); var result = Self.initFill(0); inline for (std.enums.values(Dimension)) |d| result.set(d, a.get(d) - b.get(d)); @@ -77,13 +76,19 @@ pub fn sub(comptime a: Self, comptime b: Self) Self { /// Multiply exponents by a scalar integer. Used internally by `pow` in Scalar. pub fn scale(comptime a: Self, comptime exp: comptime_int) Self { - @setEvalBranchQuota(10_000); var result = Self.initFill(0); inline for (std.enums.values(Dimension)) |d| result.set(d, a.get(d) * exp); return result; } +pub fn div(comptime a: Self, comptime exp: comptime_int) Self { + var result = Self.initFill(0); + inline for (std.enums.values(Dimension)) |d| + result.set(d, a.get(d) / exp); + return result; +} + /// Returns true if every dimension exponent is equal. Used to enforce type compatibility in `add`, `sub`, `to`. pub fn eql(comptime a: Self, comptime b: Self) bool { inline for (std.enums.values(Dimension)) |d| @@ -91,6 +96,12 @@ pub fn eql(comptime a: Self, comptime b: Self) bool { return true; } +pub fn isSquare(comptime a: Self) bool { + inline for (std.enums.values(Dimension)) |d| + if (a.get(d) % 2 != 0) return false; + return true; +} + pub fn str(comptime a: Self) []const u8 { var out: []const u8 = ""; const dims = std.enums.values(Dimension); diff --git a/src/Scalar.zig b/src/Scalar.zig index d024900..55564fb 100644 --- a/src/Scalar.zig +++ b/src/Scalar.zig @@ -7,10 +7,8 @@ const UnitScale = Scales.UnitScale; const Dimensions = @import("Dimensions.zig"); const Dimension = Dimensions.Dimension; -// TODO: Add those operation: -// - abs: Absolut value -// - pow: Scalar power another -// - log: Scalar log another +// TODO: Be able to use comptime float and int and T for mulBy ect +// Which endup being Dimension less /// A dimensioned scalar value. `T` is the numeric type, `d` the dimension exponents, `s` the SI scales. /// All dimension and unit tracking is resolved at comptime — zero runtime overhead. @@ -129,6 +127,24 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type return .{ .value = std.math.pow(T, self.value, @as(T, @floatFromInt(exp))) }; } + pub inline fn sqrt(self: Self) Scalar( + T, + dims.div(2), + s, + ) { + if (comptime !dims.isSquare()) // Check if all exponents are divisible by 2 + @compileError("Cannot take sqrt of " ++ dims.str() ++ ": exponents must be even."); + if (self.value < 0) return .{ .value = 0 }; + + if (comptime @typeInfo(T) == .int) { + const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits); + const u_len_sq = @as(UnsignedT, @intCast(self.value)); + return .{ .value = @as(T, @intCast(std.math.sqrt(u_len_sq))) }; + } else { + return .{ .value = @sqrt(self.value) }; + } + } + /// Convert to a compatible unit type. The scale ratio is computed at comptime. /// Compile error if dimensions don't match. pub inline fn to(self: Self, comptime Dest: type) Dest { @@ -463,6 +479,26 @@ test "MulBy dimensionless" { try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); } +test "Sqrt" { + const MeterSquare = Scalar(i128, Dimensions.init(.{ .L = 2 }), Scales.init(.{})); + + var d = MeterSquare{ .value = 9 }; + var scaled = d.sqrt(); + try std.testing.expectEqual(3, scaled.value); + try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); + + d = MeterSquare{ .value = -5 }; + scaled = d.sqrt(); + try std.testing.expectEqual(0, scaled.value); + try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); + + const MeterSquare_f = Scalar(f64, Dimensions.init(.{ .L = 2 }), Scales.init(.{})); + const d2 = MeterSquare_f{ .value = 20 }; + const scaled2 = d2.sqrt(); + try std.testing.expectApproxEqAbs(4.472135955, scaled2.value, 1e-4); + try std.testing.expectEqual(1, @TypeOf(scaled2).dims.get(.L)); +} + test "Chained: velocity and acceleration" { const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const Second = Scalar(f32, Dimensions.init(.{ .T = 1 }), Scales.init(.{})); diff --git a/src/Vector.zig b/src/Vector.zig index 9a3fec1..cce42b8 100644 --- a/src/Vector.zig +++ b/src/Vector.zig @@ -190,6 +190,16 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { return res; } + /// Returns a vector where each component is the absolute value of the original. + pub inline fn sqrt(self: Self) Self { + var res: Self = undefined; + inline for (self.data, 0..) |v, i| { + const q = Q{ .value = v }; + res.data[i] = q.sqrt().value; + } + return res; + } + /// Multiplies all components of the vector together. /// Resulting dimensions are (Original Dims * len). pub inline fn product(self: Self) Scalar( @@ -549,7 +559,7 @@ test "VecX Length" { const MeterInt = Scalar(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const MeterFloat = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); - // Integer length (using your custom isqrt) + // Integer length // 3-4-5 triangle on XY plane const v_int = MeterInt.Vec3{ .data = .{ 3, 4, 0 } }; try std.testing.expectEqual(25, v_int.lengthSqr()); @@ -634,7 +644,7 @@ test "Vector Dot and Cross Products" { try std.testing.expectEqual(2, @TypeOf(torque).dims.get(.L)); } -test "Vector Abs, Pow, and Product" { +test "Vector Abs, Pow, Sqrt and Product" { const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const v1 = Meter.Vec3{ .data = .{ -2.0, 3.0, -4.0 } }; @@ -654,4 +664,10 @@ test "Vector Abs, Pow, and Product" { try std.testing.expectEqual(4.0, area_vec.data[0]); try std.testing.expectEqual(16.0, area_vec.data[2]); try std.testing.expectEqual(2, @TypeOf(area_vec).dims.get(.L)); + + // 4. Sqrt + const sqrted = area_vec.sqrt(); + try std.testing.expectEqual(2, sqrted.data[0]); + try std.testing.expectEqual(4, sqrted.data[2]); + try std.testing.expectEqual(2, @TypeOf(sqrted).dims.get(.L)); }