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

impl Power for U256 #4900

Closed
wants to merge 17 commits into from
2 changes: 1 addition & 1 deletion sway-lib-std/src/u128.sw
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ impl core::ops::Subtract for U128 {
}
}
impl core::ops::Multiply for U128 {
/// Multiply a `U128` with a `U128`. Panics of overflow.
/// Multiply a `U128` with a `U128`. Panics if overflow.
fn multiply(self, other: Self) -> Self {
// in case both of the `U128` upper parts are bigger than zero,
// it automatically means overflow, as any `U128` value
Expand Down
98 changes: 97 additions & 1 deletion sway-lib-std/src/u256.sw
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! A 256-bit unsigned integer type.
library;

use ::assert::assert;
use ::assert::{assert_eq, assert};
use ::convert::From;
use ::result::Result::{self, *};
use ::u128::U128;
use ::math::Power;

/// Left shift a `u64` and preserve the overflow amount if any.
fn lsh_with_carry(word: u64, shift_amount: u64) -> (u64, u64) {
Expand Down Expand Up @@ -81,6 +82,41 @@ impl U256 {
}
}

/// Initializes a new `U256` with a value of 1.
///
/// ### Examples
///
/// ```sway
/// use std::u256::U256;
///
/// let init_one = U256::one();
/// let one_u256 = U256 { a: 0, b: 0, c: 0, d: 1 };
///
/// assert(init_one == one_u256);
/// ```
pub fn one() -> Self {
Self {
a: 0,
b: 0,
c: 0,
d: 1,
}
}

/// Returns true if value is zero.
///
/// ### Examples
///
/// ```sway
/// use std::u256::U256
///
/// let zero_u256 = U256::new();
/// assert(zero_u256.is_zero());
/// ```
pub fn is_zero(self) -> bool {
self.a == 0 && self.b == 0 && self.c == 0 && self.d == 0
}

/// Safely downcast to `u64` without loss of precision.
/// Returns `Err` if the `number > u64::max()`.
///
Expand Down Expand Up @@ -570,3 +606,63 @@ impl core::ops::Divide for U256 {
quotient
}
}

impl Power for U256 {
/// Fast exponentiation by squaring
/// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
///
/// # Panics
///
/// Panics if the result overflows the type.
fn pow(self, exponent: Self) -> Self {
let mut value = self;
let mut exp = exponent;
let one = Self::one();

if exp.is_zero() {
return one;
}

if exp == one {
// Manually clone `self`. Otherwise, we may have a `MemoryOverflow`
// issue with code that looks like: `x = x.pow(other)`
return Self::from((self.a, self.b, self.c, self.d));
}

while (exp & one).is_zero() {
value = value * value;
exp >>= 1;
}

if exp == one {
return value;
}

let mut acc = value;
while exp > one {
exp >>= 1;
value = value * value;
if exp & one == one {
acc = acc * value;
}
}
acc
}
}

#[test]
fn test_pow_u256() {
let five = U256::from((0, 0, 0, 5));
let two = U256::from((0, 0, 0, 2));
let three = U256::from((0, 0, 0, 3));
let twenty_eight = U256::from((0, 0, 0, 28));
assert_eq(five.pow(two), U256::from((0, 0, 0, 25)));
assert_eq(five.pow(three), U256::from((0, 0, 0, 125)));
assert_eq(five.pow(twenty_eight), U256::from((0, 359414837200037395, 18446744073709551615, 18446744073709551615)));
Copy link
Contributor

Choose a reason for hiding this comment

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

How you tested this? I tried testing this on Wolfram and got this: https://www.wolframalpha.com/input?i=5%5E28+%3D%3D+2*18446744073709551615%2B359414837200037395 where 18446744073709551615 = u64::MAX.

Input
5^28 = 2×18446744073709551615 + 359414837200037395
Result
True
Left hand side
5^28 = 37252902984619140625
Right hand side
2 18446744073709551615 + 359414837200037395 = 37252902984619140625

So shouldn´t this line be?

assert_eq(five.pow(twenty_eight), U256::from((0, 0, 2, 359414837200037395)));

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 each slot in the U256 type is a u64 which at max is 18446744073709551615. The total number 5^28 can be represented by two u64::MAX and one with 359414837200037395. At least, that's my understanding from looking at the test. Would you mind explaining how U256 { 0, 0, 2, 359414837200037395 } is equal to 5^28?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also odd that this test passes if that's true

Copy link
Contributor

@xunilrj xunilrj Aug 15, 2023

Choose a reason for hiding this comment

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

I got from Wolfram: https://www.wolframalpha.com/input?i=5%5E28+%3D%3D+2*18446744073709551615%2B359414837200037395.

Another option is https://www.wolframalpha.com/input?i=convert+5%5E28+to+base+18446744073709551615%5D

input: convert 5^28 to base 18446744073709551615
Input interpretation
convert 37252902984619140625 to base18446744073709551615
Result
2:359414837200037395_18446744073709551615 (2 digits)

Where 359414837200037395 is the least significant digit; and 2 and the second least significant digit.

Copy link
Contributor

@xunilrj xunilrj Aug 15, 2023

Choose a reason for hiding this comment

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

Another good indication is: https://www.wolframalpha.com/input?i=log_5%5B18446744073709551615%5D

Input
log(5, 18446744073709551615)
Result
log(18446744073709551615)/log(5)
Decimal approximation
27.563299716697155242853137766599266959746869658989720909693829506...

So u64::MAX is super close to 5^27.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see now. You're using the maximum value of u64 as the base. Why is that?

Copy link
Contributor

Choose a reason for hiding this comment

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

So u64::MAX is super close to 5^27.

u64::MAX is 10996163476785723490 greater than 5^27 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see now. You're using the maximum value of u64 as the base. Why is that?

Isn´t this what U256 is doing with four u64? Each field is a u64 digit.

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose I misunderstood since their test cases seem to imply that every next u64 is catching the overflow of the previous. I'm mostly asking out of curiosity since this area isn't my forte @xunilrj , sorry to have started such a long thread on your PR @andrewvious 😅

This could imply a few different problems:

  • assert_eq may be passing incorrectly (I think this is most likely, which could make the last problem true)
  • the implementation of Power is somehow inaccurate
  • the test case itself is inaccurate as @xunilrj stated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries @eureka-cpu, I appreciate both of you @xunilrj I see how it works now, where on overflow it just increments the next u64 over by one. For example, adding 1 to u64::MAX would read:

u256 {
     u64=a: 0,
     u64=b: 0,
     u64=c: 1,
     u64=d: 0
} 

}

#[test]
fn test_is_zero() {
let zero_u256 = U256::new();
assert(zero_u256.is_zero());
}