Skip to content

Commit

Permalink
feat:two's complement division (#68)
Browse files Browse the repository at this point in the history
* feat: u256s two's complement signed div

* tests doc

* address pr comments
  • Loading branch information
enitrat authored Aug 11, 2023
1 parent 8912644 commit 29bf3ad
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/tests/test_utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use array::{ArrayTrait, SpanTrait};
use kakarot::context::{CallContext, CallContextTrait, ExecutionContext, ExecutionContextTrait};

mod test_helpers;
mod test_u256_signed_math;

fn starknet_address() -> ContractAddress {
'starknet_address'.try_into().unwrap()
}
Expand Down
143 changes: 143 additions & 0 deletions src/tests/test_utils/test_u256_signed_math.cairo
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
}

1 change: 1 addition & 0 deletions src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use option::OptionTrait;

mod helpers;
mod constants;
mod u256_signed_math;

/// Panic with a custom message.
/// # Arguments
Expand Down
5 changes: 4 additions & 1 deletion src/utils/helpers.cairo
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use array::ArrayTrait;
use array::SpanTrait;
use traits::{Into, TryInto};
use kakarot::utils::{constants};
use option::OptionTrait;
use debug::PrintTrait;


use kakarot::utils::{constants};

/// Ceils a number of bits to the next word (32 bytes)
///
/// # Arguments
Expand Down Expand Up @@ -225,3 +227,4 @@ impl SpanPartialEq<T, impl PartialEqImpl: PartialEq<T>> of PartialEq<Span<T>> {
!(lhs == rhs)
}
}

73 changes: 73 additions & 0 deletions src/utils/u256_signed_math.cairo
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)
}

0 comments on commit 29bf3ad

Please sign in to comment.