diff --git a/Cargo.toml b/Cargo.toml index 9bb7feb..9f51d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ workspace = { members = ["example"] } [package] name = "soroban-math" -version = "0.2.0" +version = "0.2.1" edition = "2021" description = "Fixed-Point Math Library for soroban smart contracts with advanced math and high precision" license = "Apache-2.0" diff --git a/README.md b/README.md index 53da4df..f26c2e6 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Add this to your Cargo.toml: ```toml [dependencies] -soroban-math = "0.2.0" +soroban-math = "0.2.1" ``` And this to your code: diff --git a/example/src/lib.rs b/example/src/lib.rs index 9734e71..8f7d4af 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -38,5 +38,10 @@ impl SorobanMathExample { let num1 = SoroNum { value: a }; num1.sqrt(&e).value().clone() } + + pub fn log_2_u256(a: U256) -> u32 { + let num = SoroNum { value: a }; + num.log2().unwrap() + } } mod test; diff --git a/example/src/test.rs b/example/src/test.rs index f0df80d..b0312e7 100644 --- a/example/src/test.rs +++ b/example/src/test.rs @@ -26,5 +26,7 @@ fn test() { let root = client.root(&U256::from_u32(&env, 16_u32)); assert_eq!(root, U256::from_u32(&env, 4_u32)); + let log2_u128 = client.log_2_u256(&U256::from_u128(&env, 1024)); + assert_eq!(log2_u128, 10_u32); } diff --git a/example/test_snapshots/test/test.1.json b/example/test_snapshots/test/test.1.json index 488d4c5..4df0fbe 100644 --- a/example/test_snapshots/test/test.1.json +++ b/example/test_snapshots/test/test.1.json @@ -8,6 +8,7 @@ [], [], [], + [], [] ], "ledger": { @@ -366,6 +367,60 @@ } }, "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "symbol": "log_2_u256" + } + ], + "data": { + "u256": { + "hi_hi": 0, + "hi_lo": 0, + "lo_hi": 0, + "lo_lo": 1024 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "log_2_u256" + } + ], + "data": { + "u32": 10 + } + } + } + }, + "failed_call": false } ] } \ No newline at end of file diff --git a/src/log.rs b/src/log.rs index 160926d..031ecb6 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,3 +1,5 @@ +use soroban_sdk::{I256, U256}; + use crate::SoroNum; pub trait Logarithm { @@ -44,3 +46,75 @@ impl Logarithm for SoroNum { Some(count) } } + +impl Logarithm for SoroNum { + fn log2(&self) -> Option { + let bytes = self.value.to_be_bytes(); + let mut non_zero_byte = 0u8; + let mut non_zero_index = 0usize; + for (i, byte) in bytes.iter().enumerate() { + if byte != 0 { + non_zero_byte = byte; + non_zero_index = i; + break; + } + } + + // If the entire U256 is zero, return None + if non_zero_byte == 0 { + return None; + } + + // Calculate log2 based on the position of the first non-zero byte + let bits_from_most_significant_byte = 255 - (non_zero_index as u32 * 8); + let leading_zeros_in_byte = non_zero_byte.leading_zeros(); + Some(bits_from_most_significant_byte - leading_zeros_in_byte) + } + + fn log10(&self) -> Option { + // Direct calculation or iterative division by 10 isn't feasible for U256, + // so we'll use an approximation based on significant digits and `log2`. + self.log2().map(|log2_val| { + // Since log2(10) is approximately 3.32193, we use a ratio of 10:3 for approximation. + // This is a simplification for integer arithmetic, acknowledging potential rounding errors. + log2_val / 3 + }) + } +} + +impl Logarithm for SoroNum { + fn log2(&self) -> Option { + let bytes = self.value.to_be_bytes(); + let mut non_zero_byte = 0u8; + let mut non_zero_index = 0usize; + for (i, byte) in bytes.iter().enumerate() { + if byte != 0 { + non_zero_byte = byte; + non_zero_index = i; + break; + } + } + + // If the entire I256 is zero, or negative, return None + if non_zero_byte == 0 { + return None; + } + + // Calculate log2 based on the position of the first non-zero byte + let bits_from_most_significant_byte = 255 - (non_zero_index as u32 * 8); + let leading_zeros_in_byte = non_zero_byte.leading_zeros(); + Some(bits_from_most_significant_byte - leading_zeros_in_byte) + } + + fn log10(&self) -> Option { + // Similar approach as U256, ensuring we only apply this to non-negative values. + if self.value.to_i128().map_or(true, |v| v <= 0) { + None + } else { + self.log2().map(|log2_val| { + // Using the same approximation ratio as for U256. + log2_val / 3 + }) + } + } +} \ No newline at end of file