From 1cd9c48b4e32f89f04c222641d5ebf5a570488fc Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 2 Jun 2026 13:34:27 -0400 Subject: [PATCH 1/2] arrow-buffer: i256: add tests for ilog where self == base Extends the existing `test_ilog` with cases that the current tests do not cover: small results (0 and 1) for non-base-10 bases, including `self == base`. Per the std contract `n.ilog(n) == 1` (e.g. `2u32.ilog(2) == 1`), but `i256::checked_ilog` currently returns 0 for these inputs. Also cross-checks small results against `u128::ilog` as ground truth. These tests fail until the following fix is applied. Co-Authored-By: Claude Opus 4.8 (1M context) --- arrow-buffer/src/bigint/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arrow-buffer/src/bigint/mod.rs b/arrow-buffer/src/bigint/mod.rs index 9e291d140300..48789011b34f 100644 --- a/arrow-buffer/src/bigint/mod.rs +++ b/arrow-buffer/src/bigint/mod.rs @@ -1707,6 +1707,31 @@ mod tests { assert_eq!(i256::ZERO.checked_ilog10(), None); assert_eq!(i256::ZERO.checked_ilog2(), None); + // self == base, matches std: `n.ilog(n) == 1` + assert_eq!(i256::from(2).checked_ilog(i256::from(2)), Some(1)); + assert_eq!(i256::from(3).checked_ilog(i256::from(3)), Some(1)); + assert_eq!(i256::from(5).checked_ilog(i256::from(5)), Some(1)); + assert_eq!(i256::from(1000).checked_ilog(i256::from(1000)), Some(1)); + assert_eq!(i256::from(2).checked_ilog2(), Some(1)); + assert_eq!(i256::from(2).ilog2(), 1); + // base 10 goes through the checked_ilog10 fast path + assert_eq!(i256::from(10).checked_ilog(i256::from(10)), Some(1)); + + // self < base is 0 + assert_eq!(i256::from(3).checked_ilog(i256::from(5)), Some(0)); + + // cross-check small results (0 and 1) against u128::ilog + for base in [2i64, 3, 5, 7, 1000] { + for v in 1i64..64 { + let want = (v as u128).ilog(base as u128); + assert_eq!( + i256::from(v).checked_ilog(i256::from(base)), + Some(want), + "checked_ilog({v}, {base})" + ); + } + } + let value = i256::from_parts(100000000, 1234); assert_eq!(value.checked_ilog(i256::from(10)), Some(41)); assert_eq!(value.checked_ilog10(), Some(41)); From 3f8916143ec60ea64c0c4fe02bd2d053f34bfbdf Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 2 Jun 2026 13:34:49 -0400 Subject: [PATCH 2/2] arrow-buffer: i256: fix ilog off-by-one when self == base `checked_ilog` returned the early `Some(0)` for `self == base`, but the correct result is 1 (`base^1 == base <= self`), matching the std contract `n.ilog(n) == 1`. The base-10 path was unaffected since it is handled by `checked_ilog10`, so this affected every other base (including `ilog2(2)`). Narrow the early return from `self <= base` to `self < base`; the loop already computes the correct value for `self == base`. Co-Authored-By: Claude Opus 4.8 (1M context) --- arrow-buffer/src/bigint/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-buffer/src/bigint/mod.rs b/arrow-buffer/src/bigint/mod.rs index 48789011b34f..5ba87c51b2d1 100644 --- a/arrow-buffer/src/bigint/mod.rs +++ b/arrow-buffer/src/bigint/mod.rs @@ -630,7 +630,7 @@ impl i256 { if base <= Self::ONE { return None; } - if self <= base { + if self < base { return Some(0); }