Compare commits
4
Commits
0.1.0
...
c4e462add3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4e462add3 | ||
|
|
4c910319d5 | ||
|
|
86841318f2 | ||
|
|
ec05b60fc3 |
+2
-2
@@ -134,13 +134,13 @@ test "BaseQuantities - Dynamics (Force and Work)" {
|
|||||||
|
|
||||||
// Force = mass * acceleration
|
// Force = mass * acceleration
|
||||||
const f = m.mulBy(a);
|
const f = m.mulBy(a);
|
||||||
try std.testing.expectEqual(98000, f.value);
|
try std.testing.expectEqual(98, f.value);
|
||||||
try std.testing.expect(Force.dims.eql(@TypeOf(f).dims));
|
try std.testing.expect(Force.dims.eql(@TypeOf(f).dims));
|
||||||
|
|
||||||
// Energy (Work) = Force * distance
|
// Energy (Work) = Force * distance
|
||||||
const distance = Meter.Of(f32){ .value = 5.0 };
|
const distance = Meter.Of(f32){ .value = 5.0 };
|
||||||
const energy = f.mulBy(distance);
|
const energy = f.mulBy(distance);
|
||||||
try std.testing.expectEqual(490000, energy.value);
|
try std.testing.expectEqual(490, energy.value);
|
||||||
try std.testing.expect(Energy.dims.eql(@TypeOf(energy).dims));
|
try std.testing.expect(Energy.dims.eql(@TypeOf(energy).dims));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ pub const Dimension = enum {
|
|||||||
|
|
||||||
// --------- Dimensions struct ---------
|
// --------- Dimensions struct ---------
|
||||||
|
|
||||||
|
/// Holds the exponent of each SI base dimension for a given quantity (e.g. velocity = L¹T⁻¹).
|
||||||
|
/// All values are `comptime_int` — no runtime storage.
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
data: std.EnumArray(Dimension, comptime_int),
|
data: std.EnumArray(Dimension, comptime_int),
|
||||||
|
|
||||||
|
/// Create a `Dimensions` from an anonymous struct literal, e.g. `.{ .L = 1, .T = -1 }`.
|
||||||
|
/// Unspecified dimensions default to 0.
|
||||||
pub fn init(comptime init_val: anytype) Self {
|
pub fn init(comptime init_val: anytype) Self {
|
||||||
var s = Self{ .data = std.EnumArray(Dimension, comptime_int).initFill(0) };
|
var s = Self{ .data = std.EnumArray(Dimension, comptime_int).initFill(0) };
|
||||||
inline for (std.meta.fields(@TypeOf(init_val))) |f|
|
inline for (std.meta.fields(@TypeOf(init_val))) |f|
|
||||||
@@ -54,6 +58,7 @@ pub fn set(comptime self: *Self, comptime key: Dimension, comptime val: i8) void
|
|||||||
self.data.set(key, val);
|
self.data.set(key, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add exponents component-wise. Used internally by `mulBy`.
|
||||||
pub fn add(comptime a: Self, comptime b: Self) Self {
|
pub fn add(comptime a: Self, comptime b: Self) Self {
|
||||||
var result = Self.initFill(0);
|
var result = Self.initFill(0);
|
||||||
for (std.enums.values(Dimension)) |d|
|
for (std.enums.values(Dimension)) |d|
|
||||||
@@ -61,6 +66,7 @@ pub fn add(comptime a: Self, comptime b: Self) Self {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subtract exponents component-wise. Used internally by `divBy`.
|
||||||
pub fn sub(comptime a: Self, comptime b: Self) Self {
|
pub fn sub(comptime a: Self, comptime b: Self) Self {
|
||||||
@setEvalBranchQuota(10_000);
|
@setEvalBranchQuota(10_000);
|
||||||
var result = Self.initFill(0);
|
var result = Self.initFill(0);
|
||||||
@@ -69,6 +75,7 @@ pub fn sub(comptime a: Self, comptime b: Self) Self {
|
|||||||
return result;
|
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 {
|
pub fn eql(comptime a: Self, comptime b: Self) bool {
|
||||||
inline for (std.enums.values(Dimension)) |d|
|
inline for (std.enums.values(Dimension)) |d|
|
||||||
if (a.get(d) != b.get(d)) return false;
|
if (a.get(d) != b.get(d)) return false;
|
||||||
|
|||||||
+167
-12
@@ -7,60 +7,79 @@ const UnitScale = Scales.UnitScale;
|
|||||||
const Dimensions = @import("Dimensions.zig");
|
const Dimensions = @import("Dimensions.zig");
|
||||||
const Dimension = Dimensions.Dimension;
|
const Dimension = Dimensions.Dimension;
|
||||||
|
|
||||||
|
// TODO: Add those operation:
|
||||||
|
// - abs: Absolut value
|
||||||
|
// - pow: Scalar power another
|
||||||
|
// - log: Scalar log another
|
||||||
|
|
||||||
|
/// 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.
|
||||||
pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type {
|
pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type {
|
||||||
@setEvalBranchQuota(10_000_000);
|
@setEvalBranchQuota(10_000_000);
|
||||||
return struct {
|
return struct {
|
||||||
value: T,
|
value: T,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
/// Type of Vector(3, Self)
|
||||||
pub const Vec3: type = Vector(3, Self);
|
pub const Vec3: type = Vector(3, Self);
|
||||||
|
|
||||||
|
/// Type of underline value, mostly use for Vector
|
||||||
pub const ValueType: type = T;
|
pub const ValueType: type = T;
|
||||||
|
|
||||||
|
/// Dimensions of this type
|
||||||
pub const dims: Dimensions = d;
|
pub const dims: Dimensions = d;
|
||||||
|
|
||||||
|
/// Scales of this type
|
||||||
pub const scales = s;
|
pub const scales = s;
|
||||||
|
|
||||||
|
/// Add two quantities. Dimensions must match — compile error otherwise.
|
||||||
|
/// Scales are auto-resolved to the finer of the two.
|
||||||
pub inline fn add(self: Self, rhs: anytype) Scalar(
|
pub inline fn add(self: Self, rhs: anytype) Scalar(
|
||||||
T,
|
T,
|
||||||
dims,
|
dims,
|
||||||
scales.min(@TypeOf(rhs).scales),
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
) {
|
) {
|
||||||
if (comptime !dims.eql(@TypeOf(rhs).dims))
|
if (comptime !dims.eql(@TypeOf(rhs).dims))
|
||||||
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
|
@compileError("Dimension mismatch in add: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
|
||||||
if (comptime @TypeOf(rhs) == Self)
|
if (comptime @TypeOf(rhs) == Self)
|
||||||
return .{ .value = self.value + rhs.value };
|
return .{ .value = self.value + rhs.value };
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims, scales.min(@TypeOf(rhs).scales));
|
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 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;
|
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
|
||||||
|
|
||||||
return .{ .value = lhs_val + rhs_val };
|
return .{ .value = lhs_val + rhs_val };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subtract two quantities. Dimensions must match — compile error otherwise.
|
||||||
|
/// Scales are auto-resolved to the finer of the two.
|
||||||
pub inline fn sub(self: Self, rhs: anytype) Scalar(
|
pub inline fn sub(self: Self, rhs: anytype) Scalar(
|
||||||
T,
|
T,
|
||||||
dims,
|
dims,
|
||||||
scales.min(@TypeOf(rhs).scales),
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
) {
|
) {
|
||||||
if (comptime !dims.eql(@TypeOf(rhs).dims))
|
if (comptime !dims.eql(@TypeOf(rhs).dims))
|
||||||
@compileError("Dimension mismatch in sub: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
|
@compileError("Dimension mismatch in sub: " ++ dims.str() ++ " vs " ++ @TypeOf(rhs).dims.str());
|
||||||
if (comptime @TypeOf(rhs) == Self)
|
if (comptime @TypeOf(rhs) == Self)
|
||||||
return .{ .value = self.value - rhs.value };
|
return .{ .value = self.value - rhs.value };
|
||||||
|
|
||||||
const TargetType = Scalar(T, dims, scales.min(@TypeOf(rhs).scales));
|
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 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;
|
const rhs_val = if (comptime @TypeOf(rhs) == TargetType) rhs.value else rhs.to(TargetType).value;
|
||||||
|
|
||||||
return .{ .value = lhs_val - rhs_val };
|
return .{ .value = lhs_val - rhs_val };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Multiply two quantities. Dimension exponents are summed: `L¹ * T⁻¹ → L¹T⁻¹`.
|
||||||
pub inline fn mulBy(self: Self, rhs: anytype) Scalar(
|
pub inline fn mulBy(self: Self, rhs: anytype) Scalar(
|
||||||
T,
|
T,
|
||||||
dims.add(@TypeOf(rhs).dims),
|
dims.add(@TypeOf(rhs).dims),
|
||||||
scales.min(@TypeOf(rhs).scales),
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
) {
|
) {
|
||||||
const RhsType = @TypeOf(rhs);
|
const RhsType = @TypeOf(rhs);
|
||||||
const SelfNorm = Scalar(T, dims, scales.min(RhsType.scales));
|
const SelfNorm = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
|
||||||
const RhsNorm = Scalar(T, RhsType.dims, scales.min(RhsType.scales));
|
const RhsNorm = Scalar(T, RhsType.dims, hlp.finerScales(Self, @TypeOf(rhs)));
|
||||||
if (comptime Self == SelfNorm and RhsType == RhsNorm)
|
if (comptime Self == SelfNorm and RhsType == RhsNorm)
|
||||||
return .{ .value = self.value * rhs.value };
|
return .{ .value = self.value * rhs.value };
|
||||||
|
|
||||||
@@ -69,14 +88,16 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
|
|||||||
return .{ .value = lhs_val * rhs_val };
|
return .{ .value = lhs_val * rhs_val };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Divide two quantities. Dimension exponents are subtracted: `L¹ / T¹ → L¹T⁻¹`.
|
||||||
|
/// Integer types use truncating division.
|
||||||
pub inline fn divBy(self: Self, rhs: anytype) Scalar(
|
pub inline fn divBy(self: Self, rhs: anytype) Scalar(
|
||||||
T,
|
T,
|
||||||
dims.sub(@TypeOf(rhs).dims),
|
dims.sub(@TypeOf(rhs).dims),
|
||||||
scales.min(@TypeOf(rhs).scales),
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
) {
|
) {
|
||||||
const RhsType = @TypeOf(rhs);
|
const RhsType = @TypeOf(rhs);
|
||||||
const SelfNorm = Scalar(T, dims, scales.min(RhsType.scales));
|
const SelfNorm = Scalar(T, dims, hlp.finerScales(Self, @TypeOf(rhs)));
|
||||||
const RhsNorm = Scalar(T, RhsType.dims, scales.min(RhsType.scales));
|
const RhsNorm = Scalar(T, RhsType.dims, hlp.finerScales(Self, @TypeOf(rhs)));
|
||||||
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
|
const lhs_val = if (comptime Self == SelfNorm) self.value else self.to(SelfNorm).value;
|
||||||
const rhs_val = if (comptime RhsType == RhsNorm) rhs.value else rhs.to(RhsNorm).value;
|
const rhs_val = if (comptime RhsType == RhsNorm) rhs.value else rhs.to(RhsNorm).value;
|
||||||
if (comptime @typeInfo(T) == .int) {
|
if (comptime @typeInfo(T) == .int) {
|
||||||
@@ -86,6 +107,8 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub inline fn to(self: Self, comptime Dest: type) Dest {
|
||||||
if (comptime !dims.eql(Dest.dims))
|
if (comptime !dims.eql(Dest.dims))
|
||||||
@compileError("Dimension mismatch in to: " ++ dims.str() ++ " vs " ++ Dest.dims.str());
|
@compileError("Dimension mismatch in to: " ++ dims.str() ++ " vs " ++ Dest.dims.str());
|
||||||
@@ -104,7 +127,6 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
|
|||||||
const div: DestT = comptime @intFromFloat(1.0 / ratio);
|
const div: DestT = comptime @intFromFloat(1.0 / ratio);
|
||||||
const val = @as(DestT, @intCast(self.value));
|
const val = @as(DestT, @intCast(self.value));
|
||||||
const half = comptime div / 2;
|
const half = comptime div / 2;
|
||||||
// Native round-to-nearest
|
|
||||||
const rounded = if (val >= 0) @divTrunc(val + half, div) else @divTrunc(val - half, div);
|
const rounded = if (val >= 0) @divTrunc(val + half, div) else @divTrunc(val - half, div);
|
||||||
return .{ .value = rounded };
|
return .{ .value = rounded };
|
||||||
}
|
}
|
||||||
@@ -128,10 +150,105 @@ pub fn Scalar(comptime T: type, comptime d: Dimensions, comptime s: Scales) type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Vec(self: Self, comptime len: comptime_int) Vector(len, Self) {
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a `Vector(len, Self)` with all components set to this value.
|
||||||
|
pub fn vec(self: Self, comptime len: comptime_int) Vector(len, Self) {
|
||||||
return Vector(len, Self).initDefault(self.value);
|
return Vector(len, Self).initDefault(self.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shorthand for `Vec(3)` — wrap this value into a 3-component vector.
|
||||||
pub fn vec3(self: Self) Vec3 {
|
pub fn vec3(self: Self) Vec3 {
|
||||||
return Vec3.initDefault(self.value);
|
return Vec3.initDefault(self.value);
|
||||||
}
|
}
|
||||||
@@ -184,6 +301,32 @@ test "Generate quantity" {
|
|||||||
try std.testing.expectEqual(2, time.value);
|
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" {
|
test "Add" {
|
||||||
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||||
|
|
||||||
@@ -329,6 +472,18 @@ test "DivBy integer exact" {
|
|||||||
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Finer scales skip dim 0" {
|
||||||
|
const Dimless = Scalar(i128, Dimensions.init(.{}), Scales.init(.{}));
|
||||||
|
const KiloMetre = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||||
|
|
||||||
|
const r = Dimless{ .value = 30 };
|
||||||
|
const time = KiloMetre{ .value = 4 };
|
||||||
|
const vel = r.mulBy(time);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(120, vel.value);
|
||||||
|
try std.testing.expectEqual(Scales.UnitScale.k, @TypeOf(vel).scales.get(.L));
|
||||||
|
}
|
||||||
|
|
||||||
test "Conversion chain: km -> m -> cm" {
|
test "Conversion chain: km -> m -> cm" {
|
||||||
const KiloMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
const KiloMeter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k }));
|
||||||
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
const Meter = Scalar(i128, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||||
|
|||||||
+13
-13
@@ -3,6 +3,9 @@ const hlp = @import("helper.zig");
|
|||||||
const Dimensions = @import("Dimensions.zig");
|
const Dimensions = @import("Dimensions.zig");
|
||||||
const Dimension = @import("Dimensions.zig").Dimension;
|
const Dimension = @import("Dimensions.zig").Dimension;
|
||||||
|
|
||||||
|
/// SI prefix (pico…peta) plus time-unit aliases (min, hour, year).
|
||||||
|
/// The integer value encodes the exponent for SI prefixes (e.g. `k = 3` → 10³),
|
||||||
|
/// and the literal factor for time units (e.g. `hour = 3600`).
|
||||||
pub const UnitScale = enum(isize) {
|
pub const UnitScale = enum(isize) {
|
||||||
P = 15,
|
P = 15,
|
||||||
T = 12,
|
T = 12,
|
||||||
@@ -54,10 +57,13 @@ pub const UnitScale = enum(isize) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Maps each SI base dimension to its `UnitScale`. Stored and resolved entirely at comptime.
|
||||||
const Scales = @This();
|
const Scales = @This();
|
||||||
|
|
||||||
data: std.EnumArray(Dimension, UnitScale),
|
data: std.EnumArray(Dimension, UnitScale),
|
||||||
|
|
||||||
|
/// Create a `Scales` from an anonymous struct literal, e.g. `.{ .L = .k, .T = .hour }`.
|
||||||
|
/// Unspecified dimensions default to `.none` (factor 1).
|
||||||
pub fn init(comptime init_val: anytype) Scales {
|
pub fn init(comptime init_val: anytype) Scales {
|
||||||
comptime var s = Scales{ .data = std.EnumArray(Dimension, UnitScale).initFill(.none) };
|
comptime var s = Scales{ .data = std.EnumArray(Dimension, UnitScale).initFill(.none) };
|
||||||
inline for (std.meta.fields(@TypeOf(init_val))) |f| {
|
inline for (std.meta.fields(@TypeOf(init_val))) |f| {
|
||||||
@@ -81,25 +87,19 @@ pub fn set(comptime self: *Scales, comptime key: Dimension, comptime val: UnitSc
|
|||||||
comptime self.data.set(key, val);
|
comptime self.data.set(key, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn min(comptime s1: Scales, comptime s2: Scales) Scales {
|
/// Compute the combined scale factor for a given dimension signature.
|
||||||
comptime var out = Scales.initFill(.none);
|
/// Each dimension's prefix is raised to its exponent and multiplied together.
|
||||||
inline for (std.enums.values(Dimension)) |dim|
|
|
||||||
out.set(dim, if (s1.get(dim).getFactorInt() > s2.get(dim).getFactorInt()) s2.get(dim) else s1.get(dim));
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn getFactor(comptime s: Scales, comptime d: Dimensions) comptime_float {
|
pub inline fn getFactor(comptime s: Scales, comptime d: Dimensions) comptime_float {
|
||||||
comptime var factor: f64 = 1.0;
|
comptime var factor: f64 = 1.0;
|
||||||
inline for (std.enums.values(Dimension)) |dim| {
|
inline for (std.enums.values(Dimension)) |dim| {
|
||||||
const power = d.get(dim);
|
const power = comptime d.get(dim);
|
||||||
if (power == 0) continue;
|
if (comptime power == 0) continue;
|
||||||
|
|
||||||
const base = s.get(dim).getFactor();
|
const base = comptime s.get(dim).getFactor();
|
||||||
|
|
||||||
var i: i32 = 0;
|
var i: comptime_int = 0;
|
||||||
const abs_power = if (power < 0) -power else power;
|
const abs_power = if (power < 0) -power else power;
|
||||||
while (i < abs_power) : (i += 1) {
|
inline while (i < abs_power) : (i += 1) {
|
||||||
if (power > 0)
|
if (power > 0)
|
||||||
factor *= base
|
factor *= base
|
||||||
else
|
else
|
||||||
|
|||||||
+300
-22
@@ -7,6 +7,7 @@ const UnitScale = Scales.UnitScale;
|
|||||||
const Dimensions = @import("Dimensions.zig");
|
const Dimensions = @import("Dimensions.zig");
|
||||||
const Dimension = Dimensions.Dimension;
|
const Dimension = Dimensions.Dimension;
|
||||||
|
|
||||||
|
/// A fixed-size array of `len` elements sharing the same dimension and scale as scalar type `Q`.
|
||||||
pub fn Vector(comptime len: usize, comptime Q: type) type {
|
pub fn Vector(comptime len: usize, comptime Q: type) type {
|
||||||
const T = Q.ValueType;
|
const T = Q.ValueType;
|
||||||
const d: Dimensions = Q.dims;
|
const d: Dimensions = Q.dims;
|
||||||
@@ -30,19 +31,28 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
|
|||||||
return .{ .data = data };
|
return .{ .data = data };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn add(self: Self, rhs: anytype) Vector(len, Scalar(T, d, s.min(@TypeOf(rhs).scales))) {
|
/// Element-wise addition. Dimensions must match; scales resolve to the finer of the two.
|
||||||
|
pub inline fn add(self: Self, rhs: anytype) Vector(len, Scalar(
|
||||||
|
T,
|
||||||
|
dims,
|
||||||
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
|
)) {
|
||||||
const Tr = @TypeOf(rhs);
|
const Tr = @TypeOf(rhs);
|
||||||
var res: Vector(len, Scalar(T, d, s.min(Tr.scales))) = undefined;
|
var res: Vector(len, Scalar(T, d, hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i| {
|
||||||
const q = (Q{ .value = v }).add(Tr.ScalarType{ .value = rhs.data[i] });
|
const q = (Q{ .value = v }).add(Tr.ScalarType{ .value = rhs.data[i] });
|
||||||
res.data[i] = q.value;
|
res.data[i] = q.value;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
/// Element-wise subtraction. Dimensions must match; scales resolve to the finer of the two.
|
||||||
pub inline fn sub(self: Self, rhs: anytype) Vector(len, Scalar(T, d, s.min(@TypeOf(rhs).scales))) {
|
pub inline fn sub(self: Self, rhs: anytype) Vector(len, Scalar(
|
||||||
|
T,
|
||||||
|
dims,
|
||||||
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
|
)) {
|
||||||
const Tr = @TypeOf(rhs);
|
const Tr = @TypeOf(rhs);
|
||||||
var res: Vector(len, Scalar(T, d, s.min(Tr.scales))) = undefined;
|
var res: Vector(len, Scalar(T, d, hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i| {
|
||||||
const q = (Q{ .value = v }).sub(Tr.ScalarType{ .value = rhs.data[i] });
|
const q = (Q{ .value = v }).sub(Tr.ScalarType{ .value = rhs.data[i] });
|
||||||
res.data[i] = q.value;
|
res.data[i] = q.value;
|
||||||
@@ -50,12 +60,17 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Element-wise division. Dimension exponents are subtracted per component.
|
||||||
pub inline fn divBy(
|
pub inline fn divBy(
|
||||||
self: Self,
|
self: Self,
|
||||||
rhs: anytype,
|
rhs: anytype,
|
||||||
) Vector(len, Scalar(T, d.sub(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) {
|
) Vector(len, Scalar(
|
||||||
|
T,
|
||||||
|
dims.sub(@TypeOf(rhs).dims),
|
||||||
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
|
)) {
|
||||||
const Tr = @TypeOf(rhs);
|
const Tr = @TypeOf(rhs);
|
||||||
var res: Vector(len, Scalar(T, d.sub(Tr.dims), s.min(Tr.scales))) = undefined;
|
var res: Vector(len, Scalar(T, d.sub(Tr.dims), hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i| {
|
||||||
const q = (Q{ .value = v }).divBy(Tr.ScalarType{ .value = rhs.data[i] });
|
const q = (Q{ .value = v }).divBy(Tr.ScalarType{ .value = rhs.data[i] });
|
||||||
res.data[i] = q.value;
|
res.data[i] = q.value;
|
||||||
@@ -63,12 +78,17 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Element-wise multiplication. Dimension exponents are summed per component.
|
||||||
pub inline fn mulBy(
|
pub inline fn mulBy(
|
||||||
self: Self,
|
self: Self,
|
||||||
rhs: anytype,
|
rhs: anytype,
|
||||||
) Vector(len, Scalar(T, d.add(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) {
|
) Vector(len, Scalar(
|
||||||
|
T,
|
||||||
|
dims.add(@TypeOf(rhs).dims),
|
||||||
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
|
)) {
|
||||||
const Tr = @TypeOf(rhs);
|
const Tr = @TypeOf(rhs);
|
||||||
var res: Vector(len, Scalar(T, d.add(Tr.dims), s.min(Tr.scales))) = undefined;
|
var res: Vector(len, Scalar(T, d.add(Tr.dims), hlp.finerScales(Self, @TypeOf(rhs)))) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i| {
|
||||||
const q = (Q{ .value = v }).mulBy(Tr.ScalarType{ .value = rhs.data[i] });
|
const q = (Q{ .value = v }).mulBy(Tr.ScalarType{ .value = rhs.data[i] });
|
||||||
res.data[i] = q.value;
|
res.data[i] = q.value;
|
||||||
@@ -76,11 +96,16 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Divide every component by a single scalar. Dimensions are subtracted (e.g. position / time → velocity).
|
||||||
pub inline fn divByScalar(
|
pub inline fn divByScalar(
|
||||||
self: Self,
|
self: Self,
|
||||||
scalar: anytype,
|
scalar: anytype,
|
||||||
) Vector(len, Scalar(T, d.sub(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) {
|
) Vector(len, Scalar(
|
||||||
var res: Vector(len, Scalar(T, d.sub(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) = undefined;
|
T,
|
||||||
|
dims.sub(@TypeOf(scalar).dims),
|
||||||
|
hlp.finerScales(Self, @TypeOf(scalar)),
|
||||||
|
)) {
|
||||||
|
var res: Vector(len, Scalar(T, d.sub(@TypeOf(scalar).dims), hlp.finerScales(Self, @TypeOf(scalar)))) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i| {
|
||||||
const q = Q{ .value = v };
|
const q = Q{ .value = v };
|
||||||
res.data[i] = q.divBy(scalar).value;
|
res.data[i] = q.divBy(scalar).value;
|
||||||
@@ -88,11 +113,16 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Multiply every component by a single scalar. Dimensions are summed.
|
||||||
pub inline fn mulByScalar(
|
pub inline fn mulByScalar(
|
||||||
self: Self,
|
self: Self,
|
||||||
scalar: anytype,
|
scalar: anytype,
|
||||||
) Vector(len, Scalar(T, d.add(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) {
|
) Vector(len, Scalar(
|
||||||
var res: Vector(len, Scalar(T, d.add(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) = undefined;
|
T,
|
||||||
|
dims.add(@TypeOf(scalar).dims),
|
||||||
|
hlp.finerScales(Self, @TypeOf(scalar)),
|
||||||
|
)) {
|
||||||
|
var res: Vector(len, Scalar(T, d.add(@TypeOf(scalar).dims), hlp.finerScales(Self, @TypeOf(scalar)))) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i| {
|
||||||
const q = Q{ .value = v };
|
const q = Q{ .value = v };
|
||||||
res.data[i] = q.mulBy(scalar).value;
|
res.data[i] = q.mulBy(scalar).value;
|
||||||
@@ -100,38 +130,213 @@ pub fn Vector(comptime len: usize, comptime Q: type) type {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Standard dot product. Dimensions are summed (e.g., Force * Distance = Energy).
|
||||||
|
/// Returns a Scalar type with the combined dimensions and finest scale.
|
||||||
|
pub inline fn dot(self: Self, rhs: anytype) Scalar(
|
||||||
|
T,
|
||||||
|
dims.add(@TypeOf(rhs).dims),
|
||||||
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
|
) {
|
||||||
|
const Tr = @TypeOf(rhs);
|
||||||
|
|
||||||
|
var sum: T = 0;
|
||||||
|
inline for (self.data, 0..) |v, i| {
|
||||||
|
const q_lhs = Q{ .value = v };
|
||||||
|
const q_rhs = Tr.ScalarType{ .value = rhs.data[i] };
|
||||||
|
sum += q_lhs.mulBy(q_rhs).value;
|
||||||
|
}
|
||||||
|
return .{ .value = sum };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 3D Cross product. Dimensions are summed.
|
||||||
|
/// Only valid for vectors of length 3.
|
||||||
|
pub inline fn cross(self: Self, rhs: anytype) Vector(3, Scalar(
|
||||||
|
T,
|
||||||
|
dims.add(@TypeOf(rhs).dims),
|
||||||
|
hlp.finerScales(Self, @TypeOf(rhs)),
|
||||||
|
)) {
|
||||||
|
if (comptime len != 3)
|
||||||
|
@compileError("Cross product is only defined for Vector(3, ...)");
|
||||||
|
|
||||||
|
const Tr = @TypeOf(rhs);
|
||||||
|
const ResScalar = Scalar(T, d.add(Tr.dims), hlp.finerScales(Self, Tr));
|
||||||
|
const ResVec = Vector(3, ResScalar);
|
||||||
|
|
||||||
|
// Calculation: [y1*z2 - z1*y2, z1*x2 - x1*z2, x1*y2 - y1*x2]
|
||||||
|
const s1 = Q{ .value = self.data[0] };
|
||||||
|
const s2 = Q{ .value = self.data[1] };
|
||||||
|
const s3 = Q{ .value = self.data[2] };
|
||||||
|
|
||||||
|
const o1 = Tr.ScalarType{ .value = rhs.data[0] };
|
||||||
|
const o2 = Tr.ScalarType{ .value = rhs.data[1] };
|
||||||
|
const o3 = Tr.ScalarType{ .value = rhs.data[2] };
|
||||||
|
|
||||||
|
return ResVec{
|
||||||
|
.data = .{
|
||||||
|
s2.mulBy(o3).sub(s3.mulBy(o2)).value,
|
||||||
|
s3.mulBy(o1).sub(s1.mulBy(o3)).value,
|
||||||
|
s1.mulBy(o2).sub(s2.mulBy(o1)).value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub fn negate(self: Self) Self {
|
||||||
var res: Self = undefined;
|
var res: Self = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i|
|
||||||
res.data[i] = -v;
|
res.data[i] = -v;
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert all components to a compatible scalar type. Compile error on dimension mismatch.
|
||||||
pub inline fn to(self: Self, comptime DestQ: type) Vector(len, DestQ) {
|
pub inline fn to(self: Self, comptime DestQ: type) Vector(len, DestQ) {
|
||||||
var res: Vector(len, DestQ) = undefined;
|
var res: Vector(len, DestQ) = undefined;
|
||||||
inline for (self.data, 0..) |v, i| {
|
inline for (self.data, 0..) |v, i|
|
||||||
res.data[i] = (Q{ .value = v }).to(DestQ).value;
|
res.data[i] = (Q{ .value = v }).to(DestQ).value;
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sum of squared components. Cheaper than `length` — use for comparisons.
|
||||||
pub inline fn lengthSqr(self: Self) T {
|
pub inline fn lengthSqr(self: Self) T {
|
||||||
var sum: T = 0;
|
var sum: T = 0;
|
||||||
inline for (self.data) |v| {
|
inline for (self.data) |v|
|
||||||
sum += v * v;
|
sum += v * v;
|
||||||
}
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Euclidean length. Integer types use integer sqrt (truncated).
|
||||||
pub inline fn length(self: Self) T {
|
pub inline fn length(self: Self) T {
|
||||||
const len_sq = self.lengthSqr();
|
const len_sq = self.lengthSqr();
|
||||||
|
|
||||||
if (comptime @typeInfo(T) == .int) {
|
if (comptime @typeInfo(T) == .int) {
|
||||||
// Construct the unsigned equivalent of T at comptime (e.g., i32 -> u32)
|
|
||||||
const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits);
|
const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits);
|
||||||
|
|
||||||
// len_sq is always positive, so @intCast is perfectly safe
|
|
||||||
const u_len_sq = @as(UnsignedT, @intCast(len_sq));
|
const u_len_sq = @as(UnsignedT, @intCast(len_sq));
|
||||||
return @as(T, @intCast(std.math.sqrt(u_len_sq)));
|
return @as(T, @intCast(std.math.sqrt(u_len_sq)));
|
||||||
} else {
|
} else {
|
||||||
@@ -316,3 +521,76 @@ test "VecX Length" {
|
|||||||
try std.testing.expectApproxEqAbs(@as(f32, 25.0), v_float.lengthSqr(), 1e-4);
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Vector Dot and Cross Products" {
|
||||||
|
const Meter = Scalar(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{}));
|
||||||
|
const Newton = Scalar(f32, Dimensions.init(.{ .M = 1, .L = 1, .T = -2 }), Scales.init(.{}));
|
||||||
|
|
||||||
|
const pos = Meter.Vec3{ .data = .{ 10.0, 0.0, 0.0 } };
|
||||||
|
const force = Newton.Vec3{ .data = .{ 5.0, 5.0, 0.0 } };
|
||||||
|
|
||||||
|
// 1. Dot Product (Work = F dot d)
|
||||||
|
const work = force.dot(pos);
|
||||||
|
try std.testing.expectEqual(50.0, work.value);
|
||||||
|
// Dimensions should be M¹L²T⁻² (Energy/Joules)
|
||||||
|
try std.testing.expectEqual(1, @TypeOf(work).dims.get(.M));
|
||||||
|
try std.testing.expectEqual(2, @TypeOf(work).dims.get(.L));
|
||||||
|
try std.testing.expectEqual(-2, @TypeOf(work).dims.get(.T));
|
||||||
|
|
||||||
|
// 2. Cross Product (Torque = r cross F)
|
||||||
|
const torque = pos.cross(force);
|
||||||
|
try std.testing.expectEqual(0.0, torque.data[0]);
|
||||||
|
try std.testing.expectEqual(0.0, torque.data[1]);
|
||||||
|
try std.testing.expectEqual(50.0, torque.data[2]);
|
||||||
|
// Torque dimensions are same as Energy but as a Vector
|
||||||
|
try std.testing.expectEqual(2, @TypeOf(torque).dims.get(.L));
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,3 +30,30 @@ pub fn printSuperscript(writer: *std.Io.Writer, n: i32) !void {
|
|||||||
try writer.writeAll(s);
|
try writer.writeAll(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Scales = @import("Scales.zig");
|
||||||
|
const Dimensions = @import("Dimensions.zig");
|
||||||
|
const Dimension = @import("Dimensions.zig").Dimension;
|
||||||
|
|
||||||
|
pub fn finerScales(comptime T1: type, comptime T2: type) Scales {
|
||||||
|
const d1: Dimensions = T1.dims;
|
||||||
|
const d2: Dimensions = T2.dims;
|
||||||
|
const s1: Scales = T1.scales;
|
||||||
|
const s2: Scales = T2.scales;
|
||||||
|
comptime var out = Scales.initFill(.none);
|
||||||
|
inline for (std.enums.values(Dimension)) |dim| {
|
||||||
|
const scale1 = comptime s1.get(dim);
|
||||||
|
const scale2 = comptime s2.get(dim);
|
||||||
|
out.set(dim, if (comptime d1.get(dim) == 0 and d2.get(dim) == 0)
|
||||||
|
.none
|
||||||
|
else if (comptime d1.get(dim) == 0)
|
||||||
|
scale2
|
||||||
|
else if (comptime d2.get(dim) == 0)
|
||||||
|
scale1
|
||||||
|
else if (comptime scale1.getFactor() > scale2.getFactor())
|
||||||
|
scale2
|
||||||
|
else
|
||||||
|
scale1);
|
||||||
|
}
|
||||||
|
comptime return out;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user