diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index 2a2e6df4..0cc00c45 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -156,20 +156,411 @@ fn mul_div_roundup( /// * `value` - The value to the exponent is applied to. /// * `divisor` - The exponent applied. fn apply_exponent_factor(float_value: u128, exponent_factor: u128) -> u128 { // TODO - // if float_value < FLOAT_PRECISION { - // return 0; - // } - // if exponent_factor == FLOAT_PRECISION { - // return float_value; - // } - // let wei_value = float_to_wei(float_value); - // let exponent_wei = float_to_wei(exponent_factor); - // let wei_result = pow(wei_value, exponent_wei); - // let float_result = wei_to_float(wei_result); - // float_result - 0 + if float_value < FLOAT_PRECISION { + return 0; + } + if exponent_factor == FLOAT_PRECISION { + return float_value; + } + let wei_value = float_to_wei(float_value); + let exponent_wei = float_to_wei(exponent_factor); + let wei_result = pow_decimal(wei_value.into(), exponent_wei.into()); + + let wei_u128: u128 = wei_result.try_into().unwrap(); + let float_result = wei_to_float(wei_u128); + float_result +//0 +} + +//use starknet::cairo::common::cairo_builtins::bitwise_and; +//use starknet::{*}; +use alexandria_math::BitShift; + +fn exp2(mut x: u256) -> u256 { + let EXP2_MAX_INPUT = 192 * 1000000000000000000 - 1; + if x > EXP2_MAX_INPUT { + panic_with_felt252('error'); + } + x = BitShift::shl(x, 64); + x = x / 1000000000000000000; + //what is the cairo equivalent of `unchecked` in solidity? + + // Start from 0.5 in the 192.64-bit fixed-point format. + let mut result = 0x800000000000000000000000000000000000000000000000; + + // The following logic multiplies the result by $\sqrt{2^{-i}}$ when the bit at position i is 1. Key points: + // + // 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65. + // 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing + // a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1, + // we know that `x & 0xFF` is also 1. + + if (x & 0xFF00000000000000 > 0) { + if (x & 0x8000000000000000 > 0) { + result = BitShift::shr(result * 0x16A09E667F3BCC909, 64); + } + if (x & 0x4000000000000000 > 0) { + result = BitShift::shr(result * 0x1306FE0A31B7152DF, 64); + } + if (x & 0x2000000000000000 > 0) { + result = BitShift::shr(result * 0x1172B83C7D517ADCE, 64); + } + if (x & 0x1000000000000000 > 0) { + result = BitShift::shr(result * 0x10B5586CF9890F62A, 64); + } + if (x & 0x800000000000000 > 0) { + result = BitShift::shr(result * 0x1059B0D31585743AE, 64); + } + if (x & 0x400000000000000 > 0) { + result = BitShift::shr(result * 0x102C9A3E778060EE7, 64); + } + if (x & 0x200000000000000 > 0) { + result = BitShift::shr(result * 0x10163DA9FB33356D8, 64); + } + if (x & 0x100000000000000 > 0) { + result = BitShift::shr(result * 0x100B1AFA5ABCBED61, 64); + } + } + + if (x & 0xFF000000000000 > 0) { + if (x & 0x80000000000000 > 0) { + result = BitShift::shr(result * 0x10058C86DA1C09EA2, 64); + } + if (x & 0x40000000000000 > 0) { + result = BitShift::shr(result * 0x1002C605E2E8CEC50, 64); + } + if (x & 0x20000000000000 > 0) { + result = BitShift::shr(result * 0x100162F3904051FA1, 64); + } + if (x & 0x10000000000000 > 0) { + result = BitShift::shr(result * 0x1000B175EFFDC76BA, 64); + } + if (x & 0x8000000000000 > 0) { + result = BitShift::shr(result * 0x100058BA01FB9F96D, 64); + } + if (x & 0x4000000000000 > 0) { + result = BitShift::shr(result * 0x10002C5CC37DA9492, 64); + } + if (x & 0x2000000000000 > 0) { + result = BitShift::shr(result * 0x1000162E525EE0547, 64); + } + if (x & 0x1000000000000 > 0) { + result = BitShift::shr(result * 0x10000B17255775C04, 64); + } + } + + if (x & 0xFF0000000000 > 0) { + if (x & 0x800000000000 > 0) { + result = BitShift::shr(result * 0x1000058B91B5BC9AE, 64); + } + if (x & 0x400000000000 > 0) { + result = BitShift::shr(result * 0x100002C5C89D5EC6D, 64); + } + if (x & 0x200000000000 > 0) { + result = BitShift::shr(result * 0x10000162E43F4F831, 64); + } + if (x & 0x100000000000 > 0) { + result = BitShift::shr(result * 0x100000B1721BCFC9A, 64); + } + if (x & 0x80000000000 > 0) { + result = BitShift::shr(result * 0x10000058B90CF1E6E, 64); + } + if (x & 0x40000000000 > 0) { + result = BitShift::shr(result * 0x1000002C5C863B73F, 64); + } + if (x & 0x20000000000 > 0) { + result = BitShift::shr(result * 0x100000162E430E5A2, 64); + } + if (x & 0x10000000000 > 0) { + result = BitShift::shr(result * 0x1000000B172183551, 64); + } + } + + if (x & 0xFF00000000 > 0) { + if (x & 0x8000000000 > 0) { + result = BitShift::shr(result * 0x100000058B90C0B49, 64); + } + if (x & 0x4000000000 > 0) { + result = BitShift::shr(result * 0x10000002C5C8601CC, 64); + } + if (x & 0x2000000000 > 0) { + result = BitShift::shr(result * 0x1000000162E42FFF0, 64); + } + if (x & 0x1000000000 > 0) { + result = BitShift::shr(result * 0x10000000B17217FBB, 64); + } + if (x & 0x800000000 > 0) { + result = BitShift::shr(result * 0x1000000058B90BFCE, 64); + } + if (x & 0x400000000 > 0) { + result = BitShift::shr(result * 0x100000002C5C85FE3, 64); + } + if (x & 0x200000000 > 0) { + result = BitShift::shr(result * 0x10000000162E42FF1, 64); + } + if (x & 0x100000000 > 0) { + result = BitShift::shr(result * 0x100000000B17217F8, 64); + } + } + + if (x & 0xFF000000 > 0) { + if (x & 0x80000000 > 0) { + result = BitShift::shr(result * 0x10000000058B90BFC, 64); + } + if (x & 0x40000000 > 0) { + result = BitShift::shr(result * 0x1000000002C5C85FE, 64); + } + if (x & 0x20000000 > 0) { + result = BitShift::shr(result * 0x100000000162E42FF, 64); + } + if (x & 0x10000000 > 0) { + result = BitShift::shr(result * 0x1000000000B17217F, 64); + } + if (x & 0x8000000 > 0) { + result = BitShift::shr(result * 0x100000000058B90C0, 64); + } + if (x & 0x4000000 > 0) { + result = BitShift::shr(result * 0x10000000002C5C860, 64); + } + if (x & 0x2000000 > 0) { + result = BitShift::shr(result * 0x1000000000162E430, 64); + } + if (x & 0x1000000 > 0) { + result = BitShift::shr(result * 0x10000000000B17218, 64); + } + } + + if (x & 0xFF0000 > 0) { + if (x & 0x800000 > 0) { + result = BitShift::shr(result * 0x1000000000058B90C, 64); + } + if (x & 0x400000 > 0) { + result = BitShift::shr(result * 0x100000000002C5C86, 64); + } + if (x & 0x200000 > 0) { + result = BitShift::shr(result * 0x10000000000162E43, 64); + } + if (x & 0x100000 > 0) { + result = BitShift::shr(result * 0x100000000000B1721, 64); + } + if (x & 0x80000 > 0) { + result = BitShift::shr(result * 0x10000000000058B91, 64); + } + if (x & 0x40000 > 0) { + result = BitShift::shr(result * 0x1000000000002C5C8, 64); + } + if (x & 0x20000 > 0) { + result = BitShift::shr(result * 0x100000000000162E4, 64); + } + if (x & 0x10000 > 0) { + result = BitShift::shr(result * 0x1000000000000B172, 64); + } + } + + if (x & 0xFF00 > 0) { + if (x & 0x8000 > 0) { + result = BitShift::shr(result * 0x100000000000058B9, 64); + } + if (x & 0x4000 > 0) { + result = BitShift::shr(result * 0x10000000000002C5D, 64); + } + if (x & 0x2000 > 0) { + result = BitShift::shr(result * 0x1000000000000162E, 64); + } + if (x & 0x1000 > 0) { + result = BitShift::shr(result * 0x10000000000000B17, 64); + } + if (x & 0x800 > 0) { + result = BitShift::shr(result * 0x1000000000000058C, 64); + } + if (x & 0x400 > 0) { + result = BitShift::shr(result * 0x100000000000002C6, 64); + } + if (x & 0x200 > 0) { + result = BitShift::shr(result * 0x10000000000000163, 64); + } + if (x & 0x100 > 0) { + result = BitShift::shr(result * 0x100000000000000B1, 64); + } + } + + if (x & 0xFF > 0) { + if (x & 0x80 > 0) { + result = BitShift::shr(result * 0x10000000000000059, 64); + } + if (x & 0x40 > 0) { + result = BitShift::shr(result * 0x1000000000000002C, 64); + } + if (x & 0x20 > 0) { + result = BitShift::shr(result * 0x10000000000000016, 64); + } + if (x & 0x10 > 0) { + result = BitShift::shr(result * 0x1000000000000000B, 64); + } + if (x & 0x8 > 0) { + result = BitShift::shr(result * 0x10000000000000006, 64); + } + if (x & 0x4 > 0) { + result = BitShift::shr(result * 0x10000000000000003, 64); + } + if (x & 0x2 > 0) { + result = BitShift::shr(result * 0x10000000000000001, 64); + } + if (x & 0x1 > 0) { + result = BitShift::shr(result * 0x10000000000000001, 64); + } + } + + // In the code snippet below, two operations are executed simultaneously: + // + // 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1 + // accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192. + // 2. The result is then converted to an unsigned 60.18-decimal fixed-point format. + // + // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the, + // integer part, $2^n$. + + result *= 1000000000000000000; + result = BitShift::shr(result, 191 - BitShift::shr(x, 64)); + result +} + +fn exp(x: u256) -> u256 { + //check if x is not too big, but it's already checked in exp 2? + let uLOG2_E = 1_442695040888963407; + let double_unit_product = x * uLOG2_E; + exp2(double_unit_product / 1000000000000000000) } +/// Raise a number to a power, computes x^n. +/// * `x` - The number to raise. +/// * `n` - The exponent. +/// # Returns +/// * `u256` - The result of x raised to the power of n. +fn pow256(x: u256, n: usize) -> u256 { + if n == 0 { + 1 + } else if n == 1 { + x + } else if (n & 1) == 1 { + x * pow256(x * x, n / 2) + } else { + pow256(x * x, n / 2) + } +} + +fn msb(mut x: u256) -> u256 { + let mut result = 0; + if (x >= pow256(2, 128)) { + x = BitShift::shr(x, 128); + result += 128; + } + if x >= pow256(2, 64) { + x = BitShift::shr(x, 64); + result += 64; + } + if x >= pow256(2, 32) { + x = BitShift::shr(x, 32); + result += 32; + } + if x >= pow256(2, 16) { + x = BitShift::shr(x, 16); + result += 16; + } + if x >= pow256(2, 8) { + x = BitShift::shr(x, 8); + result += 8; + } + if x >= pow256(2, 4) { + x = BitShift::shr(x, 4); + result += 4; + } + if x >= pow256(2, 2) { + x = BitShift::shr(x, 2); + result += 2; + } + if x >= pow256(2, 1) { + result += 1; + } + result +} + +fn log2(x: u256) -> u256 { + let xUint: u256 = x; + + // If the input value is smaller than the base unit, error out. + if xUint < 1000000000000000000 { + panic_with_felt252('error'); + } + + // Calculate the integer part of the logarithm. + let n: u256 = msb(xUint / 1000000000000000000); + + // Calculate the integer part of the logarithm as a fixed-point number. + let mut resultUint: u256 = n * 1000000000000000000; + + // Calculate y = x * 2^{-n} + let mut y: u256 = BitShift::shr(xUint, n); + + // If y equals the base unit, the fractional part is zero. + if y == 1000000000000000000 { + return resultUint; + } + + // Calculate the fractional part through iterative approximation. + let mut delta: u256 = 500000000000000000; + loop { + if delta == 0 { + break; + } + y = (y * y) / 1000000000000000000; + + if y >= 2000000000000000000 { + resultUint += delta; + y = BitShift::shr(y, 1); + } + delta = BitShift::shr(delta, 1); // Decrement the delta by halving it. + }; + + return resultUint; +} + +fn pow_decimal(x: u256, y: u256) -> u256 { + let xUint: u256 = x; + let yUint: u256 = y; + + // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero. + if (xUint == 0) { + if yUint == 0 { + return 1000000000000000000; + } else { + return 0; + } + } // If x is `UNIT`, the result is always `UNIT`. + else if (xUint == 1000000000000000000) { + return 1000000000000000000; + } + + // If y is zero, the result is always `UNIT`. + if (yUint == 0) { + return 1000000000000000000; + } // If y is `UNIT`, the result is always x. + else if (yUint == 1000000000000000000) { + return x; + } + + // If x is greater than `UNIT`, use the standard formula. + if (xUint > 1000000000000000000) { + return exp2(log2(x) * y / 1000000000000000000); + } // Conversely, if x is less than `UNIT`, use the equivalent formula. + else { + let i = 1000000000000000000000000000000000000 / xUint; + let w = exp2(log2(i) * y); + return 1000000000000000000000000000000000000 / w; + } +} + + /// Compute factor from value and divisor with a roundup. /// # Arguments /// * `value` - The value to compute the factor. diff --git a/tests/utils/test_precision.cairo b/tests/utils/test_precision.cairo index 198c762e..f1623a7f 100644 --- a/tests/utils/test_precision.cairo +++ b/tests/utils/test_precision.cairo @@ -84,6 +84,101 @@ fn test_mul_div_inum_roundup_positive() { assert(result == i128_new(53, false), 'should be 53.'); } +#[test] +fn test_exp2() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 2500000000000000000; + let value3: u256 = 7482948646372839484; + + let result1 = precision::exp2(value1); + let result2 = precision::exp2(value2); + let result3 = precision::exp2(value3); + + assert(result1 == 4000000000000000000, 'should be 4000000000000000000'); + assert(result2 == 5656854249492380195, 'should be 5656854249492380195'); + assert(result3 == 178892444495791357043, 'should be 178892444495791357043'); +} + +#[test] +fn test_exp() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 2500000000000000000; + let value3: u256 = 7482948646372839484; + let value4: u256 = 0000000000000000000; + let value5: u256 = 1000000000000000000; + + let result1 = precision::exp(value1); + let result2 = precision::exp(value2); + let result3 = precision::exp(value3); + let result4 = precision::exp(value4); + let result5 = precision::exp(value5); + + assert(result1 == 7389056098930650223, 'should be 7389056098930650223'); + assert(result2 == 12182493960703473424, 'should be 12182493960703473424'); + assert(result3 == 1777474199233404337144, 'should_1777474199233404337144'); + assert(result4 == 1000000000000000000, 'should be 1000000000000000000'); + assert(result5 == 2718281828459045234, 'should be 2718281828459045234'); +} + + +#[test] +fn test_log2() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 5000000000000000000; + let value3: u256 = 4000000000000000000; + let value5: u256 = 1000000000000000000; + + let result1 = precision::log2(value1); + let result2 = precision::log2(value2); + let result3 = precision::log2(value3); + let result5 = precision::log2(value5); + + assert(result1 == 1000000000000000000, 'should be 1000000000000000000'); + assert(result2 == 2321928094887362334, 'should be 2321928094887362334'); + assert(result3 == 2000000000000000000, 'should be 2000000000000000000'); + assert(result5 == 0000000000000000000, 'should be 0000000000000000000'); +} + +#[test] +fn test_pow_decimal() { + let value1: u256 = 2000000000000000000; + let value2: u256 = 3000000000000000000; + let value3: u256 = 4000000000000000000; + let value5: u256 = 1000000000000000000; + let value6: u256 = 1524558784654678955; + + let result1 = precision::pow_decimal(value2, value1); + let result2 = precision::pow_decimal(value2, value5); + let result3 = precision::pow_decimal(value3, 0); + let result4 = precision::pow_decimal(value3, value6); + //let result5 = precision::pow_decimal(0, value5); + + assert(result1 == 8999999999999999806, 'should be 8999999999999999806'); + //assert(result2 == 2999999999999999967, 'should be 2999999999999999967'); + assert(result3 == 1000000000000000000, 'should be 2000000000000000000'); + assert(result4 == 8277055145359463000, 'should be 8277055145359463000'); +} + +#[test] +fn test_apply_exponent_factor() { + let value1: u128 = 2000000000000000000000000000000; + let value2: u128 = 3000000000000000000000000000000; + let value3: u128 = 4000000000000000000000000000000; + let value5: u128 = 1000000000000000000000000000000; + let value6: u128 = 1524558784654678955000000000000; + + let result1 = precision::apply_exponent_factor(value2, value1); + let result2 = precision::apply_exponent_factor(value2, value5); + let result3 = precision::apply_exponent_factor(value3, 0); + let result4 = precision::apply_exponent_factor(value3, value6); + //let result5 = precision::pow_decimal(0, value5); + + assert(result1 == 8999999999999999806000000000000, 'should be '); + //assert(result2 == 2999999999999999967, 'should be 2999999999999999967'); + assert(result3 == 1000000000000000000000000000000, 'should be '); + assert(result4 == 8277055145359463000000000000000, 'should be '); +} + #[test] fn test_to_factor_roundup() { let value: u128 = 450000;