arrow-buffer: i256: implement ilog - #9453
Conversation
| /// Returns `None` if `self` is less than or equal to zero, or if `base` is less than 2. | ||
| #[inline] | ||
| pub fn checked_ilog(self, base: i256) -> Option<u32> { | ||
| if self <= Self::ZERO || base < i256::from(2) { |
There was a problem hiding this comment.
Since these invariants are checked by the call to ExtI256::checked_ilog anyway, what's the point of restating the logic here? The to_ext_i256 calls should be no-ops anyway.
There was a problem hiding this comment.
The reason was to place checks closer to the caller. For procmacros of i256, it's less clear from the source code, but the documentation explicitly lists the required checks, and we repeat them in a docstring. Removed extra code.
| /// Returns `None` if `self` is less than or equal to zero. | ||
| #[inline] | ||
| pub fn checked_ilog10(self) -> Option<u32> { | ||
| // Switch to I256::checked_ilog10 when I256 switches MSRV |
There was a problem hiding this comment.
Why can't it be used now?
There was a problem hiding this comment.
checked_ilog10 is literally commented out in i256 code: Alexhuszagh/i256-rs@85c0bb1/src/int/checked.rs#L102
There was a problem hiding this comment.
Ah, I only checked checked_ilog2. My mistake :)
| /// Returns `None` if `self` is less than or equal to zero. | ||
| #[inline] | ||
| pub fn checked_ilog2(self) -> Option<u32> { | ||
| // Switch to I256::checked_ilog2 when I256 switches MSRV |
There was a problem hiding this comment.
Same as above: why not now?
There was a problem hiding this comment.
For checked_ilog2 - it exists, updated the call.
| // log10 should be 76 or 77 | ||
| let result = large.checked_ilog(i256::from(10)); | ||
| assert!(result.is_some()); | ||
| assert!(result.unwrap() >= 76 && result.unwrap() <= 77); |
There was a problem hiding this comment.
Why can't an assert_eq! be used here? Surely the result is deterministic?
|
I am not sure about adding a new dependency on the i256 crate. It seems that it is not widely used across the ecosystem https://crates.io/crates/i256 This is the kind of crate that I worry will end up having a RUSTSEC against it (due to unmaintained, etc) and I think will add an additional maintenance burden |
That's true - it is much less popular than other math libraries. I could suggest that the bnum crate is much more widely used and provides the required primitives. If we prefer to avoid a dependency on a smaller crate, it's pretty simple to implement arithmetic manually, as is already done with |
This would be my preference -- "a little copying vs a little of dependency" https://www.youtube.com/clip/UgkxWCEmMJFW0-TvSMzcMEAHZcpt2FsVXP65 |
Thank you, that's insightful! I'll update the implementation shortly. |
@alamb I've switched to the native i256 support without external deps. Could you please re-review? |
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) <noreply@anthropic.com>
`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) <noreply@anthropic.com>
alamb
left a comment
There was a problem hiding this comment.
Thanks @theirix and @Rafferty97 -- this is very close, but i think I found a bug (ilog(n, n) should be 1, not 0). Please see this PR:
Otherwise this looks good to me.
| if base <= Self::ONE { | ||
| return None; | ||
| } | ||
| if self <= base { |
There was a problem hiding this comment.
Shouldn't ilog(n, n) be 1 (not zero)?
There was a problem hiding this comment.
Update: yes I think it should -- here is a PR that fixes it on your forl:
arrow-buffer: i256: fix ilog off-by-one when self == base
Thank you very much, @alamb, it's a good spot! Merged your fix |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Thank you @theirix and @Rafferty97
I pushed a small fmt fix to get CI to pass and I think this looks good to me now
THanks again
Which issue does this PR close?
Rationale for this change
Implementation of integer logarithm. There is no matching
num_traitstrait, but this implementation provides a good motivation for such a trait.What changes are included in this PR?
Are these changes tested?
Are there any user-facing changes?
No