authorgravatar for 77922942+expikr@users.noreply.github.comexpikr <77922942+expikr@users.noreply.github.com> 2024-01-15 09:45:39+08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-14 20:45:39-05:00
log1a7a711964c17b2e2b0ab5f404898bb729012db1
tree8d5fe069bdd4b072c474ed1daf3295e06a590fdf
parent8afafa717f5c036595a3a781c63b6be7b478c025
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.math: add `wrap` function (#18154)


1 files changed, 94 insertions(+), 0 deletions(-)

lib/std/math.zig+94
......@@ -428,6 +428,100 @@ pub const min3 = @compileError("deprecated; use @min instead");
428428pub const max3 = @compileError("deprecated; use @max instead");
429429pub const ln = @compileError("deprecated; use @log instead");
430430
431/// Odd sawtooth function
432/// ```
433/// |
434/// / | / /
435/// / |/ /
436/// --/----/----/--
437/// / /| /
438/// / / | /
439/// |
440/// ```
441/// Limit x to the half-open interval [-r, r).
442pub fn wrap(x: anytype, r: anytype) @TypeOf(x) {
443 const info_x = @typeInfo(@TypeOf(x));
444 const info_r = @typeInfo(@TypeOf(r));
445 if (info_x == .Int and info_x.Int.signedness != .signed) {
446 @compileError("x must be floating point, comptime integer, or signed integer.");
447 }
448 switch (info_r) {
449 .Int => {
450 // in the rare usecase of r not being comptime_int or float,
451 // take the penalty of having an intermediary type conversion,
452 // otherwise the alternative is to unwind iteratively to avoid overflow
453 const R = comptime do: {
454 var info = info_r;
455 info.Int.bits += 1;
456 info.Int.signedness = .signed;
457 break :do @Type(info);
458 };
459 const radius: if (info_r.Int.signedness == .signed) @TypeOf(r) else R = r;
460 return @intCast(@mod(x - radius, 2 * @as(R, r)) - r); // provably impossible to overflow
461 },
462 else => {
463 return @mod(x - r, 2 * r) - r;
464 },
465 }
466}
467test "wrap" {
468 // Within range
469 try testing.expect(wrap(@as(i32, -75), @as(i32, 180)) == -75);
470 try testing.expect(wrap(@as(i32, -75), @as(i32, -180)) == -75);
471 // Below
472 try testing.expect(wrap(@as(i32, -225), @as(i32, 180)) == 135);
473 try testing.expect(wrap(@as(i32, -225), @as(i32, -180)) == 135);
474 // Above
475 try testing.expect(wrap(@as(i32, 361), @as(i32, 180)) == 1);
476 try testing.expect(wrap(@as(i32, 361), @as(i32, -180)) == 1);
477
478 // One period, right limit, positive r
479 try testing.expect(wrap(@as(i32, 180), @as(i32, 180)) == -180);
480 // One period, left limit, positive r
481 try testing.expect(wrap(@as(i32, -180), @as(i32, 180)) == -180);
482 // One period, right limit, negative r
483 try testing.expect(wrap(@as(i32, 180), @as(i32, -180)) == 180);
484 // One period, left limit, negative r
485 try testing.expect(wrap(@as(i32, -180), @as(i32, -180)) == 180);
486
487 // Two periods, right limit, positive r
488 try testing.expect(wrap(@as(i32, 540), @as(i32, 180)) == -180);
489 // Two periods, left limit, positive r
490 try testing.expect(wrap(@as(i32, -540), @as(i32, 180)) == -180);
491 // Two periods, right limit, negative r
492 try testing.expect(wrap(@as(i32, 540), @as(i32, -180)) == 180);
493 // Two periods, left limit, negative r
494 try testing.expect(wrap(@as(i32, -540), @as(i32, -180)) == 180);
495
496 // Floating point
497 try testing.expect(wrap(@as(f32, 1.125), @as(f32, 1.0)) == -0.875);
498 try testing.expect(wrap(@as(f32, -127.5), @as(f32, 180)) == -127.5);
499
500 // Mix of comptime and non-comptime
501 var i: i32 = 1;
502 _ = &i;
503 try testing.expect(wrap(i, 10) == 1);
504}
505test wrap {
506 const limit: i32 = 180;
507 // Within range
508 try testing.expect(wrap(@as(i32, -75), limit) == -75);
509 // Below
510 try testing.expect(wrap(@as(i32, -225), limit) == 135);
511 // Above
512 try testing.expect(wrap(@as(i32, 361), limit) == 1);
513}
514
515/// Odd ramp function
516/// ```
517/// | _____
518/// | /
519/// |/
520/// -------/-------
521/// /|
522/// _____/ |
523/// |
524/// ```
431525/// Limit val to the inclusive range [lower, upper].
432526pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) {
433527 assert(lower <= upper);