diff --git a/README.md b/README.md index 1d5dc7f..87ff679 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A comptime-first dimensional analysis module for Zig. If you try to add meters to seconds, **it won't compile**. That's the point. -Born from a space simulation where `i128` positions were needed to avoid float imprecision far from the origin, this module grew into a full physical-unit type system with zero runtime overhead. +Started by a space simulation where `i128` positions were needed to avoid float imprecision far from the origin, this module grew into a full physical-unit type system with zero runtime overhead. > **Source:** [git.bouvais.lu/adrien/zig-dimal](https://git.bouvais.lu/adrien/zig-dimal) > **Minimum Zig version:** `0.16.0` @@ -18,6 +18,10 @@ Born from a space simulation where `i128` positions were needed to avoid float i - **Time scale support** — `min`, `hour`, `year` built in. - **Scalar and Vector types** — operate on individual values or fixed-size arrays with the same dimensional safety. - **Built-in physical quantities** — `dma.Base` provides ready-made types for `Velocity`, `Acceleration`, `Force`, `Energy`, `Pressure`, `ElectricCharge`, `ThermalConductivity`, and many more. +- **Comparison operations** — `eq`, `ne`, `gt`, `gte`, `lt`, `lte` on both `Scalar` and `Vector`, with automatic scale resolution. +- **Arithmetic with bare numbers** — multiply or divide a dimensioned value by a `comptime_int`, `comptime_float`, or plain `T` directly. The value is treated as dimensionless; dimensions pass through unchanged. +- **`abs`, `pow`, `sqrt`** — unary operations with correct dimension tracking (`pow(2)` on `L¹` → `L²`, etc.). +- **Vector geometry** — `dot` product (returns a `Scalar`), `cross` product (Vec3 only), element-wise `product` (all components multiplied). - **Rich formatting** — values print with their unit automatically: `9.81m.s⁻²`, `42m.kg.s⁻¹`, `0.172km`. - **`i128` support** — the whole reason this exists. Use large integers for high-precision fixed-point positions without manual conversion. - **Tests and benchmarks included** — run them and see how it performs on your machine (results welcome!). @@ -43,18 +47,7 @@ Born from a space simulation where `i128` positions were needed to avoid float i ### 1. Fetch the dependency ```sh -zig fetch --save git+https://git.bouvais.lu/adrien/zig-dimal#b9647e04266e3f395cfd26b41622b0c119a1e5be -``` - -This will add the following to your `build.zig.zon` automatically: - -```zig -.dependencies = .{ - .dimal = .{ - .url = "git+https://git.bouvais.lu/adrien/zig-dimal#b9647e04266e3f395cfd26b41622b0c119a1e5be", - .hash = "dimal-0.1.0-WNhSHvomAQAX1ISvq9ZBal-Gam6078y8hE67aC82l63V", - }, -}, +zig fetch --save git+https://git.bouvais.lu/adrien/zig-dimal#0.1.1 ``` ### 2. Wire it up in `build.zig` @@ -96,37 +89,37 @@ const Scales = dma.Scales; ### Defining unit types -A `Scalar` type is parameterized by three things: the numeric type (`f64`, `i128`, …), the dimensions (which physical quantities, and their exponents), and the scales (SI prefixes or custom time units). +A `Scalar` type is parameterized by three things: the numeric type (`f64`, `i128`, …), the dimensions (which physical quantities and their exponents), and the scales (SI prefixes or custom time units). Both the dimension and scale arguments are plain struct literals — no wrapper call needed. ```zig -const Meter = Scalar(f64, .init(.{ .L = 1 }), .init(.{})); -const NanoMeter = Scalar(i64, .init(.{ .L = 1 }), .init(.{ .L = .n })); -const KiloMeter = Scalar(f64, .init(.{ .L = 1 }), .init(.{ .L = .k })); -const Second = Scalar(f64, .init(.{ .T = 1 }), .init(.{})); -const Velocity = Scalar(f64, .init(.{ .L = 1, .T = -1 }), .init(.{})); -const Kmh = Scalar(f64, .init(.{ .L = 1, .T = -1 }), .init(.{ .L = .k, .T = .hour })); +const Meter = Scalar(f64, .{ .L = 1 }, .{}); +const NanoMeter = Scalar(i64, .{ .L = 1 }, .{ .L = .n }); +const KiloMeter = Scalar(f64, .{ .L = 1 }, .{ .L = .k }); +const Second = Scalar(f64, .{ .T = 1 }, .{}); +const Velocity = Scalar(f64, .{ .L = 1, .T = -1 }, .{}); +const Kmh = Scalar(f64, .{ .L = 1, .T = -1 }, .{ .L = .k, .T = .hour }); ``` Or use the pre-built helpers from `dma.Base`: ```zig const Acceleration = dma.Base.Acceleration.Of(f64); -const KmhSpeed = dma.Base.Speed.Scaled(f64, Scales.init(.{ .L = .k, .T = .hour })); +const KmhSpeed = dma.Base.Speed.Scaled(f64, .{ .L = .k, .T = .hour }); ``` ### Kinematics example ```zig -const v0 = Velocity{ .value = 10.0 }; // 10 m/s -const accel = Acceleration{ .value = 9.81 }; // 9.81 m/s² -const time = Second{ .value = 5.0 }; // 5 s +const v0 = Velocity{ .value = 10.0 }; // 10 m/s +const accel = Acceleration{ .value = 9.81 }; // 9.81 m/s² +const time = Second{ .value = 5.0 }; // 5 s // d = v₀t + ½at² -const d1 = v0.mulBy(time); // → Meter -const d2 = accel.mulBy(time.mulBy(time)).scale(0.5); // → Meter +const d1 = v0.mul(time); // → Meter +const d2 = accel.mul(time).mul(time).mul(0.5); // → Meter (bare 0.5 is dimensionless) const dist = d1.add(d2); -const v_final = v0.add(accel.mulBy(time)); +const v_final = v0.add(accel.mul(time)); std.debug.print("Distance: {d} | {d}\n", .{ dist, dist.to(KiloMeter) }); // Distance: 172.625m | 0.172625km @@ -147,22 +140,105 @@ const speed_ms = speed_kmh.to(Velocity); // 33.333... m/s — comptime ratio // const bad = speed_kmh.to(Second); // "Dimension mismatch in to: L1T-1 vs T1" ``` -### Working with Vectors +### Arithmetic with bare numbers -Every `Scalar` type exposes a `.Vec3` and a generic `.Vec(n)`: +Passing a `comptime_int`, `comptime_float`, or plain `T` to `mul` / `div` treats it as a dimensionless value. Dimensions pass through unchanged. ```zig -const Vec3Meter = Meter.Vec3; // or: Vector(3, Meter) +const Meter = Scalar(f64, .{ .L = 1 }, .{}); +const d = Meter{ .value = 6.0 }; + +const half = d.mul(0.5); // comptime_float → still Meter +const doubled = d.mul(2); // comptime_int → still Meter +const factor: f64 = 3.0; +const tripled = d.mul(factor); // runtime f64 → still Meter +``` + +### Comparisons + +`eq`, `ne`, `gt`, `gte`, `lt`, `lte` work on any two `Scalar` values of the **same dimension**. Scales are resolved automatically before comparing. + +```zig +const Meter = Scalar(i64, .{ .L = 1 }, .{}); +const KiloMeter = Scalar(i64, .{ .L = 1 }, .{ .L = .k }); + +const m1000 = Meter{ .value = 1000 }; +const km1 = KiloMeter{ .value = 1 }; +const km2 = KiloMeter{ .value = 2 }; + +_ = m1000.eq(km1); // true — same magnitude +_ = km2.gt(m1000); // true — 2 km > 1000 m +_ = m1000.lte(km2); // true + +// Comparing with a bare number works when the scalar is dimensionless. +// Comparing incompatible dimensions is a compile error. +``` + +### Unary operations: `abs`, `pow`, `sqrt` + +```zig +const Meter = Scalar(f64, .{ .L = 1 }, .{}); +const d = Meter{ .value = -4.0 }; + +const magnitude = d.abs(); // 4.0 m — dimension unchanged +const area = d.pow(2); // 16.0 m² — dims scaled by exponent +const side = area.sqrt(); // 4.0 m — dims halved (requires even exponents) +``` + +`pow` accepts any `comptime_int` exponent and adjusts the dimension exponents accordingly. `sqrt` is a compile error unless all dimension exponents are even. + +### Working with Vectors + +Every `Scalar` type exposes a `.Vec3` alias and a generic `.Vec(n)` type accessor: + +```zig +const Vec3Meter = Meter.Vec3; // equivalent to Vector(3, Meter) const pos = Vec3Meter{ .data = .{ 100, 200, 300 } }; const t = Second{ .value = 10 }; -const vel = pos.divByScalar(t); // → Vec3 of Velocity (m/s) - +const vel = pos.divScalar(t); // → Vec3 of Velocity (m/s) std.debug.print("{d}\n", .{vel}); // (10, 20, 30)m.s⁻¹ ``` -Vectors support: `add`, `sub`, `mulBy`, `divBy`, `mulByScalar`, `divByScalar`, `negate`, `to`, `length`, `lengthSqr`. +#### Dot and cross products + +```zig +const Newton = Scalar(f32, .{ .M = 1, .L = 1, .T = -2 }, .{}); + +const r = Meter.Vec3{ .data = .{ 10.0, 0.0, 0.0 } }; +const force = Newton.Vec3{ .data = .{ 5.0, 5.0, 0.0 } }; + +// Dot product — returns a Scalar (dimensions summed) +const work = force.dot(r); // 50.0 J (M¹L²T⁻²) + +// Cross product — returns a Vec3 (dimensions summed, Vec3 only) +const torque = r.cross(force); // (0, 0, 50) N·m +``` + +#### Vector comparisons + +Element-wise comparisons return `[len]bool`. Whole-vector equality uses `eqAll` / `neAll`. A single scalar can be broadcast with the `*Scalar` variants. + +```zig +const positions = Meter.Vec3{ .data = .{ 500.0, 1200.0, 3000.0 } }; +const threshold = KiloMeter{ .value = 1.0 }; // 1 km + +const exceeded = positions.gtScalar(threshold); // [false, true, true] +const eq_each = positions.eq(positions); // [true, true, true] (element-wise) +const all_same = positions.eqAll(positions); // true (whole-vector) +``` + +#### Other Vector operations + +```zig +const v = Meter.Vec3{ .data = .{ -2.0, 3.0, -4.0 } }; + +const v_abs = v.abs(); // { 2, 3, 4 } m +const vol = v_abs.product(); // 24 m³ (dims × len) +const area = v_abs.pow(2); // { 4, 9, 16 } m² +const sides = area.sqrt(); // { 2, 3, 4 } m (element-wise sqrt) +``` --- @@ -174,15 +250,45 @@ Vectors support: `add`, `sub`, `mulBy`, `divBy`, `mulByScalar`, `divByScalar`, ` |---|---| | `.add(rhs)` | Add two quantities of the same dimension. Auto-converts scales. | | `.sub(rhs)` | Subtract. Auto-converts scales. | -| `.mulBy(rhs)` | Multiply — dimensions are **summed**. `m * s⁻¹` → `m·s⁻¹`. | -| `.divBy(rhs)` | Divide — dimensions are **subtracted**. `m / s` → `m·s⁻¹`. | +| `.mul(rhs)` | Multiply — dimensions are **summed**. `rhs` may be a `Scalar`, `T`, `comptime_int`, or `comptime_float` (bare numbers are dimensionless). | +| `.div(rhs)` | Divide — dimensions are **subtracted**. Same `rhs` flexibility as `mul`. | +| `.abs()` | Absolute value. Dimensions and scales unchanged. | +| `.pow(exp)` | Raise to a `comptime_int` exponent. Dimension exponents are multiplied by `exp`. | +| `.sqrt()` | Square root. Compile error unless all dimension exponents are even. | +| `.eq(rhs)` / `.ne(rhs)` | Equality / inequality comparison. Scales auto-resolved. | +| `.gt(rhs)` / `.gte(rhs)` | Greater-than / greater-than-or-equal. | +| `.lt(rhs)` / `.lte(rhs)` | Less-than / less-than-or-equal. | | `.to(DestType)` | Convert to another unit of the same dimension. Compile error on mismatch. | -| `.vec3()` | Wrap the value in a `Vec3` of the same type. | -| `.Vec(n)` | Get the `Vector(n, Self)` type. | +| `.vec(len)` | Return a `Vector(len, Self)` with all components set to this value. | +| `.vec3()` | Shorthand for `.vec(3)`. | +| `.Vec3` | Type alias for `Vector(3, Self)`. | + +### `Vector(len, Q)` + +| Method | Description | +|---|---| +| `.add(rhs)` / `.sub(rhs)` | Element-wise add / subtract. | +| `.mul(rhs)` / `.div(rhs)` | Element-wise multiply / divide (both operands are Vectors). | +| `.mulScalar(s)` / `.divScalar(s)` | Scale every component by a single `Scalar`, `T`, `comptime_int`, or `comptime_float`. | +| `.dot(rhs)` | Dot product → `Scalar` with combined dimensions. | +| `.cross(rhs)` | Cross product → `Vector(3, …)`. Vec3 only. | +| `.abs()` | Element-wise absolute value. | +| `.pow(exp)` | Element-wise `comptime_int` power. Dimension exponents scaled. | +| `.sqrt()` | Element-wise square root. | +| `.product()` | Multiply all components → `Scalar` with dimensions × `len`. | +| `.negate()` | Negate all components. | +| `.length()` | Euclidean length (returns `T`). | +| `.lengthSqr()` | Sum of squared components (returns `T`). Cheaper than `length`. | +| `.eq(rhs)` / `.ne(rhs)` | Element-wise comparison → `[len]bool`. | +| `.gt(rhs)` / `.gte(rhs)` / `.lt(rhs)` / `.lte(rhs)` | Element-wise ordered comparisons → `[len]bool`. | +| `.eqAll(rhs)` / `.neAll(rhs)` | Whole-vector equality / inequality → `bool`. | +| `.eqScalar(s)` / `.neScalar(s)` | Broadcast scalar comparison → `[len]bool`. | +| `.gtScalar(s)` / `.gteScalar(s)` / `.ltScalar(s)` / `.lteScalar(s)` | Broadcast ordered scalar comparisons → `[len]bool`. | +| `.to(DestQ)` | Convert all components to a compatible scalar type. | ### `dma.Base` — Pre-built quantities -A selection of what's available (call `.Of(T)` for base units, `.Scaled(T, scales)` for custom scales): +Call `.Of(T)` for base-unit scalars, `.Scaled(T, scales)` for custom scales: `Meter`, `Second`, `Gramm`, `Kelvin`, `ElectricCurrent`, `Speed`, `Acceleration`, `Inertia`, `Force`, `Pressure`, `Energy`, `Power`, `Area`, `Volume`, `Density`, `Frequency`, `Viscosity`, `ElectricCharge`, `ElectricPotential`, `ElectricResistance`, `MagneticFlux`, `ThermalCapacity`, `ThermalConductivity`, and more. @@ -206,6 +312,8 @@ A selection of what's available (call `.Of(T)` for base units, `.Scaled(T, scale | `.hour` | 3600 | | `.year` | 31 536 000 | +Scale entries for dimensions with exponent `0` are ignored — multiplying a dimensionless value by a kilometre-scale value no longer accidentally inherits the `k` prefix. + --- ## Running Tests and Benchmarks @@ -221,7 +329,6 @@ Benchmark results are very welcome — feel free to share yours! ## Roadmap / Known Limitations -- More operations beyond `add`, `sub`, `mulBy`, `divBy` (e.g. `pow`, `sqrt`). - SIMD acceleration for `Vector` operations. - Some paths may still fall back to runtime computation — optimization ongoing. - More test coverage. diff --git a/src/Base.zig b/src/Base.zig index 90da1ab..c6b106e 100644 --- a/src/Base.zig +++ b/src/Base.zig @@ -153,12 +153,12 @@ test "BaseQuantities - Kinematics equations" { const t = Second.Of(f32){ .value = 2.0 }; // Velocity = Distance / Time - const v = d.divBy(t); + const v = d.div(t); try std.testing.expectEqual(25.0, v.value); try std.testing.expect(Speed.dims.eql(@TypeOf(v).dims)); // Acceleration = Velocity / Time - const a = v.divBy(t); + const a = v.div(t); try std.testing.expectEqual(12.5, a.value); try std.testing.expect(Acceleration.dims.eql(@TypeOf(a).dims)); } @@ -170,13 +170,13 @@ test "BaseQuantities - Dynamics (Force and Work)" { const a = Acceleration.Of(f32){ .value = 9.8 }; // Force = mass * acceleration - const f = m.mulBy(a); + const f = m.mul(a); try std.testing.expectEqual(98, f.value); try std.testing.expect(Force.dims.eql(@TypeOf(f).dims)); // Energy (Work) = Force * distance const distance = Meter.Of(f32){ .value = 5.0 }; - const energy = f.mulBy(distance); + const energy = f.mul(distance); try std.testing.expectEqual(490, energy.value); try std.testing.expect(Energy.dims.eql(@TypeOf(energy).dims)); } @@ -186,7 +186,7 @@ test "BaseQuantities - Electric combinations" { const time = Second.Of(f32){ .value = 3.0 }; // 3 s // Charge = Current * time - const charge = current.mulBy(time); + const charge = current.mul(time); try std.testing.expectEqual(6.0, charge.value); try std.testing.expect(ElectricCharge.dims.eql(@TypeOf(charge).dims)); } diff --git a/src/Dimensions.zig b/src/Dimensions.zig index 58852c7..e91e78d 100644 --- a/src/Dimensions.zig +++ b/src/Dimensions.zig @@ -75,7 +75,7 @@ pub fn argsOpt(self: Self) ArgOpts { return args; } -/// Add exponents component-wise. Used internally by `mulBy`. +/// Add exponents component-wise. Used internally by `mul`. pub fn add(comptime a: Self, comptime b: Self) Self { var result = Self.initFill(0); inline for (std.enums.values(Dimension)) |d| @@ -83,7 +83,7 @@ pub fn add(comptime a: Self, comptime b: Self) Self { return result; } -/// Subtract exponents component-wise. Used internally by `divBy`. +/// Subtract exponents component-wise. Used internally by `div`. pub fn sub(comptime a: Self, comptime b: Self) Self { var result = Self.initFill(0); inline for (std.enums.values(Dimension)) |d| diff --git a/src/Scalar.zig b/src/Scalar.zig index 6231b53..12a10f7 100644 --- a/src/Scalar.zig +++ b/src/Scalar.zig @@ -90,7 +90,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o /// Multiply two quantities. Dimension exponents are summed: `L¹ * T⁻¹ → L¹T⁻¹`. /// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float` /// (bare numbers are treated as dimensionless — dimensions pass through unchanged). - pub inline fn mulBy(self: Self, r: anytype) Scalar( + pub inline fn mul(self: Self, r: anytype) Scalar( T, dims.add(RhsT(@TypeOf(r)).dims).argsOpt(), hlp.finerScales(Self, RhsT(@TypeOf(r))).argsOpt(), @@ -110,7 +110,7 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o /// Divide two quantities. Dimension exponents are subtracted: `L¹ / T¹ → L¹T⁻¹`. /// Integer types use truncating division. /// `rhs` may be a Scalar, `T`, `comptime_int`, or `comptime_float`. - pub inline fn divBy(self: Self, r: anytype) Scalar( + pub inline fn div(self: Self, r: anytype) Scalar( T, dims.sub(RhsT(@TypeOf(r)).dims).argsOpt(), hlp.finerScales(Self, RhsT(@TypeOf(r))).argsOpt(), @@ -193,10 +193,10 @@ pub fn Scalar(comptime T: type, comptime d_opt: Dimensions.ArgOpts, comptime s_o const mult: DestT = comptime @intFromFloat(ratio); return .{ .value = @as(DestT, @intCast(self.value)) * mult }; } else if (comptime ratio < 1.0 and @round(1.0 / ratio) == 1.0 / ratio) { - const div: DestT = comptime @intFromFloat(1.0 / ratio); + const d: DestT = comptime @intFromFloat(1.0 / ratio); const val = @as(DestT, @intCast(self.value)); - const half = comptime div / 2; - const rounded = if (val >= 0) @divTrunc(val + half, div) else @divTrunc(val - half, div); + const half = comptime d / 2; + const rounded = if (val >= 0) @divTrunc(val + half, d) else @divTrunc(val - half, d); return .{ .value = rounded }; } } @@ -473,13 +473,13 @@ test "MulBy" { const d = Meter{ .value = 3.0 }; const t = Second{ .value = 4.0 }; - const area_time = d.mulBy(t); + const area_time = d.mul(t); try std.testing.expectEqual(12, area_time.value); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L)); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T)); const d2 = Meter{ .value = 5.0 }; - const area = d.mulBy(d2); + const area = d.mul(d2); try std.testing.expectEqual(15, area.value); try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L)); try std.testing.expectEqual(0, @TypeOf(area).dims.get(.T)); @@ -491,7 +491,7 @@ test "MulBy with scale" { const dist = KiloMeter{ .value = 2.0 }; const mass = KiloGram{ .value = 3.0 }; - const prod = dist.mulBy(mass); + const prod = dist.mul(mass); try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.L)); try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.M)); } @@ -505,8 +505,8 @@ test "MulBy with type change" { const d = Meter{ .value = 3.0 }; const t = Second{ .value = 4.0 }; - const area_time = d.mulBy(t).to(KmSec); - const area_time_f = d.mulBy(t).to(KmSec_f); + const area_time = d.mul(t).to(KmSec); + const area_time_f = d.mul(t).to(KmSec_f); try std.testing.expectEqual(12, area_time.value); try std.testing.expectApproxEqAbs(12, area_time_f.value, 0.0001); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L)); @@ -520,7 +520,7 @@ test "MulBy small" { const d = Meter{ .value = 3.0 }; const t = Second{ .value = 4.0 }; - const area_time = d.mulBy(t); + const area_time = d.mul(t); try std.testing.expectEqual(12, area_time.value); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.L)); try std.testing.expectEqual(1, @TypeOf(area_time).dims.get(.T)); @@ -531,7 +531,7 @@ test "MulBy dimensionless" { const Meter = Scalar(i128, .{ .L = 1 }, .{}); const d = Meter{ .value = 7 }; - const scaled = d.mulBy(DimLess{ .value = 3 }); + const scaled = d.mul(DimLess{ .value = 3 }); try std.testing.expectEqual(21, scaled.value); try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); } @@ -562,13 +562,13 @@ test "Chained: velocity and acceleration" { const dist = Meter{ .value = 100.0 }; const t1 = Second{ .value = 5.0 }; - const velocity = dist.divBy(t1); + const velocity = dist.div(t1); try std.testing.expectEqual(20, velocity.value); try std.testing.expectEqual(1, @TypeOf(velocity).dims.get(.L)); try std.testing.expectEqual(-1, @TypeOf(velocity).dims.get(.T)); const t2 = Second{ .value = 4.0 }; - const accel = velocity.divBy(t2); + const accel = velocity.div(t2); try std.testing.expectEqual(5, accel.value); try std.testing.expectEqual(1, @TypeOf(accel).dims.get(.L)); try std.testing.expectEqual(-2, @TypeOf(accel).dims.get(.T)); @@ -580,7 +580,7 @@ test "DivBy integer exact" { const dist = Meter{ .value = 120 }; const time = Second{ .value = 4 }; - const vel = dist.divBy(time); + const vel = dist.div(time); try std.testing.expectEqual(30, vel.value); try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L)); @@ -593,7 +593,7 @@ test "Finer scales skip dim 0" { const r = Dimless{ .value = 30 }; const time = KiloMetre{ .value = 4 }; - const vel = r.mulBy(time); + const vel = r.mul(time); try std.testing.expectEqual(120, vel.value); try std.testing.expectEqual(Scales.UnitScale.k, @TypeOf(vel).scales.get(.L)); @@ -690,49 +690,49 @@ test "Pow" { try std.testing.expectEqual(3, @TypeOf(area_f).dims.get(.L)); } -test "mulBy comptime_int" { +test "mul comptime_int" { const Meter = Scalar(i128, .{ .L = 1 }, .{}); const d = Meter{ .value = 7 }; - const scaled = d.mulBy(3); // comptime_int → dimensionless + const scaled = d.mul(3); // comptime_int → dimensionless try std.testing.expectEqual(21, scaled.value); try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); try std.testing.expectEqual(0, @TypeOf(scaled).dims.get(.T)); } -test "mulBy comptime_float" { +test "mul comptime_float" { const MeterF = Scalar(f64, .{ .L = 1 }, .{}); const d = MeterF{ .value = 4.0 }; - const scaled = d.mulBy(2.5); // comptime_float → dimensionless + const scaled = d.mul(2.5); // comptime_float → dimensionless try std.testing.expectApproxEqAbs(10.0, scaled.value, 1e-9); try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); } -test "mulBy T (value type)" { +test "mul T (value type)" { const MeterF = Scalar(f32, .{ .L = 1 }, .{}); const d = MeterF{ .value = 6.0 }; const factor: f32 = 0.5; - const scaled = d.mulBy(factor); // bare f32 → dimensionless + const scaled = d.mul(factor); // bare f32 → dimensionless try std.testing.expectApproxEqAbs(3.0, scaled.value, 1e-6); try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); } -test "divBy comptime_int" { +test "div comptime_int" { const Meter = Scalar(i128, .{ .L = 1 }, .{}); const d = Meter{ .value = 100 }; - const half = d.divBy(4); // comptime_int → dimensionless divisor + const half = d.div(4); // comptime_int → dimensionless divisor try std.testing.expectEqual(25, half.value); try std.testing.expectEqual(1, @TypeOf(half).dims.get(.L)); } -test "divBy comptime_float" { +test "div comptime_float" { const MeterF = Scalar(f64, .{ .L = 1 }, .{}); const d = MeterF{ .value = 9.0 }; - const r = d.divBy(3.0); + const r = d.div(3.0); try std.testing.expectApproxEqAbs(3.0, r.value, 1e-9); try std.testing.expectEqual(1, @TypeOf(r).dims.get(.L)); } diff --git a/src/Vector.zig b/src/Vector.zig index 64da36b..2e2bdee 100644 --- a/src/Vector.zig +++ b/src/Vector.zig @@ -42,7 +42,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { }) @compileError( "Expected a Scalar or bare number; got a Vector. " ++ - "Use mulBy / divBy for element-wise vector operations.", + "Use mul / div for element-wise vector operations.", ); return hlp.rhsScalarType(T, Rhs); } @@ -94,7 +94,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { } /// Element-wise division. Dimension exponents are subtracted per component. - pub inline fn divBy( + pub inline fn div( self: Self, rhs: anytype, ) Vector(len, Scalar( @@ -109,14 +109,14 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(), )) = undefined; inline for (self.data, 0..) |v, i| { - const q = (Q{ .value = v }).divBy(Tr.ScalarType{ .value = rhs.data[i] }); + const q = (Q{ .value = v }).div(Tr.ScalarType{ .value = rhs.data[i] }); res.data[i] = q.value; } return res; } /// Element-wise multiplication. Dimension exponents are summed per component. - pub inline fn mulBy( + pub inline fn mul( self: Self, rhs: anytype, ) Vector(len, Scalar( @@ -131,7 +131,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { hlp.finerScales(Self, @TypeOf(rhs)).argsOpt(), )) = undefined; inline for (self.data, 0..) |v, i| { - const q = (Q{ .value = v }).mulBy(Tr.ScalarType{ .value = rhs.data[i] }); + const q = (Q{ .value = v }).mul(Tr.ScalarType{ .value = rhs.data[i] }); res.data[i] = q.value; } return res; @@ -144,7 +144,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { /// Divide every component by a single scalar. Dimensions are subtracted. /// `scalar` may be a Scalar, `T`, `comptime_int`, or `comptime_float`. - pub inline fn divByScalar( + pub inline fn divScalar( self: Self, scalar: anytype, ) Vector(len, Scalar( @@ -160,13 +160,13 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { hlp.finerScales(Self, SN).argsOpt(), )) = undefined; inline for (self.data, 0..) |v, i| - res.data[i] = (Q{ .value = v }).divBy(s_norm).value; + res.data[i] = (Q{ .value = v }).div(s_norm).value; return res; } /// Multiply every component by a single scalar. Dimensions are summed. /// `scalar` may be a Scalar, `T`, `comptime_int`, or `comptime_float`. - pub inline fn mulByScalar( + pub inline fn mulScalar( self: Self, scalar: anytype, ) Vector(len, Scalar( @@ -182,7 +182,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { hlp.finerScales(Self, SN).argsOpt(), )) = undefined; inline for (self.data, 0..) |v, i| - res.data[i] = (Q{ .value = v }).mulBy(s_norm).value; + res.data[i] = (Q{ .value = v }).mul(s_norm).value; return res; } @@ -203,7 +203,7 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { 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; + sum += q_lhs.mul(q_rhs).value; } return .{ .value = sum }; } @@ -233,9 +233,9 @@ pub fn Vector(comptime len: usize, comptime Q: type) type { 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, + s2.mul(o3).sub(s3.mul(o2)).value, + s3.mul(o1).sub(s1.mul(o3)).value, + s1.mul(o2).sub(s2.mul(o1)).value, }, }; } @@ -591,7 +591,7 @@ test "VecX Kinematics (Scalar Mul/Div)" { const time = Second{ .value = 10 }; // Vector divided by scalar (Velocity = Position / Time) - const vel = pos.divByScalar(time); + const vel = pos.divScalar(time); try std.testing.expectEqual(10, vel.data[0]); try std.testing.expectEqual(20, vel.data[1]); try std.testing.expectEqual(30, vel.data[2]); @@ -599,7 +599,7 @@ test "VecX Kinematics (Scalar Mul/Div)" { try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T)); // Vector multiplied by scalar (Position = Velocity * Time) - const new_pos = vel.mulByScalar(time); + const new_pos = vel.mulScalar(time); try std.testing.expectEqual(100, new_pos.data[0]); try std.testing.expectEqual(200, new_pos.data[1]); try std.testing.expectEqual(300, new_pos.data[2]); @@ -615,7 +615,7 @@ test "VecX Element-wise Math and Scaling" { const v2 = Vec3M{ .data = .{ 2, 5, 10 } }; // Element-wise division - const div = v1.divBy(v2); + const div = v1.div(v2); try std.testing.expectEqual(5, div.data[0]); try std.testing.expectEqual(4, div.data[1]); try std.testing.expectEqual(3, div.data[2]); @@ -755,11 +755,11 @@ test "Vector Abs, Pow, Sqrt and Product" { try std.testing.expectEqual(2, @TypeOf(sqrted).dims.get(.L)); } -test "mulByScalar comptime_int" { +test "mulScalar comptime_int" { const Meter = Scalar(i32, .{ .L = 1 }, .{}); const v = Meter.Vec3{ .data = .{ 1, 2, 3 } }; - const scaled = v.mulByScalar(10); // comptime_int → dimensionless + const scaled = v.mulScalar(10); // comptime_int → dimensionless try std.testing.expectEqual(10, scaled.data[0]); try std.testing.expectEqual(20, scaled.data[1]); try std.testing.expectEqual(30, scaled.data[2]); @@ -768,45 +768,45 @@ test "mulByScalar comptime_int" { try std.testing.expectEqual(0, @TypeOf(scaled).dims.get(.T)); } -test "mulByScalar comptime_float" { +test "mulScalar comptime_float" { const MeterF = Scalar(f32, .{ .L = 1 }, .{}); const v = MeterF.Vec3{ .data = .{ 1.0, 2.0, 4.0 } }; - const scaled = v.mulByScalar(0.5); // comptime_float → dimensionless + const scaled = v.mulScalar(0.5); // comptime_float → dimensionless try std.testing.expectApproxEqAbs(0.5, scaled.data[0], 1e-6); try std.testing.expectApproxEqAbs(1.0, scaled.data[1], 1e-6); try std.testing.expectApproxEqAbs(2.0, scaled.data[2], 1e-6); try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); } -test "mulByScalar T (value type)" { +test "mulScalar T (value type)" { const MeterF = Scalar(f32, .{ .L = 1 }, .{}); const v = MeterF.Vec3{ .data = .{ 3.0, 6.0, 9.0 } }; const factor: f32 = 2.0; - const scaled = v.mulByScalar(factor); + const scaled = v.mulScalar(factor); try std.testing.expectApproxEqAbs(6.0, scaled.data[0], 1e-6); try std.testing.expectApproxEqAbs(12.0, scaled.data[1], 1e-6); try std.testing.expectApproxEqAbs(18.0, scaled.data[2], 1e-6); try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); } -test "divByScalar comptime_int" { +test "divScalar comptime_int" { const Meter = Scalar(i32, .{ .L = 1 }, .{}); const v = Meter.Vec3{ .data = .{ 10, 20, 30 } }; - const halved = v.divByScalar(2); // comptime_int → dimensionless divisor + const halved = v.divScalar(2); // comptime_int → dimensionless divisor try std.testing.expectEqual(5, halved.data[0]); try std.testing.expectEqual(10, halved.data[1]); try std.testing.expectEqual(15, halved.data[2]); try std.testing.expectEqual(1, @TypeOf(halved).dims.get(.L)); } -test "divByScalar comptime_float" { +test "divScalar comptime_float" { const MeterF = Scalar(f64, .{ .L = 1 }, .{}); const v = MeterF.Vec3{ .data = .{ 9.0, 6.0, 3.0 } }; - const r = v.divByScalar(3.0); + const r = v.divScalar(3.0); try std.testing.expectApproxEqAbs(3.0, r.data[0], 1e-9); try std.testing.expectApproxEqAbs(2.0, r.data[1], 1e-9); try std.testing.expectApproxEqAbs(1.0, r.data[2], 1e-9); diff --git a/src/benchmark.zig b/src/benchmark.zig index 1ffb7cc..3c80227 100644 --- a/src/benchmark.zig +++ b/src/benchmark.zig @@ -80,7 +80,7 @@ fn bench_Scalar(writer: *std.Io.Writer) !void { const Types = .{ i16, i32, i64, i128, i256, f32, f64 }; const TNames = .{ "i16", "i32", "i64", "i128", "i256", "f32", "f64" }; - const Ops = .{ "add", "sub", "mulBy", "divBy", "to", "abs", "pow", "eq", "gt", "mulBy(n)" }; + const Ops = .{ "add", "sub", "mul", "div", "to", "abs", "pow", "eq", "gt", "mul(n)" }; var results_matrix: [Ops.len][Types.len]f64 = undefined; @@ -103,10 +103,10 @@ fn bench_Scalar(writer: *std.Io.Writer) !void { (M{ .value = getVal(T, i, 63) }).add(M{ .value = getVal(T, i +% 7, 63) }) else if (comptime std.mem.eql(u8, op_name, "sub")) (M{ .value = getVal(T, i +% 10, 63) }).sub(M{ .value = getVal(T, i, 63) }) - else if (comptime std.mem.eql(u8, op_name, "mulBy")) - (M{ .value = getVal(T, i, 63) }).mulBy(M{ .value = getVal(T, i +% 1, 63) }) - else if (comptime std.mem.eql(u8, op_name, "divBy")) - (M{ .value = getVal(T, i +% 10, 63) }).divBy(S{ .value = getVal(T, i, 63) }) + else if (comptime std.mem.eql(u8, op_name, "mul")) + (M{ .value = getVal(T, i, 63) }).mul(M{ .value = getVal(T, i +% 1, 63) }) + else if (comptime std.mem.eql(u8, op_name, "div")) + (M{ .value = getVal(T, i +% 10, 63) }).div(S{ .value = getVal(T, i, 63) }) else if (comptime std.mem.eql(u8, op_name, "to")) (KM{ .value = getVal(T, i, 15) }).to(M) else if (comptime std.mem.eql(u8, op_name, "abs")) @@ -115,8 +115,8 @@ fn bench_Scalar(writer: *std.Io.Writer) !void { (M{ .value = getVal(T, i, 63) }).eq(M{ .value = getVal(T, i +% 3, 63) }) else if (comptime std.mem.eql(u8, op_name, "gt")) (M{ .value = getVal(T, i, 63) }).gt(M{ .value = getVal(T, i +% 3, 63) }) - else // "mulBy(n)" — bare comptime_int, dimensionless - (M{ .value = getVal(T, i, 63) }).mulBy(3); + else + (M{ .value = getVal(T, i, 63) }).mul(3); }, ); } @@ -171,7 +171,7 @@ fn bench_vsNative(writer: *std.Io.Writer) !void { const Types = .{ i32, i64, i128, f32, f64 }; const TNames = .{ "i32", "i64", "i128", "f32", "f64" }; - const Ops = .{ "add", "mulBy", "divBy" }; + const Ops = .{ "add", "mul", "div" }; try writer.print( \\ @@ -200,7 +200,7 @@ fn bench_vsNative(writer: *std.Io.Writer) !void { const b = getValT(T, 2); _ = if (comptime std.mem.eql(u8, op_name, "add")) a + b - else if (comptime std.mem.eql(u8, op_name, "mulBy")) + else if (comptime std.mem.eql(u8, op_name, "mul")) a * b else if (comptime @typeInfo(T) == .int) @divTrunc(a, b) else a / b; } @@ -211,14 +211,14 @@ fn bench_vsNative(writer: *std.Io.Writer) !void { const q_start = getTime(); for (0..ITERS) |i| { const qa = M{ .value = getValT(T, i) }; - const qb = if (comptime std.mem.eql(u8, op_name, "divBy")) S{ .value = getValT(T, 2) } else M{ .value = getValT(T, 2) }; + const qb = if (comptime std.mem.eql(u8, op_name, "div")) S{ .value = getValT(T, 2) } else M{ .value = getValT(T, 2) }; _ = if (comptime std.mem.eql(u8, op_name, "add")) qa.add(qb) - else if (comptime std.mem.eql(u8, op_name, "mulBy")) - qa.mulBy(qb) + else if (comptime std.mem.eql(u8, op_name, "mul")) + qa.mul(qb) else - qa.divBy(qb); + qa.div(qb); } const q_end = getTime(); quantity_total_ns += @as(f64, @floatFromInt(q_start.durationTo(q_end).toNanoseconds())); @@ -268,7 +268,7 @@ fn bench_crossTypeVsNative(writer: *std.Io.Writer) !void { const Types = .{ i16, i64, i128, f32, f64 }; const TNames = .{ "i16", "i64", "i128", "f32", "f64" }; - const Ops = .{ "add", "mulBy", "divBy" }; + const Ops = .{ "add", "mul", "div" }; try writer.print( \\ @@ -301,7 +301,7 @@ fn bench_crossTypeVsNative(writer: *std.Io.Writer) !void { _ = if (comptime std.mem.eql(u8, op_name, "add")) a + b - else if (comptime std.mem.eql(u8, op_name, "mulBy")) + else if (comptime std.mem.eql(u8, op_name, "mul")) a * b else if (comptime @typeInfo(T1) == .int) @divTrunc(a, b) @@ -315,17 +315,17 @@ fn bench_crossTypeVsNative(writer: *std.Io.Writer) !void { const q_start = getTime(); for (0..ITERS) |i| { const qa = M1{ .value = getValT(T1, i) }; - const qb = if (comptime std.mem.eql(u8, op_name, "divBy")) + const qb = if (comptime std.mem.eql(u8, op_name, "div")) S2{ .value = getValT(T2, 2) } else M2{ .value = getValT(T2, 2) }; _ = if (comptime std.mem.eql(u8, op_name, "add")) qa.add(qb) - else if (comptime std.mem.eql(u8, op_name, "mulBy")) - qa.mulBy(qb) + else if (comptime std.mem.eql(u8, op_name, "mul")) + qa.mul(qb) else - qa.divBy(qb); + qa.div(qb); } const q_end = getTime(); quantity_total_ns += @as(f64, @floatFromInt(q_start.durationTo(q_end).toNanoseconds())); @@ -387,7 +387,7 @@ fn bench_Vector(writer: *std.Io.Writer) !void { const TNames = .{ "i32", "i64", "i128", "f32", "f64" }; const Lengths = .{ 3, 4, 16 }; // "cross" is only valid for len=3; other cells will show " --- " - const Ops = .{ "add", "divBy", "mulByScalar", "dot", "cross", "product", "pow", "length" }; + const Ops = .{ "add", "div", "mulScalar", "dot", "cross", "product", "pow", "length" }; inline for (Ops, 0..) |op_name, o_idx| { inline for (Types, TNames) |T, tname| { @@ -416,11 +416,11 @@ fn bench_Vector(writer: *std.Io.Writer) !void { if (comptime std.mem.eql(u8, op_name, "add")) { const v2 = V.initDefault(getVal(T, i +% 7, 63)); _ = v1.add(v2); - } else if (comptime std.mem.eql(u8, op_name, "divBy")) { - _ = v1.divBy(V.initDefault(getVal(T, i +% 2, 63))); - } else if (comptime std.mem.eql(u8, op_name, "mulByScalar")) { + } else if (comptime std.mem.eql(u8, op_name, "div")) { + _ = v1.div(V.initDefault(getVal(T, i +% 2, 63))); + } else if (comptime std.mem.eql(u8, op_name, "mulScalar")) { const s_val = Q_time{ .value = getVal(T, i +% 2, 63) }; - _ = v1.mulByScalar(s_val); + _ = v1.mulScalar(s_val); } else if (comptime std.mem.eql(u8, op_name, "dot")) { const v2 = V.initDefault(getVal(T, i +% 5, 63)); _ = v1.dot(v2);