Added abs, pow and product operations

This commit is contained in:
adrien 2026-04-22 14:50:21 +02:00
parent c4e462add3
commit 0dd9e02f59
4 changed files with 131 additions and 6 deletions

View File

@ -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|

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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));
}