diff options
Diffstat (limited to 'lib/std/math.zig')
| -rw-r--r-- | lib/std/math.zig | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/lib/std/math.zig b/lib/std/math.zig index 33b4245b6b..d223b79484 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -428,6 +428,100 @@ pub const min3 = @compileError("deprecated; use @min instead"); pub const max3 = @compileError("deprecated; use @max instead"); pub const ln = @compileError("deprecated; use @log instead"); +/// Odd sawtooth function +/// ``` +/// | +/// / | / / +/// / |/ / +/// --/----/----/-- +/// / /| / +/// / / | / +/// | +/// ``` +/// Limit x to the half-open interval [-r, r). +pub fn wrap(x: anytype, r: anytype) @TypeOf(x) { + const info_x = @typeInfo(@TypeOf(x)); + const info_r = @typeInfo(@TypeOf(r)); + if (info_x == .Int and info_x.Int.signedness != .signed) { + @compileError("x must be floating point, comptime integer, or signed integer."); + } + switch (info_r) { + .Int => { + // in the rare usecase of r not being comptime_int or float, + // take the penalty of having an intermediary type conversion, + // otherwise the alternative is to unwind iteratively to avoid overflow + const R = comptime do: { + var info = info_r; + info.Int.bits += 1; + info.Int.signedness = .signed; + break :do @Type(info); + }; + const radius: if (info_r.Int.signedness == .signed) @TypeOf(r) else R = r; + return @intCast(@mod(x - radius, 2 * @as(R, r)) - r); // provably impossible to overflow + }, + else => { + return @mod(x - r, 2 * r) - r; + }, + } +} +test "wrap" { + // Within range + try testing.expect(wrap(@as(i32, -75), @as(i32, 180)) == -75); + try testing.expect(wrap(@as(i32, -75), @as(i32, -180)) == -75); + // Below + try testing.expect(wrap(@as(i32, -225), @as(i32, 180)) == 135); + try testing.expect(wrap(@as(i32, -225), @as(i32, -180)) == 135); + // Above + try testing.expect(wrap(@as(i32, 361), @as(i32, 180)) == 1); + try testing.expect(wrap(@as(i32, 361), @as(i32, -180)) == 1); + + // One period, right limit, positive r + try testing.expect(wrap(@as(i32, 180), @as(i32, 180)) == -180); + // One period, left limit, positive r + try testing.expect(wrap(@as(i32, -180), @as(i32, 180)) == -180); + // One period, right limit, negative r + try testing.expect(wrap(@as(i32, 180), @as(i32, -180)) == 180); + // One period, left limit, negative r + try testing.expect(wrap(@as(i32, -180), @as(i32, -180)) == 180); + + // Two periods, right limit, positive r + try testing.expect(wrap(@as(i32, 540), @as(i32, 180)) == -180); + // Two periods, left limit, positive r + try testing.expect(wrap(@as(i32, -540), @as(i32, 180)) == -180); + // Two periods, right limit, negative r + try testing.expect(wrap(@as(i32, 540), @as(i32, -180)) == 180); + // Two periods, left limit, negative r + try testing.expect(wrap(@as(i32, -540), @as(i32, -180)) == 180); + + // Floating point + try testing.expect(wrap(@as(f32, 1.125), @as(f32, 1.0)) == -0.875); + try testing.expect(wrap(@as(f32, -127.5), @as(f32, 180)) == -127.5); + + // Mix of comptime and non-comptime + var i: i32 = 1; + _ = &i; + try testing.expect(wrap(i, 10) == 1); +} +test wrap { + const limit: i32 = 180; + // Within range + try testing.expect(wrap(@as(i32, -75), limit) == -75); + // Below + try testing.expect(wrap(@as(i32, -225), limit) == 135); + // Above + try testing.expect(wrap(@as(i32, 361), limit) == 1); +} + +/// Odd ramp function +/// ``` +/// | _____ +/// | / +/// |/ +/// -------/------- +/// /| +/// _____/ | +/// | +/// ``` /// Limit val to the inclusive range [lower, upper]. pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) { assert(lower <= upper); |
