generated from keep-starknet-strange/alexandria
-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:two's complement division (#68)
* feat: u256s two's complement signed div * tests doc * address pr comments
- Loading branch information
Showing
5 changed files
with
223 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use kakarot::utils::u256_signed_math::{u256_not, u256_neg, u256_signed_div_rem, ALL_ONES, MAX_U256}; | ||
use integer::u256_safe_div_rem; | ||
use debug::PrintTrait; | ||
|
||
const MAX_SIGNED_VALUE: u256 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; | ||
const MIN_SIGNED_VALUE: u256 = 0x8000000000000000000000000000000000000000000000000000000000000000; | ||
|
||
#[test] | ||
#[available_gas(20000000)] | ||
fn test_u256_not() { | ||
let x = u256_not(1); | ||
/// 0000_0001 turns into 1111_1110 | ||
assert(x == u256 { low: ALL_ONES - 1, high: ALL_ONES }, 'u256_not failed.'); | ||
|
||
let x = u256_not(0); | ||
/// 0000_0000 turns into 1111_1111 | ||
assert(x == MAX_U256, 'u256_not with zero failed.'); | ||
|
||
let x = MAX_U256; | ||
/// 1111_1111 turns into 0000_0000 | ||
assert(u256_not(x) == 0, 'u256_not with MAX_U256 failed.'); | ||
} | ||
|
||
|
||
#[test] | ||
#[available_gas(20000000)] | ||
fn test_u256_neg() { | ||
let x = u256_neg(1); | ||
// 0000_0001 turns into 1111_1110 + 1 = 1111_1111 | ||
assert(x.low == MAX_U256.low && x.high == MAX_U256.high, 'u256_neg failed.'); | ||
|
||
let x = u256_neg(0); | ||
// 0000_0000 turns into 1111_1111 + 1 = 0000_0000 | ||
assert(x == 0, 'u256_neg with zero failed.'); | ||
|
||
let x = MAX_U256; | ||
// 1111_1111 turns into 0000_0000 + 1 = 0000_0001 | ||
assert(u256_neg(x) == 1, 'u256_neg with MAX_U256 failed.'); | ||
} | ||
|
||
|
||
#[test] | ||
#[available_gas(20000000)] | ||
fn test_signed_div_rem() { | ||
// Zero Division | ||
assert(u256_signed_div_rem(0, 0) == (0, 0), 'Zero Division failed - 1.'); | ||
assert(u256_signed_div_rem(0xc0ffee, 0) == (0, 0), 'Zero Division failed - 2.'); | ||
|
||
// Division by -1 | ||
assert( | ||
u256_signed_div_rem(1, MAX_U256) == (MAX_U256, 0), 'Division by -1 failed - 1.' | ||
); // 1 / -1 == -1 | ||
assert( | ||
u256_signed_div_rem(MAX_U256, MAX_U256) == (1, 0), 'Division by -1 failed - 2.' | ||
); // -1 / -1 == 1 | ||
assert(u256_signed_div_rem(0, MAX_U256) == (0, 0), 'Division by -1 failed - 3.'); // 0 / -1 == 0 | ||
|
||
// Simple Division | ||
assert(u256_signed_div_rem(10, 2) == (5, 0), 'Simple Division failed - 1.'); // 10 / 2 == 5 | ||
assert( | ||
u256_signed_div_rem(10, 3) == (3, 1), 'Simple Division failed - 2.' | ||
); // 10 / 3 == 3 remainder 1 | ||
|
||
// Dividing a Negative Number | ||
assert( | ||
u256_signed_div_rem(MAX_U256, 1) == (MAX_U256, 0), 'Dividing a neg num failed - 1.' | ||
); // -1 / 1 == -1 | ||
assert( | ||
u256_signed_div_rem( | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, 0x2 | ||
) == (MAX_U256, 0), | ||
'Dividing a neg num failed - 2.' | ||
); // -2 / 2 == -1 | ||
// - 2**255 / 2 == - 2**254 | ||
assert( | ||
u256_signed_div_rem( | ||
0x8000000000000000000000000000000000000000000000000000000000000000, 0x2 | ||
) == (0xc000000000000000000000000000000000000000000000000000000000000000, 0), | ||
'Dividing a neg num failed - 3.' | ||
); | ||
|
||
// Dividing by a Negative Number | ||
assert( | ||
u256_signed_div_rem( | ||
0x4, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE | ||
) == (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, 0), | ||
'Div by a neg num failed - 1.' | ||
); // 4 / -2 == -2 | ||
assert( | ||
u256_signed_div_rem( | ||
MAX_SIGNED_VALUE, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF | ||
) == (MIN_SIGNED_VALUE + 1, 0), | ||
'Div by a neg num failed - 2.' | ||
); // MAX_VALUE (2**255 -1) / -1 == MIN_VALUE + 1 | ||
assert( | ||
u256_signed_div_rem( | ||
0x1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF | ||
) == (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0), | ||
'Div by a neg num failed - 3.' | ||
); // 1 / -1 == -1 | ||
|
||
// Both Dividend and Divisor Negative | ||
assert( | ||
u256_signed_div_rem( | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6, | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB | ||
) == (2, 0), | ||
'Div w/ both neg num failed - 1.' | ||
); // -10 / -5 == 2 | ||
assert( | ||
u256_signed_div_rem( | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6, | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 | ||
) == (0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6), | ||
'Div w/ both neg num failed - 2.' | ||
); // -10 / -11 == 0 remainder -10 | ||
|
||
// Division with Remainder | ||
assert( | ||
u256_signed_div_rem( | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9, 0x3 | ||
) == ( | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF | ||
), | ||
'Div with rem failed - 1.' | ||
); // -7 / 3 == -2 remainder -1 | ||
assert( | ||
u256_signed_div_rem( | ||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0x2 | ||
) == (0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF), | ||
'Div with rem failed - 2.' | ||
); // -1 / 2 == 0 remainder -1 | ||
|
||
// Edge Case: Dividing Minimum Value by -1 | ||
assert( | ||
u256_signed_div_rem( | ||
MIN_SIGNED_VALUE, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF | ||
) == (MIN_SIGNED_VALUE, 0), | ||
'Div w/ both neg num failed - 3.' | ||
); // MIN / -1 == MIN because 2**255 is out of range | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use integer::u256_safe_div_rem; | ||
use traits::TryInto; | ||
use option::OptionTrait; | ||
|
||
const ALL_ONES: u128 = 0xffffffffffffffffffffffffffffffff; | ||
const TWO_POW_127: u128 = 0x80000000000000000000000000000000; | ||
const MAX_U256: u256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; | ||
|
||
// Returns the bitwise NOT of an integer. | ||
fn u256_not(a: u256) -> u256 { | ||
u256 { low: ALL_ONES - a.low, high: ALL_ONES - a.high } | ||
} | ||
|
||
|
||
// Returns the negation of an integer. | ||
// Note that the negation of -2**255 is -2**255. | ||
fn u256_neg(a: u256) -> u256 { | ||
// If a is 0, adding one to its bitwise NOT will overflow and return 0. | ||
if a == 0 { | ||
return 0; | ||
} | ||
u256_not(a) + 1 | ||
} | ||
|
||
/// Signed integer division between two integers. Returns the quotient and the remainder. | ||
/// Conforms to EVM specifications. | ||
/// See ethereum yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf, page 29). | ||
/// Note that the remainder may be negative if one of the inputs is negative and that | ||
/// (-2**255) / (-1) = -2**255 because 2*255 is out of range. | ||
fn u256_signed_div_rem(a: u256, div: u256) -> (u256, u256) { | ||
if div == 0 { | ||
return (0, 0); | ||
} | ||
|
||
// When div=-1, simply return -a. | ||
if div == MAX_U256 { | ||
return (u256_neg(a), 0); | ||
} | ||
|
||
// Take the absolute value of a and div. | ||
// Checks the MSB bit sign for a 256-bit integer | ||
let a_positive = a.high < TWO_POW_127; | ||
let a = if a_positive { | ||
a | ||
} else { | ||
u256_neg(a) | ||
}; | ||
let div_positive = div.high < TWO_POW_127; | ||
let div = if div_positive { | ||
div | ||
} else { | ||
u256_neg(div) | ||
}; | ||
|
||
// Compute the quotient and remainder. | ||
// Can't panic as zero case is handled in the first instruction | ||
let (quot, rem) = u256_safe_div_rem(a, div.try_into().unwrap()); | ||
|
||
// Restore remainder sign. | ||
let rem = if a_positive { | ||
rem | ||
} else { | ||
u256_neg(rem) | ||
}; | ||
|
||
// If the signs of a and div are the same, return the quotient and remainder. | ||
if a_positive == div_positive { | ||
return (quot, rem); | ||
} | ||
|
||
// Otherwise, return the negation of the quotient and the remainder. | ||
(u256_neg(quot), rem) | ||
} |