-
Notifications
You must be signed in to change notification settings - Fork 70
[WIP] Int - sign-and-magnitude
#694
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
Changes from all commits
7a0780a
47a0fde
2aae18a
5595c2e
b6b73b6
26d1501
3efe421
e38062a
43700d6
2d7257d
f97d60c
aa5a085
b318da3
e85d199
66ef85f
12232b3
7e379c9
5b8c12f
7a8e45c
8f6802d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| //! Stack-allocated big signed integers. | ||
|
|
||
| use num_traits::Zero; | ||
| use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; | ||
|
|
||
| use crate::{Limb, Uint}; | ||
|
|
||
| mod add; | ||
| mod cmp; | ||
| mod div; | ||
| mod mul; | ||
| mod neg; | ||
| mod sub; | ||
|
|
||
| #[derive(Copy, Clone, Hash, Debug, PartialEq)] | ||
| pub struct Int<const LIMBS: usize> { | ||
| // TODO(erik): replace with Choice once I've figured how to const-construct those. | ||
| is_negative: bool, | ||
|
Comment on lines
+17
to
+18
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| magnitude: Uint<LIMBS>, | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> Int<LIMBS> { | ||
| /// The value `0`. | ||
| pub const ZERO: Self = Self { | ||
| is_negative: false, | ||
| magnitude: Uint::ZERO, | ||
| }; | ||
|
|
||
| /// The value `1`. | ||
| pub const ONE: Self = Self { | ||
| is_negative: false, | ||
| magnitude: Uint::ONE, | ||
| }; | ||
|
|
||
| /// The value `-1`. | ||
| pub const MINUS_ONE: Self = Self { | ||
| is_negative: true, | ||
| magnitude: Uint::ONE, | ||
| }; | ||
|
|
||
| /// Maximum value this [`Uint`] can express. | ||
| pub const MAX: Self = Self { | ||
| is_negative: false, | ||
| magnitude: Uint::MAX, | ||
| }; | ||
|
|
||
| /// Smallest value this [`Uint`] can express. | ||
| // Note: keep in mind that when `LIMBS = 0`, the minimal value we can express is zero. | ||
| pub const MIN: Self = Self { | ||
| is_negative: LIMBS != 0, | ||
| magnitude: Uint::MAX, | ||
| }; | ||
|
|
||
| /// The number of limbs used on this platform. | ||
| pub const LIMBS: usize = LIMBS; | ||
|
|
||
| /// Const-friendly [`Int`] constructor. | ||
| pub const fn new(is_negative: bool, limbs: [Limb; LIMBS]) -> Self { | ||
| Self { | ||
| is_negative, | ||
| magnitude: Uint::new(limbs), | ||
| } | ||
| } | ||
|
|
||
| /// Const-friendly [`Int`] constructor, using a `Uint`. | ||
| pub const fn new_from_uint(is_negative: bool, magnitude: Uint<LIMBS>) -> Self { | ||
| Self { | ||
| is_negative, | ||
| magnitude, | ||
| } | ||
| } | ||
|
|
||
| /// Whether this [`Int`] is negative | ||
| pub fn is_negative(&self) -> Choice { | ||
| Choice::from(u8::from(self.is_negative)) | ||
| } | ||
|
|
||
| /// Whether this [`Int`] is zero | ||
| pub fn is_zero(&self) -> bool { | ||
| self.magnitude.is_zero() | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> ConditionallySelectable for Int<LIMBS> { | ||
| fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { | ||
| let magnitude = Uint::conditional_select(&a.magnitude, &b.magnitude, choice); | ||
| let is_negative = Choice::conditional_select(&a.is_negative(), &b.is_negative(), choice); | ||
|
|
||
| Self { | ||
| is_negative: is_negative.unwrap_u8() == 1, | ||
| magnitude, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> Default for Int<LIMBS> { | ||
| fn default() -> Self { | ||
| Self::ZERO | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> Zero for Int<LIMBS> { | ||
| fn zero() -> Self { | ||
| Self::ZERO | ||
| } | ||
|
|
||
| fn is_zero(&self) -> bool { | ||
| self.ct_eq(&Self::ZERO).into() | ||
| } | ||
| } | ||
|
|
||
| #[cfg(target_pointer_width = "64")] | ||
| #[allow(dead_code)] | ||
| type I128 = Int<2>; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is where it's tricky, if we want to relate this to builtin i128 type, this is actually closer to I129 because of using sign and magnitude instead of two's complements. |
||
|
|
||
| #[cfg(target_pointer_width = "32")] | ||
| #[allow(dead_code)] | ||
| type I128 = Int<4>; | ||
|
|
||
| #[cfg(test)] | ||
| #[allow(clippy::unwrap_used)] | ||
| mod tests { | ||
| use crate::{ | ||
| int::{Int, I128}, | ||
| Uint, U128, | ||
| }; | ||
|
|
||
| #[test] | ||
| fn zero() { | ||
| let n = I128::ZERO; | ||
| assert!(!n.is_negative); | ||
| assert_eq!(n.magnitude, U128::ZERO); | ||
| } | ||
|
|
||
| #[test] | ||
| fn one() { | ||
| let n = I128::ONE; | ||
| assert!(!n.is_negative); | ||
| assert_eq!(n.magnitude, U128::ONE); | ||
| } | ||
|
|
||
| #[test] | ||
| fn minus_one() { | ||
| let n = I128::MINUS_ONE; | ||
| assert!(n.is_negative); | ||
| assert_eq!(n.magnitude, U128::ONE); | ||
| } | ||
|
|
||
| #[test] | ||
| fn min() { | ||
| let n = I128::MIN; | ||
| assert!(n.is_negative); | ||
| assert_eq!(n.magnitude, U128::MAX); | ||
|
|
||
| // Deal with case that LIMBS = 0 | ||
| let n = Int::<0>::MIN; | ||
| assert!(!n.is_negative); | ||
| assert_eq!(n.magnitude, Uint::<0>::MAX); // dirty trick; MAX is zero, while ZERO panics. | ||
| } | ||
|
|
||
| #[test] | ||
| fn max() { | ||
| let n = I128::MAX; | ||
| assert!(!n.is_negative); | ||
| assert_eq!(n.magnitude, U128::MAX); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| //! [`Int`] addition operations. | ||
|
|
||
| use core::ops::{Add, AddAssign}; | ||
|
|
||
| use subtle::{Choice, ConstantTimeEq, CtOption}; | ||
|
|
||
| use crate::int::Int; | ||
| use crate::{Checked, CheckedAdd, CheckedSub, ConstantTimeSelect}; | ||
|
|
||
| impl<const LIMBS: usize> Int<LIMBS> { | ||
| /// Add two [`Int`]s, checking for overflow. | ||
| fn checked_adc(&self, rhs: &Self) -> CtOption<Self> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is unnecessary complex.
|
||
| // Step 1. Select the element with the largest magnitude to be the lhs. | ||
| let (lhs, rhs) = Int::abs_max_min(self, rhs); | ||
|
|
||
| // Step 2. Add/subtract the magnitudes of the two sides to/from each other | ||
| let magnitude_add = lhs.magnitude.checked_add(&rhs.magnitude); | ||
| let magnitude_sub = lhs.magnitude.checked_sub(&rhs.magnitude); | ||
|
|
||
| // Step 3. Select the correct magnitude. | ||
| // magnitude_sub is used when the signs of the two elements are not the same. | ||
| let different_signs = lhs.is_negative().ct_ne(&rhs.is_negative()); | ||
| let magnitude = CtOption::ct_select(&magnitude_add, &magnitude_sub, different_signs); | ||
|
|
||
| // Step 4. Determine the sign of the result. | ||
| // This is always the same as the sign of the self (since it assumed to have the | ||
| // larger magnitude), except when the sum is zero. | ||
| let sum_is_zero = different_signs & lhs.magnitude.ct_eq(&rhs.magnitude); | ||
| let is_negative = lhs.is_negative() & !sum_is_zero; | ||
|
|
||
| magnitude.and_then(|magnitude| { | ||
| CtOption::new( | ||
| Self { | ||
| is_negative: is_negative.unwrap_u8() == 1, | ||
| magnitude, | ||
| }, | ||
| Choice::from(1), | ||
| ) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> Add for Int<LIMBS> { | ||
| type Output = Self; | ||
|
|
||
| fn add(self, rhs: Self) -> Self { | ||
| self.add(&rhs) | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> Add<&Int<LIMBS>> for Int<LIMBS> { | ||
| type Output = Self; | ||
|
|
||
| fn add(self, rhs: &Self) -> Self { | ||
| self.checked_add(rhs) | ||
| .expect("attempted to add with overflow") | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> AddAssign for Int<LIMBS> { | ||
| fn add_assign(&mut self, other: Self) { | ||
| *self += &other; | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> AddAssign<&Int<LIMBS>> for Int<LIMBS> { | ||
| fn add_assign(&mut self, other: &Self) { | ||
| *self = *self + other; | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> AddAssign for Checked<Int<LIMBS>> { | ||
| fn add_assign(&mut self, other: Self) { | ||
| *self = *self + other; | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> AddAssign<&Checked<Int<LIMBS>>> for Checked<Int<LIMBS>> { | ||
| fn add_assign(&mut self, other: &Self) { | ||
| *self = *self + other; | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> CheckedAdd for Int<LIMBS> { | ||
| /// Add two [`Int`]s, checking for overflow. | ||
| fn checked_add(&self, rhs: &Self) -> CtOption<Self> { | ||
| self.checked_adc(rhs) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::int::I128; | ||
| use crate::{CheckedAdd, U128}; | ||
|
|
||
| #[test] | ||
| fn checked_add() { | ||
| let max_minus_one = U128::MAX.wrapping_sub(&U128::ONE); | ||
| let two = U128::from(2u32); | ||
|
|
||
| // lhs = MIN | ||
|
|
||
| let result = I128::MIN.checked_add(&I128::MIN); | ||
| assert!(bool::from(result.is_none())); | ||
|
|
||
| let result = I128::MIN.checked_add(&I128::MINUS_ONE); | ||
| assert!(bool::from(result.is_none())); | ||
|
|
||
| let result = I128::MIN.checked_add(&I128::ZERO); | ||
| assert_eq!(result.unwrap(), I128::MIN); | ||
|
|
||
| let result = I128::MIN.checked_add(&I128::ONE); | ||
| assert_eq!(result.unwrap(), I128::new_from_uint(true, max_minus_one)); | ||
|
|
||
| let result = I128::MIN.checked_add(&I128::MAX); | ||
| assert_eq!(result.unwrap(), I128::ZERO); | ||
|
|
||
| // lhs = -1 | ||
|
|
||
| let result = I128::MINUS_ONE.checked_add(&I128::MIN); | ||
| assert!(bool::from(result.is_none())); | ||
|
|
||
| let result = I128::MINUS_ONE.checked_add(&I128::MINUS_ONE); | ||
| assert_eq!(result.unwrap(), I128::new_from_uint(true, two)); | ||
|
|
||
| let result = I128::MINUS_ONE.checked_add(&I128::ZERO); | ||
| assert_eq!(result.unwrap(), I128::MINUS_ONE); | ||
|
|
||
| let result = I128::MINUS_ONE.checked_add(&I128::ONE); | ||
| assert_eq!(result.unwrap(), I128::ZERO); | ||
|
|
||
| let result = I128::MINUS_ONE.checked_add(&I128::MAX); | ||
| assert_eq!(result.unwrap(), I128::new_from_uint(false, max_minus_one)); | ||
|
|
||
| // lhs = 0 | ||
|
|
||
| let result = I128::ZERO.checked_add(&I128::MIN); | ||
| assert_eq!(result.unwrap(), I128::MIN); | ||
|
|
||
| let result = I128::ZERO.checked_add(&I128::MINUS_ONE); | ||
| assert_eq!(result.unwrap(), I128::MINUS_ONE); | ||
|
|
||
| let result = I128::ZERO.checked_add(&I128::ZERO); | ||
| assert_eq!(result.unwrap(), I128::ZERO); | ||
|
|
||
| let result = I128::ZERO.checked_add(&I128::ONE); | ||
| assert_eq!(result.unwrap(), I128::ONE); | ||
|
|
||
| let result = I128::ZERO.checked_add(&I128::MAX); | ||
| assert_eq!(result.unwrap(), I128::MAX); | ||
|
|
||
| // lhs = 1 | ||
|
|
||
| let result = I128::ONE.checked_add(&I128::MIN); | ||
| assert_eq!(result.unwrap(), I128::new_from_uint(true, max_minus_one)); | ||
|
|
||
| let result = I128::ONE.checked_add(&I128::MINUS_ONE); | ||
| assert_eq!(result.unwrap(), I128::ZERO); | ||
|
|
||
| let result = I128::ONE.checked_add(&I128::ZERO); | ||
| assert_eq!(result.unwrap(), I128::ONE); | ||
|
|
||
| let result = I128::ONE.checked_add(&I128::ONE); | ||
| assert_eq!(result.unwrap(), I128::new_from_uint(false, two)); | ||
|
|
||
| let result = I128::ONE.checked_add(&I128::MAX); | ||
| assert!(bool::from(result.is_none())); | ||
|
|
||
| // lhs = MAX | ||
|
|
||
| let result = I128::MAX.checked_add(&I128::MIN); | ||
| assert_eq!(result.unwrap(), I128::ZERO); | ||
|
|
||
| let result = I128::MAX.checked_add(&I128::MINUS_ONE); | ||
| assert_eq!(result.unwrap(), I128::new_from_uint(false, max_minus_one)); | ||
|
|
||
| let result = I128::MAX.checked_add(&I128::ZERO); | ||
| assert_eq!(result.unwrap(), I128::MAX); | ||
|
|
||
| let result = I128::MAX.checked_add(&I128::ONE); | ||
| assert!(bool::from(result.is_none())); | ||
|
|
||
| let result = I128::MAX.checked_add(&I128::MAX); | ||
| assert!(bool::from(result.is_none())); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| //! [`Int`] comparisons. | ||
| //! | ||
| //! By default, these are all constant-time and use the `subtle` crate. | ||
|
|
||
| use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeLess}; | ||
|
|
||
| use crate::int::Int; | ||
| use crate::Uint; | ||
|
|
||
| impl<const LIMBS: usize> Int<LIMBS> { | ||
| /// Given two [`Int`]s, return a tuple with the larger magnitude first. | ||
| /// | ||
| /// When both have the same magnitude, they are returned in the same order | ||
| /// as they were provided. | ||
| pub(crate) fn abs_max_min(&self, rhs: &Self) -> (Self, Self) { | ||
| let swap = self.magnitude.ct_lt(&rhs.magnitude); | ||
| let greatest = Int::conditional_select(self, rhs, swap); | ||
| let smallest = Int::conditional_select(rhs, self, swap); | ||
| (greatest, smallest) | ||
| } | ||
|
|
||
| #[inline] | ||
| /// TODO: make this a const function? | ||
| pub(crate) fn eq(lhs: &Self, rhs: &Self) -> Choice { | ||
| Choice::from(Uint::eq(&lhs.magnitude, &rhs.magnitude)) | ||
| & Choice::ct_eq(&lhs.is_negative(), &rhs.is_negative()) | ||
| } | ||
| } | ||
|
|
||
| impl<const LIMBS: usize> ConstantTimeEq for Int<LIMBS> { | ||
| #[inline] | ||
| fn ct_eq(&self, other: &Self) -> Choice { | ||
| Int::eq(self, other) | ||
| } | ||
| } |
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.
no problem here, worst case scenario, just use u8, CtChoice is more or less that.