Skip to content
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

Merged
merged 4 commits into from
Mar 26, 2023

Conversation

tustvold
Copy link
Contributor

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?

@github-actions github-actions bot added the arrow Changes to the arrow crate label Mar 25, 2023
@@ -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 _)?;
Copy link
Contributor Author

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

Copy link
Member

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 })
Copy link
Contributor Author

@tustvold tustvold Mar 25, 2023

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()?;
Copy link
Contributor Author

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


/// Returns `true` if this [`i256`] is negative
#[inline]
pub fn is_negative(self) -> bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tustvold tustvold requested a review from viirya March 25, 2023 23:59
i256::ONE << 129,
i256::MINUS_ONE << 127,
i256::MINUS_ONE << 128,
i256::MINUS_ONE << 129,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better coverage 👍

@tustvold tustvold merged commit 86b384f into apache:master Mar 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arrow Changes to the arrow crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

I256 Checked Subtraction Overflows for i256::MINUS_ONE I256 Checked Multiply Overflows for i256::MIN
2 participants