| ... | @@ -428,6 +428,100 @@ pub const min3 = @compileError("deprecated; use @min instead"); | ... | @@ -428,6 +428,100 @@ pub const min3 = @compileError("deprecated; use @min instead"); |
| 428 | pub const max3 = @compileError("deprecated; use @max instead"); | 428 | pub const max3 = @compileError("deprecated; use @max instead"); |
| 429 | pub const ln = @compileError("deprecated; use @log instead"); | 429 | pub const ln = @compileError("deprecated; use @log instead"); |
| 430 | | 430 | |
| | 431 | /// Odd sawtooth function |
| | 432 | /// ``` |
| | 433 | /// | |
| | 434 | /// / | / / |
| | 435 | /// / |/ / |
| | 436 | /// --/----/----/-- |
| | 437 | /// / /| / |
| | 438 | /// / / | / |
| | 439 | /// | |
| | 440 | /// ``` |
| | 441 | /// Limit x to the half-open interval [-r, r). |
| | 442 | pub 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 | } |
| | 467 | test "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 | } |
| | 505 | test 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 | /// ``` |
| 431 | /// Limit val to the inclusive range [lower, upper]. | 525 | /// Limit val to the inclusive range [lower, upper]. |
| 432 | pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) { | 526 | pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) { |
| 433 | assert(lower <= upper); | 527 | assert(lower <= upper); |