Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
525 changes: 301 additions & 224 deletions docs/stdlib.json

Large diffs are not rendered by default.

143 changes: 81 additions & 62 deletions simf/lib/u128/math.simf
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::lib::binary::{not, or, and};
use crate::lib::u64::convert::u64_to_u128;

use crate::lib::u128::comparison::{is_zero_128, lt_128};
use crate::lib::u128::convert::{u128_to_u256, split_u128_into_u64};

/// Arithmetic

/// Adds two integers and returns the carry
pub fn add_128(a: u128, b: u128) -> (bool, u128) {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (b_high, b_low): (u64, u64) = <u128>::into(b);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

let (carry_low, sum_low): (bool, u64) = jet::add_64(a_low, b_low);
let (carry_high, sum_high): (bool, u64) = jet::full_add_64(carry_low, a_high, b_high);
Expand All @@ -17,7 +20,7 @@ pub fn add_128(a: u128, b: u128) -> (bool, u128) {

/// Adds the 128-bit integer with the 64-bit integer and returns the carry
pub fn add_128_64(a: u128, b: u64) -> (bool, u128) {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);

let (carry_low, res_low): (bool, u64) = jet::add_64(a_low, b);
let (carry_high, res_high): (bool, u64) = jet::full_add_64(carry_low, a_high, 0);
Expand All @@ -28,8 +31,8 @@ pub fn add_128_64(a: u128, b: u64) -> (bool, u128) {

/// Adds two integers. Takes a carry-in and returns a carry-out
pub fn full_add_128(carry_in: bool, a: u128, b: u128) -> (bool, u128) {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (b_high, b_low): (u64, u64) = <u128>::into(b);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

let (carry_low, sum_low): (bool, u64) = jet::full_add_64(carry_in, a_low, b_low);
let (carry_out, sum_high): (bool, u64) = jet::full_add_64(carry_low, a_high, b_high);
Expand All @@ -55,8 +58,8 @@ pub fn safe_add_128(a: u128, b: u128) -> u128 {

/// Subtracts the second integer from the first integer, and returns the borrow bit
pub fn sub_128(a: u128, b: u128) -> (bool, u128) {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (b_high, b_low): (u64, u64) = <u128>::into(b);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

let (borrow_low, diff_low): (bool, u64) = jet::subtract_64(a_low, b_low);
let (borrow_high, diff_high): (bool, u64) = jet::full_subtract_64(borrow_low, a_high, b_high);
Expand All @@ -67,8 +70,8 @@ pub fn sub_128(a: u128, b: u128) -> (bool, u128) {

/// Subtracts the second integer from the first integer, takes a borrow-in and returns a borrow-out
pub fn full_sub_128(borrow_in: bool, a: u128, b: u128) -> (bool, u128) {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (b_high, b_low): (u64, u64) = <u128>::into(b);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

let (borrow_low, diff_low): (bool, u64) = jet::full_subtract_64(borrow_in, a_low, b_low);
let (borrow_out, diff_high): (bool, u64) = jet::full_subtract_64(borrow_low, a_high, b_high);
Expand Down Expand Up @@ -98,19 +101,19 @@ pub fn safe_sub_128(a: u128, b: u128) -> u128 {
/// In the same way, b = b_high * 2^64 + b_low.
/// Therefore, a * b = 2^128 * a_high * b_high + 2^64(a_high * b_low + a_low * b_high) + a_low * b_low.
pub fn mul_128(a: u128, b: u128) -> u256 {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (b_high, b_low): (u64, u64) = <u128>::into(b);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

let highest: u128 = jet::multiply_64(a_high, b_high);
let lowest: u128 = jet::multiply_64(a_low, b_low);
let (word_1, word_0): (u64, u64) = <u128>::into(lowest);
let (word_3, word_2): (u64, u64) = <u128>::into(highest);
let (word_1, word_0): (u64, u64) = split_u128_into_u64(lowest);
let (word_3, word_2): (u64, u64) = split_u128_into_u64(highest);

let product_1: u128 = jet::multiply_64(a_high, b_low);
let product_2: u128 = jet::multiply_64(b_high, a_low);
let (carry_3a, middle): (bool, u128) = add_128(product_1, product_2);

let (middle_2, middle_1): (u64, u64) = <u128>::into(middle);
let (middle_2, middle_1): (u64, u64) = split_u128_into_u64(middle);

// fold the low-side carry directly into the word_2 addition via full_add_64,
// then propagate any resulting carry into word_3
Expand All @@ -136,13 +139,13 @@ pub fn mul_128(a: u128, b: u128) -> u256 {
/// so a = a_high * 2^64 + a_low.
/// Therefore, a * b = 2^64 * a_high * b + a_low * b.
pub fn mul_128_64(a: u128, b: u64) -> u256 {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);

let highest: u128 = jet::multiply_64(a_high, b);
let lowest: u128 = jet::multiply_64(a_low, b);

let (word_1, word_0): (u64, u64) = <u128>::into(lowest);
let (word_3, word_2): (u64, u64) = <u128>::into(highest);
let (word_1, word_0): (u64, u64) = split_u128_into_u64(lowest);
let (word_3, word_2): (u64, u64) = split_u128_into_u64(highest);

let (carry_2, res_1): (bool, u64) = jet::add_64(word_1, word_2);
// a * b fits into u192, so addition below can not overflow and `full_add_64`
Expand Down Expand Up @@ -171,17 +174,17 @@ pub fn safe_mul_128(a: u128, b: u128) -> u128 {
/// Helper function that can be used with jet::div_mod_128_64 or Algorithm D.
/// Returns the normalization factor by which `b` should be multiplied so that
/// its most significant non-zero word is greater than or equal to 2^63
pub fn calculate_normalizer_base_64(b: u128, is_b_u128: bool) -> u64 {
pub fn calculate_normalizer_base_64(b: u128, is_b_u128: bool) -> u64 {
// Compile-time constant: 2^63. Avoids a runtime jet::left_shift_64 call
let threshold: u64 = 0x8000000000000000;
let (b_high, b_low): (u64, u64) = <u128>::into(b);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

let b_highest_word: u64 = match is_b_u128 {
true => b_high,
false => {
assert!(jet::is_zero_64(b_high));
b_low
}
},
};
assert!(not(jet::is_zero_64(b_highest_word)));

Expand All @@ -206,10 +209,10 @@ pub fn calculate_normalizer_base_64(b: u128, is_b_u128: bool) -> u64 {
/// Division algorithms operate in base 2^64, so the normalization threshold is 2^63
fn normalize_to_threshold_128_63(a: u128, b: u128, is_b_u128: bool) -> (u256, u128, u64) {
let norm: u64 = calculate_normalizer_base_64(b, is_b_u128);
let norm_128: u128 = <(u64, u64)>::into((0, norm));
let norm_128: u128 = u64_to_u128(norm);

match jet::eq_64(norm, 1) {
true => (<(u128, u128)>::into((0, a)), b, norm),
true => ( u128_to_u256(a), b, norm),
false => (mul_128(a, norm_128), safe_mul_128(b, norm_128), norm),
}
}
Expand All @@ -220,7 +223,7 @@ fn normalize_to_threshold_128_63(a: u128, b: u128, is_b_u128: bool) -> (u256, u1
pub fn estimate_quotient_digit_base_64(u2: u64, u1: u64, u0: u64, v1: u64, v0: u64) -> u64 {
let (q_hat, r_hat, carry): (u64, u64, bool) = match jet::lt_64(u2, v1) {
true => {
let (q_hat, r_hat) : (u64, u64) =jet::div_mod_128_64(<(u64, u64)>::into((u2, u1)), v1);
let (q_hat, r_hat): (u64, u64) = jet::div_mod_128_64(<(u64, u64)>::into((u2, u1)), v1);
(q_hat, r_hat, false)
},
false => {
Expand All @@ -230,7 +233,7 @@ pub fn estimate_quotient_digit_base_64(u2: u64, u1: u64, u0: u64, v1: u64, v0: u
let (carry, r_hat): (bool, u64) = jet::add_64(u1, v1);

(jet::high_64(), r_hat, carry)
}
},
};

match carry {
Expand All @@ -242,7 +245,7 @@ pub fn estimate_quotient_digit_base_64(u2: u64, u1: u64, u0: u64, v1: u64, v0: u
match lt_128(r_hat_u0, jet::multiply_64(q_hat, v0)) {
true => {
// can not overflow because r_hat_u0 < q_hat * v0, so q_hat is at least 1
let (_, q_hat): (bool, u64) = jet::subtract_64(q_hat, 1);
let (_, q_hat): (bool, u64) = jet::subtract_64(q_hat, 1);
let (carry, r_hat): (bool, u64) = jet::add_64(r_hat, v1);

match carry {
Expand All @@ -253,18 +256,18 @@ pub fn estimate_quotient_digit_base_64(u2: u64, u1: u64, u0: u64, v1: u64, v0: u
match lt_128(r_hat_u0, jet::multiply_64(q_hat, v0)) {
true => {
// can not overflow because r_hat_u0 < q_hat * v0, so q_hat is at least 1
let (_, q_hat): (bool, u64) = jet::subtract_64(q_hat, 1);
let (_, q_hat): (bool, u64) = jet::subtract_64(q_hat, 1);

q_hat
}
},
false => q_hat,
}
}
},
}
},
false => q_hat,
}
}
},
}
}

Expand All @@ -273,22 +276,27 @@ pub fn estimate_quotient_digit_base_64(u2: u64, u1: u64, u0: u64, v1: u64, v0: u
/// Implements Algorithm D by Donald Knuth.
/// Requires the upper half of the divisor to be non-zero.
fn algorithm_d_128_128(dividend: u128, divisor: u128) -> (u64, u128) {
let (norm_dividend, norm_divisor, _): (u256, u128, u64) = normalize_to_threshold_128_63(dividend, divisor, true);
let (
norm_dividend,
norm_divisor,
_
): (u256, u128, u64) = normalize_to_threshold_128_63(dividend, divisor, true);

// normalized dividend fits into 192 bits
let (_, u2, u1, u0): (u64, u64, u64, u64) = <u256>::into(norm_dividend);
let (v1, v0): (u64, u64) = <u128>::into(norm_divisor);
let (v1, v0): (u64, u64) = split_u128_into_u64(norm_divisor);

let q: u64 = estimate_quotient_digit_base_64(u2, u1, u0, v1, v0);

let remainder: u128 = safe_sub_128(dividend, safe_mul_128(divisor, <(u64, u64)>::into((0, q))));
let remainder: u128 = safe_sub_128(dividend, safe_mul_128(divisor, u64_to_u128(q)));
(q, remainder)
}

/// Divides the 128-bit integer by the 64-bit integer,
/// returns a tuple of the u128 quotient and the u64 remainder
/// returns a tuple of the u128 quotient and the u64 remainder.
/// Panics if divisor is equal to zero
pub fn div_mod_128_64(a: u128, b: u64) -> (u128, u64) {
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);

// calculate the upper half of the quotient
let (q_high, remainder): (u64, u64) = jet::div_mod_64(a_high, b);
Expand All @@ -297,11 +305,19 @@ pub fn div_mod_128_64(a: u128, b: u64) -> (u128, u64) {
let a_prime: u128 = <(u64, u64)>::into((remainder, a_low));

// we need to normalize here, because jet::div_mod_128_64 only accepts b >= 2^63
let (a_normalized, b_normalized, norm): (u256, u128, u64) = normalize_to_threshold_128_63(a_prime, <(u64, u64)>::into((0, b)), false);
let (
a_normalized,
b_normalized,
norm
): (u256, u128, u64) = normalize_to_threshold_128_63(
a_prime,
u64_to_u128(b),
false
);

// a_normalized fits into u128, because remainder < b and b_normalized fits into u64
let (_, a_normalized): (u128, u128) = <u256>::into(a_normalized);
let (_, b_normalized): (u64, u64) = <u128>::into(b_normalized);
let (_, b_normalized): (u64, u64) = split_u128_into_u64(b_normalized);

// remainder < b, so (remainder * 2^64 + a_low) / b fits into u64
let (q_low, r_normalized): (u64, u64) = jet::div_mod_128_64(a_normalized, b_normalized);
Expand All @@ -313,42 +329,45 @@ pub fn div_mod_128_64(a: u128, b: u64) -> (u128, u64) {
/// Divides the first integer by the second integer,
/// returns the quotient and the remainder
pub fn div_mod_128(a: u128, b: u128) -> (u128, u128) {
Comment thread
aritkulova marked this conversation as resolved.
let (a_high, a_low): (u64, u64) = <u128>::into(a);
let (b_high, b_low): (u64, u64) = <u128>::into(b);

match lt_128(a, b) {
match is_zero_128(b) {
true => (0, a),
false => {
match and(jet::is_zero_64(a_high), jet::is_zero_64(b_high)) {
true => {
// if both a_high and b_high are zero, this narrows down to 64-bit division
let (q, r): (u64, u64) = jet::div_mod_64(a_low, b_low);
(<(u64, u64)>::into((0, q)), <(u64, u64)>::into((0, r)))
},
let (a_high, a_low): (u64, u64) = split_u128_into_u64(a);
let (b_high, b_low): (u64, u64) = split_u128_into_u64(b);

match lt_128(a, b) {
true => (0, a),
false => {
match jet::eq_64(a_high, b_high) {
match and(jet::is_zero_64(a_high), jet::is_zero_64(b_high)) {
true => {
// safe: !lt_128(a, b) and a_high == b_high, so a_low >= b_low,
// and the subtraction can not underflow
let (_, diff): (bool, u64) = jet::subtract_64(a_low, b_low);
(1, <(u64, u64)>::into((0, diff)))
// if both a_high and b_high are zero, this narrows down to 64-bit division
let (q, r): (u64, u64) = jet::div_mod_64(a_low, b_low);
(u64_to_u128(q), u64_to_u128(r))
},
false => {
match jet::is_zero_64(b_high) {
match jet::eq_64(a_high, b_high) {
true => {
let (q, r): (u128, u64) = div_mod_128_64(a, b_low);
(q, <(u64, u64)>::into((0, r)))
// safe: !lt_128(a, b) and a_high == b_high, so a_low >= b_low,
// and the subtraction can not underflow
let (_, diff): (bool, u64) = jet::subtract_64(a_low, b_low);
(1, u64_to_u128(diff))
},
false => {
let (q, r): (u64, u128) = algorithm_d_128_128(a, b);
(<(u64, u64)>::into((0, q)), r)
}
}
}
false => match jet::is_zero_64(b_high) {
true => {
let (q, r): (u128, u64) = div_mod_128_64(a, b_low);
(q, u64_to_u128(r))
},
false => {
let (q, r): (u64, u128) = algorithm_d_128_128(a, b);
(u64_to_u128(q), r)
},
},
}
},
}
}
},
}
}
},
}
}

Expand Down
15 changes: 15 additions & 0 deletions simf/lib/u128/mul_div.simf
Comment thread
Hrom131 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::lib::u128::math::mul_128;
use crate::lib::u128::convert::u128_to_u256;

use crate::lib::u256::math::div_256;
use crate::lib::u256::convert::safe_u256_to_u128;

/// Calculates `floor(a * b / denominator)` with full precision.
/// Panics if the result overflows a u128
pub fn mul_div_128(a: u128, b: u128, denominator: u128) -> u128 {
let denominator_256: u256 = u128_to_u256(denominator);

let result_256: u256 = div_256(mul_128(a, b), denominator_256);

safe_u256_to_u128(result_256)
}
12 changes: 12 additions & 0 deletions simf/lib/u16/mul_div.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::lib::u16::convert::u16_to_u32;
use crate::lib::u32::convert::safe_u32_to_u16;

/// Calculates `floor(a * b / denominator)` with full precision.
/// Panics if the result overflows a u16
pub fn mul_div_16(a: u16, b: u16, denominator: u16) -> u16 {
let denominator_32: u32 = u16_to_u32(denominator);

let result_32: u32 = jet::divide_32(jet::multiply_16(a, b), denominator_32);

safe_u32_to_u16(result_32)
}
Loading