# `nupp.math` `nupp.math` adds the scalar, fixed-width and two-dimensional operations missing from Lua's built-in `math` table. It is pure generated Lua and adds no native dependency. ```nupp:playground assert(nupp.math.lerp(10, 20, 0.25) == 12.5) local turn = nupp.math.deltaAngle(math.rad(350), math.rad(10)) assert(math.abs(math.deg(turn) - 20) < 0.000001) ``` ## Scalar operations `lerp(from, to, t)` interpolates linearly without clamping `t`, so factors outside `[0, 1]` extrapolate. It answers `from` exactly at zero and `to` exactly at one, because the endpoints are answered rather than computed. `wrapAngle(radians)` answers the equivalent angle in `[-π, π)`, and `deltaAngle(from, to)` answers the shortest signed rotation from one angle to another. ## Fixed-width arithmetic `float`, `int32`, and `uint32` are unboxed refinements of Lua numbers. They widen to `number` without code, while entering one requires an exact literal, a reified load, an explicit conversion, or another established fixed-width value. The erased assertion `as` changes a static claim but does not establish the value. ```nupp local flags: uint32 = 0x12 local rotated = nupp.math.u32.rotateLeft(flags, 7) ``` ### Establishing a fixed-width value The establishing conversions are `nupp.math.f32.narrow(number)`, `nupp.math.i32.wrap(integer)`, and `nupp.math.u32.wrap(integer)`. Ordinary arithmetic keeps LuaJIT's numeric meaning, so no refinement survives it. Use the `f32`, `i32`, or `u32` namespace when the operation's width is part of its contract: ```nupp local distance = nupp.math.f32.narrow(10 / 3) local rate = nupp.math.f32.narrow(60) local speed = nupp.math.f32.mul(distance, rate) ``` What does survive is being whole. `int32` and `uint32` widen to `integer`, so ordinary arithmetic over them gives an `integer`. That is what lets a `uint32` cursor index a view as `cursor + 1`. `float` widens to `number` and its arithmetic gives one. Aliasing a standard member preserves its intrinsic identity. A local that merely reuses the name is an ordinary call and receives no fixed-width intrinsic treatment. ### Integer namespaces `i32` and `u32` wrap modulo 2^32. Shift counts are masked by 31, and each operation states whether its interpretation is signed or unsigned, as `shiftRightArithmetic` against `shiftRightLogical`. Calls use Lua numbers in canonical ranges rather than allocating scalar cdata. ```nupp assert(nupp.math.u32.shiftRightLogical(nupp.math.u32.wrap(-1), 24) == 255) ``` `u32` also carries `popcount`, `trailingZeros` and `leadingZeros`, which LuaJIT's `bit` library does not. ### Binary32 namespace `f32` rounds every input and result to nearest, ties to even. It preserves signed zero, subnormals, and infinities, canonicalizes NaNs, and makes `fma` one fused operation. `fromBits` and `toBits` expose that canonical bit contract. `f32.narrow` performs one binary32 store and load without changing a NaN payload, and `f32.round` retains the canonical-NaN contract. ### Narrower storage names The `int8`, `int16`, `uint8`, and `uint16` names describe physical storage rather than ordinary values. See [Numbers](../../../learn/language/types/primitives/index.html#numbers) for the positions they are allowed in and how a load behaves. ## Two-dimensional vectors `nupp.math.vec2` represents a vector as an `(x, y)` number pair rather than an allocated object, so multiple return values compose directly: ```nupp local vec2 = nupp.math.vec2 local x, y = vec2.normalize(3, 4) assert(x == 0.6 and y == 0.8) x, y = vec2.rotate(x, y, math.pi / 2) local projectedX, projectedY = vec2.project(x, y, 1, 0) ``` | Operation | Signature shape | | --- | --- | | `add`, `subtract` | (ax, ay, bx, by) -> x, y | | `scale` | (x, y, factor) -> x, y | | `dot`, `cross` | (ax, ay, bx, by) -> number | | `length`, `lengthSquared` | (x, y) -> number | | `distance`, `distanceSquared` | (ax, ay, bx, by) -> number | | `normalize` | (x, y) -> x, y | | `lerp` | (ax, ay, bx, by, t) -> x, y | | `moveTowards` | (ax, ay, bx, by, maxDistance) -> x, y | | `rotate` | (x, y, radians) -> x, y | | `angle` | (x, y) -> radians | | `angleBetween`, `signedAngleBetween` | (ax, ay, bx, by) -> radians | | `project`, `reflect` | (x, y, axisX, axisY) -> x, y | A zero vector has no direction, so the operations that would need one answer something defined rather than a NaN: `normalize(0, 0)` and projection onto the zero vector answer `(0, 0)`, an angle involving a zero vector is zero, and reflection across a zero normal answers the original vector. `moveTowards` snaps exactly to the destination when the remaining distance is within the requested step, and a nonpositive step leaves the start unchanged. `lerp` is unclamped, like the scalar one. Compare squared lengths and distances where only the ordering matters, to avoid a square root that cannot change it. ## Submodules | Module | Description | | --- | --- | | `nupp.math.f32` | IEEE-754 binary32 arithmetic with explicit rounding. | | `nupp.math.i32` | Signed 32-bit wrapping arithmetic. | | `nupp.math.u32` | Unsigned 32-bit wrapping arithmetic. | | `nupp.math.vec2` | Two-dimensional vector operations over (x, y) number pairs. | ## Functions ### `deltaAngle` _function_ ```nupp local deltaAngle: function(from: number, to: number): number ``` Computes the shortest signed rotation from one angle to another. #### Arguments | Name | Type | Description | | --- | --- | --- | | `from` | `number` | the starting angle in radians | | `to` | `number` | the target angle in radians | #### Returns | Type | Description | | --- | --- | | `number` | the signed rotation in `[-pi, pi)` | ### `lerp` _function_ ```nupp local lerp: function(from: number, to: number, t: number): number ``` Linearly interpolates between two numbers without clamping the factor. #### Arguments | Name | Type | Description | | --- | --- | --- | | `from` | `number` | the value returned when `t` is zero | | `to` | `number` | the value returned when `t` is one | | `t` | `number` | the interpolation factor | #### Returns | Type | Description | | --- | --- | | `number` | `from + (to - from) * t` | ### `wrapAngle` _function_ ```nupp local wrapAngle: function(radians: number): number ``` Wraps an angle to the equivalent value in `[-pi, pi)`. #### Arguments | Name | Type | Description | | --- | --- | --- | | `radians` | `number` | the angle in radians | #### Returns | Type | Description | | --- | --- | | `number` | the wrapped angle |