diff --git a/src/Dimensions.zig b/src/Dimensions.zig index e5805ec..4e48412 100644 --- a/src/Dimensions.zig +++ b/src/Dimensions.zig @@ -75,6 +75,15 @@ pub fn sub(comptime a: Self, comptime b: Self) Self { return result; } +/// 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; +} + /// 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| diff --git a/src/Scalar.zig b/src/Scalar.zig index f97ae1b..d024900 100644 --- a/src/Scalar.zig +++ b/src/Scalar.zig @@ -107,6 +107,28 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type } } + /// Returns the absolute value of the quantity. + /// Dimensions and scales remain entirely unchanged. + pub inline fn abs(self: Self) Self { + if (comptime @typeInfo(T) == .int) + return .{ .value = @intCast(@abs(self.value)) } + else + return .{ .value = @abs(self.value) }; + } + + /// Raises the quantity to a compile-time integer exponent. + /// Dimension exponents are multiplied by the exponent: `(L²)³ → L⁶`. + pub inline fn pow(self: Self, comptime exp: comptime_int) Scalar( + T, + dims.scale(exp), + s, + ) { + if (comptime @typeInfo(T) == .int) + return .{ .value = std.math.powi(T, self.value, exp) catch @panic("Integer overflow in pow") } + else + return .{ .value = std.math.pow(T, self.value, @as(T, @floatFromInt(exp))) }; + } + /// 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 { @@ -549,3 +571,36 @@ test "Format Scalar" { res = try std.fmt.bufPrint(&buf, "{d:_>10.1}", .{m}); try std.testing.expectEqualStrings("_______1.2m", res); } + +test "Abs" { + const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); + const m1 = Meter{ .value = -50 }; + const m2 = m1.abs(); + + try std.testing.expectEqual(50, m2.value); + try std.testing.expectEqual(1, @TypeOf(m2).dims.get(.L)); + + const m_float = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); + const m3 = m_float{ .value = -42.5 }; + try std.testing.expectEqual(42.5, m3.abs().value); +} + +test "Pow" { + const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); + const d = Meter{ .value = 4 }; + + const area = d.pow(2); + try std.testing.expectEqual(16, area.value); + try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L)); + + const volume = d.pow(3); + try std.testing.expectEqual(64, volume.value); + try std.testing.expectEqual(3, @TypeOf(volume).dims.get(.L)); + + // Float test + const MeterF = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); + const d_f = MeterF{ .value = 2.0 }; + const area_f = d_f.pow(3); + try std.testing.expectEqual(8.0, area_f.value); + try std.testing.expectEqual(3, @TypeOf(area_f).dims.get(.L)); +} diff --git a/src/Scales.zig b/src/Scales.zig index 53f74a0..e80ae79 100644 --- a/src/Scales.zig +++ b/src/Scales.zig @@ -90,21 +90,21 @@ pub fn set(comptime self: *Scales, comptime key: Dimension, comptime val: UnitSc /// Compute the combined scale factor for a given dimension signature. /// Each dimension's prefix is raised to its exponent and multiplied together. pub inline fn getFactor(comptime s: Scales, comptime d: Dimensions) comptime_float { - comptime var factor: f64 = 1.0; - inline for (std.enums.values(Dimension)) |dim| { + var factor: f64 = 1.0; + for (std.enums.values(Dimension)) |dim| { const power = comptime d.get(dim); - if (comptime power == 0) continue; + if (power == 0) continue; - const base = comptime s.get(dim).getFactor(); + const base = s.get(dim).getFactor(); var i: comptime_int = 0; const abs_power = if (power < 0) -power else power; - inline while (i < abs_power) : (i += 1) { + while (i < abs_power) : (i += 1) { if (power > 0) factor *= base else factor /= base; } } - return factor; + return comptime factor; } diff --git a/src/Vector.zig b/src/Vector.zig index d8ebad5..9a3fec1 100644 --- a/src/Vector.zig +++ b/src/Vector.zig @@ -180,6 +180,45 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { }; } + /// Returns a vector where each component is the absolute value of the original. + pub inline fn abs(self: Self) Self { + var res: Self = undefined; + inline for (self.data, 0..) |v, i| { + const q = Q{ .value = v }; + res.data[i] = q.abs().value; + } + return res; + } + + /// Multiplies all components of the vector together. + /// Resulting dimensions are (Original Dims * len). + pub inline fn product(self: Self) Scalar( + T, + dims.scale(len), + scales, + ) { + var res_val: T = 1; + inline for (self.data) |v| + res_val *= v; + return .{ .value = res_val }; + } + + /// Raises every component to a compile-time integer power. + /// Dimensions are scaled by the exponent. + pub inline fn pow(self: Self, comptime exp: comptime_int) Vector(len, Scalar( + T, + dims.scale(exp), + scales, + )) { + const ResScalar = Scalar(T, dims.scale(exp), s); + var res: Vector(len, ResScalar) = undefined; + inline for (self.data, 0..) |v, i| { + const q = Q{ .value = v }; + res.data[i] = q.pow(exp).value; + } + 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); @@ -594,3 +633,25 @@ test "Vector Dot and Cross Products" { // Torque dimensions are same as Energy but as a Vector try std.testing.expectEqual(2, @TypeOf(torque).dims.get(.L)); } + +test "Vector Abs, Pow, and Product" { + const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); + + const v1 = Meter.Vec3{ .data = .{ -2.0, 3.0, -4.0 } }; + + // 1. Abs + const v_abs = v1.abs(); + try std.testing.expectEqual(2.0, v_abs.data[0]); + try std.testing.expectEqual(4.0, v_abs.data[2]); + + // 2. Product (L1 * L1 * L1 = L3) + const vol = v_abs.product(); + try std.testing.expectEqual(24.0, vol.value); + try std.testing.expectEqual(3, @TypeOf(vol).dims.get(.L)); + + // 3. Pow (Scalar exponent: (L1)^2 = L2) + const area_vec = v_abs.pow(2); + 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)); +}