-
Notifications
You must be signed in to change notification settings - Fork 847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix checked i256 arithmetic (#3942) (#3941) #3943
Conversation
@@ -243,9 +243,8 @@ impl i256 { | |||
/// Performs checked addition | |||
#[inline] | |||
pub fn checked_add(self, other: Self) -> Option<Self> { | |||
let (low, carry) = self.low.overflowing_add(other.low); | |||
let high = self.high.checked_add(other.high)?.checked_add(carry as _)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mistake this makes is assuming that addition is transitive, in the case of overflow it is not.
In particular consider the case
i256::from_parts(u128::MAX, i128::MIN)
.checked_add(i256::from_parts(1, -1))
.unwrap();
The carry is enough to increase i128::MIN
enough that adding -1
doesn't result in overflow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is tricky.
@@ -259,9 +258,8 @@ impl i256 { | |||
/// Performs checked subtraction | |||
#[inline] | |||
pub fn checked_sub(self, other: Self) -> Option<Self> { | |||
let (low, carry) = self.low.overflowing_sub(other.low); | |||
let high = self.high.checked_sub(other.high)?.checked_sub(carry as _)?; | |||
Some(Self { low, high }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider the case
i256::from_parts(0, i128::MAX).checked_sub(i256::MINUS_ONE).unwrap();
The carry is enough to reduce i128::MAX
enough that subtracting -1
does not result in overflow
@@ -303,13 +305,14 @@ impl i256 { | |||
let hl = (l_abs.high as u128).checked_mul(r_abs.low)?; | |||
let lh = l_abs.low.checked_mul(r_abs.high as u128)?; | |||
|
|||
let high: i128 = high.checked_add(hl)?.checked_add(lh)?.try_into().ok()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If high == -i128::MIN
this would fail, as it is performed before reversing the absolute value conversion
arrow-buffer/src/bigint.rs
Outdated
|
||
/// Returns `true` if this [`i256`] is negative | ||
#[inline] | ||
pub fn is_negative(self) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This matches the standard method - https://doc.rust-lang.org/std/primitive.i32.html#method.is_negative
i256::ONE << 129, | ||
i256::MINUS_ONE << 127, | ||
i256::MINUS_ONE << 128, | ||
i256::MINUS_ONE << 129, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better coverage 👍
Which issue does this PR close?
Closes #3941
Closes #3942
Rationale for this change
The previous logic would report overflow erroneously
What changes are included in this PR?
Are there any user-facing changes?