From e707190539fde2632e7df4dcaf080b92edd0f710 Mon Sep 17 00:00:00 2001 From: Zack Puhl Date: Sat, 2 Aug 2025 16:53:36 -0400 Subject: [PATCH] Add `@intlog2` for Floored CT log-base2 (#2355) * Add `@intlog2` for Floored CT log-base2 --------- Co-authored-by: Christoffer Lerno --- lib/std/math/math.c3 | 17 +++++++++++++++++ releasenotes.md | 1 + test/unit/stdlib/math/math.c3 | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/lib/std/math/math.c3 b/lib/std/math/math.c3 index 383b8109d..6719c2d95 100644 --- a/lib/std/math/math.c3 +++ b/lib/std/math/math.c3 @@ -344,6 +344,23 @@ macro log(x, base) *> macro log2(x) => $$log2(values::promote_int(x)); +<* + @require values::@is_int($x) : `The input value must be an integer.` + @require $x >= 0 : `The input value must be a positive integer.` + @return `A floored base-2 log of an input integer value.` +*> +macro @intlog2($x) +{ + $if $x <= 1: + return 0; + $endif + + $typeof($x) $z = 0; + $for var $y = $x; $y > 0; $y >>= 1, ++$z: $endfor + + return $z - 1; +} + <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector` *> diff --git a/releasenotes.md b/releasenotes.md index d3724f558..04dd5645c 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -4,6 +4,7 @@ ### Changes / improvements - Support `alias foo = module std::io` module aliasing. +- Add compile-time `@intlog2` macro to math. ### Fixes - List.remove_at would incorrectly trigger ASAN. diff --git a/test/unit/stdlib/math/math.c3 b/test/unit/stdlib/math/math.c3 index 0dfd56528..35eec9b1d 100644 --- a/test/unit/stdlib/math/math.c3 +++ b/test/unit/stdlib/math/math.c3 @@ -457,6 +457,24 @@ fn void test_log() @test } } +fn void test_ct_intlog2() @test @if($feature(SLOW_TESTS)) +{ + uint128 actual, expected; + $for var $x = 0; $x <= 128; ++$x : + expected = (uint128)math::floor(math::log2($x)); + actual = (uint128)math::@intlog2($x); + assert(expected == actual, "input %d: floor(log2($x)) -> %d is not equal to @intlog2($x) -> %d", $x, expected, actual); + $endfor + + var $logme = (uint128)1; + $for var $x = 0; $x < 8192; ++$x : + $logme *= 13; + expected = (uint128)math::floor(math::log2((uint128)$logme)); + actual = (uint128)math::@intlog2((uint128)$logme); + assert(expected == actual, "input %d (idx %d): floor(log2(|$logme|)) -> %d is not equal to @intlog2(|$logme|) -> %d", $logme, $x, expected, actual); + $endfor +} + fn void test_pow() @test { int[<10>] e = { 2, 1, 0, -1, -2, 2, 1, 0, -1, -2 };