diff --git a/contracts/feature-tests/big-float-features/src/big_float_operators_wrapped.rs b/contracts/feature-tests/big-float-features/src/big_float_operators_wrapped.rs index 14cf56510d..f44ca95e5b 100644 --- a/contracts/feature-tests/big-float-features/src/big_float_operators_wrapped.rs +++ b/contracts/feature-tests/big-float-features/src/big_float_operators_wrapped.rs @@ -164,7 +164,7 @@ pub trait BigFloatWrappedOperators: big_float_operators::BigFloatOperators { &self, a: BigInt, precision: usize, - ) -> ManagedDecimal { + ) -> ManagedDecimalSigned { let number = self.ln_big_float_ref(&BigFloat::from(a)); number.to_managed_decimal(precision) } diff --git a/framework/base/src/err_msg.rs b/framework/base/src/err_msg.rs index f24878517b..7432a5b5c5 100644 --- a/framework/base/src/err_msg.rs +++ b/framework/base/src/err_msg.rs @@ -38,6 +38,7 @@ pub const VALUE_EXCEEDS_SLICE: &str = "value exceeds target slice"; pub const CAST_TO_I64_ERROR: &str = "cast to i64 error"; pub const BIG_UINT_EXCEEDS_SLICE: &str = "big uint as_bytes exceed target slice"; pub const BIG_UINT_SUB_NEGATIVE: &str = "cannot subtract because result would be negative"; +pub const BIG_UINT_NEGATIVE: &str = "cannot convert to unsigned, number is negative"; pub const DESERIALIZATION_INVALID_BYTE: &str = "call data deserialization error: not a valid byte"; pub const DESERIALIZATION_NOT_32_BYTES: &str = diff --git a/framework/base/src/types/managed/basic/big_float.rs b/framework/base/src/types/managed/basic/big_float.rs index b4c4d0ed39..a3d6cdc4b5 100644 --- a/framework/base/src/types/managed/basic/big_float.rs +++ b/framework/base/src/types/managed/basic/big_float.rs @@ -5,7 +5,8 @@ use crate::{ api::{ use_raw_handle, BigFloatApiImpl, ManagedTypeApi, ManagedTypeApiImpl, Sign, StaticVarApiImpl, }, - types::{BigInt, BigUint, Decimals, ManagedDecimal, ManagedType}, + contract_base::ErrorHelper, + types::{BigInt, BigUint, Decimals, ManagedDecimalSigned, ManagedType}, }; use alloc::string::String; @@ -175,8 +176,8 @@ impl BigFloat { (self * denominator).trunc() } - pub fn to_managed_decimal(&self, decimals: T) -> ManagedDecimal { - ManagedDecimal::::from_big_float(self, decimals) + pub fn to_managed_decimal(&self, decimals: T) -> ManagedDecimalSigned { + ManagedDecimalSigned::::from_big_float(self, decimals) } /// Computes the natural logarithm of the current number. @@ -242,9 +243,7 @@ impl BigFloat { result } -} -impl BigFloat { #[inline] pub fn zero() -> Self { BigFloat::from_handle(M::managed_type_impl().bf_new_zero()) @@ -304,6 +303,34 @@ impl BigFloat { } } +impl From for BigFloat { + fn from(x: f64) -> Self { + const PREC: i64 = 1_000_000_000; + Self::from_frac((x * PREC as f64) as i64, PREC) + } +} + +impl From for BigFloat { + fn from(x: f32) -> Self { + Self::from(x as f64) + } +} + +impl BigFloat { + /// Warning: cannot be used in contracts. It is only meant to simplify certain tests. + /// + /// It might also not be optimal with respect to precision. + pub fn to_f64(&self) -> f64 { + const PREC: i64 = 1_000_000_000; + let mut rescaled = Self::from(PREC); + rescaled *= self; + let ln_units = rescaled.trunc().to_i64().unwrap_or_else(|| { + ErrorHelper::::signal_error_with_message("BigFloat out of precision range") + }); + ln_units as f64 / PREC as f64 + } +} + impl Clone for BigFloat { fn clone(&self) -> Self { let new_handle: M::BigFloatHandle = use_raw_handle(M::static_var_api_impl().next_handle()); diff --git a/framework/base/src/types/managed/basic/big_int.rs b/framework/base/src/types/managed/basic/big_int.rs index b837eaa108..9e6d932835 100644 --- a/framework/base/src/types/managed/basic/big_int.rs +++ b/framework/base/src/types/managed/basic/big_int.rs @@ -3,13 +3,14 @@ use core::{convert::TryInto, marker::PhantomData}; use crate::{ abi::{TypeAbiFrom, TypeName}, api::{ - const_handles, use_raw_handle, BigIntApiImpl, HandleConstraints, ManagedBufferApiImpl, - ManagedTypeApi, ManagedTypeApiImpl, RawHandle, StaticVarApiImpl, + const_handles, use_raw_handle, BigIntApiImpl, ErrorApiImpl, HandleConstraints, + ManagedBufferApiImpl, ManagedTypeApi, ManagedTypeApiImpl, RawHandle, StaticVarApiImpl, }, codec::{ DecodeErrorHandler, EncodeErrorHandler, NestedDecode, NestedDecodeInput, NestedEncode, NestedEncodeOutput, TopDecode, TopDecodeInput, TopEncode, TopEncodeOutput, TryStaticCast, }, + err_msg, formatter::{hex_util::encode_bytes_as_hex, FormatByteReceiver, SCDisplay}, types::{heap::BoxedBytes, BigUint, ManagedBuffer, ManagedOption, ManagedType, Sign}, }; @@ -246,14 +247,35 @@ impl BigInt { (self.sign(), self.magnitude()) } + /// Converts to an unsigned `BigUint`, without performing any checks. + /// + /// # Safety + /// + /// If the number is negative, undefined behavior might occur further down the execution. + pub unsafe fn into_big_uint_unchecked(self) -> BigUint { + BigUint::from_handle(self.handle) + } + /// Converts this `BigInt` into a `BigUint`, if it's not negative. pub fn into_big_uint(self) -> ManagedOption> { if let Sign::Minus = self.sign() { ManagedOption::none() } else { - ManagedOption::some(BigUint::from_handle(self.handle)) + ManagedOption::some(unsafe { self.into_big_uint_unchecked() }) } } + + fn check_non_negative(&self) { + if M::managed_type_impl().bi_sign(self.handle.clone()) == crate::api::Sign::Minus { + M::error_api_impl().signal_error(err_msg::BIG_UINT_SUB_NEGATIVE.as_bytes()); + } + } + + /// Converts to an unsigned `BigUint`. Stops execution if number is negative. + pub fn into_big_uint_or_fail(self) -> BigUint { + self.check_non_negative(); + unsafe { self.into_big_uint_unchecked() } + } } impl TryStaticCast for BigInt {} diff --git a/framework/base/src/types/managed/wrapped/big_uint.rs b/framework/base/src/types/managed/wrapped/big_uint.rs index 33f523d83d..609af7b961 100644 --- a/framework/base/src/types/managed/wrapped/big_uint.rs +++ b/framework/base/src/types/managed/wrapped/big_uint.rs @@ -90,6 +90,10 @@ impl BigUint { pub fn as_big_int(&self) -> &BigInt { &self.value } + + pub fn into_big_int(self) -> BigInt { + self.value + } } macro_rules! big_uint_conv_num { diff --git a/framework/base/src/types/managed/wrapped/managed_decimal.rs b/framework/base/src/types/managed/wrapped/managed_decimal.rs index 73e03858b0..819e7dabf2 100644 --- a/framework/base/src/types/managed/wrapped/managed_decimal.rs +++ b/framework/base/src/types/managed/wrapped/managed_decimal.rs @@ -17,9 +17,9 @@ pub use managed_decimal_signed::ManagedDecimalSigned; use crate::{ abi::{TypeAbi, TypeAbiFrom, TypeName}, - api::{const_handles, use_raw_handle, BigFloatApiImpl, ManagedTypeApi}, + api::ManagedTypeApi, formatter::{FormatBuffer, FormatByteReceiver, SCDisplay}, - types::{BigFloat, BigUint}, + types::BigUint, }; use alloc::string::ToString; @@ -85,31 +85,24 @@ impl ManagedDecimal { ManagedDecimal::from_raw_units(self.rescale_data(scale_to_num_decimals), scale_to) } - pub fn to_big_float(&self) -> BigFloat { - let result = BigFloat::from_big_uint(&self.data); - let temp_handle: M::BigFloatHandle = use_raw_handle(const_handles::BIG_FLOAT_TEMPORARY); - let denominator = self.decimals.scaling_factor::(); - M::managed_type_impl().bf_set_bi(temp_handle.clone(), denominator.handle); - M::managed_type_impl().bf_div(result.handle.clone(), result.handle.clone(), temp_handle); - result + pub fn into_signed(self) -> ManagedDecimalSigned { + ManagedDecimalSigned { + data: self.data.into_big_int(), + decimals: self.decimals, + } } +} - pub fn from_big_float( - big_float: &BigFloat, - num_decimals: T, - ) -> ManagedDecimal { - let scaling_factor: &BigUint = &num_decimals.scaling_factor(); - let magnitude = big_float.magnitude(); - - let scaled = &BigFloat::from(scaling_factor) * &magnitude; - let fixed_big_int = scaled.trunc(); - - ManagedDecimal::from_raw_units( - fixed_big_int - .into_big_uint() - .unwrap_or_sc_panic("failed to cast BigInt to BigUint"), - num_decimals, - ) +impl From> + for ManagedDecimal> +{ + fn from(mut value: BigUint) -> Self { + let decimals = ConstDecimals; + value *= decimals.scaling_factor().deref(); + ManagedDecimal { + data: value, + decimals, + } } } @@ -251,19 +244,6 @@ impl TopDecode for ManagedDecimal { } } -impl From> - for ManagedDecimal> -{ - fn from(mut value: BigUint) -> Self { - let decimals = ConstDecimals; - value *= decimals.scaling_factor().deref(); - ManagedDecimal { - data: value, - decimals, - } - } -} - impl TypeAbiFrom for ManagedDecimal {} impl TypeAbi for ManagedDecimal { diff --git a/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_ln.rs b/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_ln.rs index 94173fd5d2..ea0ed5ec39 100644 --- a/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_ln.rs +++ b/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_ln.rs @@ -1,117 +1,69 @@ -pub use super::decimals::{ConstDecimals, Decimals, NumDecimals}; -pub use super::managed_decimal_signed::ManagedDecimalSigned; -use super::ManagedDecimal; +use super::decimals::{ConstDecimals, Decimals}; +use super::ManagedDecimalSigned; +use super::{ManagedDecimal, NumDecimals}; +use crate::proxy_imports::ManagedType; use crate::{ - abi::{TypeAbi, TypeAbiFrom, TypeName}, - api::{const_handles, use_raw_handle, BigFloatApiImpl, ManagedTypeApi}, + api::ManagedTypeApi, contract_base::ErrorHelper, - formatter::{FormatBuffer, FormatByteReceiver, SCDisplay}, - types::{BigFloat, BigUint}, + types::{BigInt, BigUint, Sign}, }; -impl ManagedDecimal { - /// Natural logarithm of a number. - /// - /// Returns `None` for 0. - /// - /// TODO: TEMP impl. - pub fn ln(&self) -> Option { - let bit_log2 = self.data.log2(); // aproximate, based on position of the most significant bit - if bit_log2 == u32::MAX { - // means the input was zero, TODO: change log2 return type - return None; - } +fn compute_ln( + data: &BigUint, + num_decimals: NumDecimals, +) -> Option>> { + let bit_log2 = data.log2(); // aproximate, based on position of the most significant bit + if bit_log2 == u32::MAX { + // means the input was zero, TODO: change log2 return type + return None; + } - let scaling_factor_9 = ConstDecimals::<9>.scaling_factor(); - let divisor = BigUint::from(1u64) << bit_log2 as usize; - let normalized = &self.data * &*scaling_factor_9 / divisor; + let scaling_factor_9 = ConstDecimals::<9>.scaling_factor(); + let divisor = BigUint::from(1u64) << bit_log2 as usize; + let normalized = data * &*scaling_factor_9 / divisor; - let x = normalized - .to_u64() - .unwrap_or_else(|| ErrorHelper::::signal_error_with_message("ln internal error")) - as i64; + let x = normalized + .to_u64() + .unwrap_or_else(|| ErrorHelper::::signal_error_with_message("ln internal error")) + as i64; - let mut result = crate::types::math_util::logarithm_i64::ln_polynomial(x); - crate::types::math_util::logarithm_i64::ln_add_bit_log2(&mut result, bit_log2); + let mut result = crate::types::math_util::logarithm_i64::ln_polynomial(x); + crate::types::math_util::logarithm_i64::ln_add_bit_log2(&mut result, bit_log2); - debug_assert!(result > 0); + debug_assert!(result > 0); - crate::types::math_util::logarithm_i64::ln_sub_decimals( - &mut result, - self.decimals.num_decimals(), - ); + crate::types::math_util::logarithm_i64::ln_sub_decimals(&mut result, num_decimals); - Some(result) - } + Some(ManagedDecimalSigned::from_raw_units( + BigInt::from(result), + ConstDecimals, + )) } -impl ManagedDecimal> { - // pub fn log( - // &self, - // target_base: &ManagedDecimal, - // precision: D, - // ) -> ManagedDecimal { - // let num_decimals = precision.num_decimals(); - // // should verify >= 1 - // let one = ManagedDecimal::from_raw_units(BigUint::from(1u64), 0usize); - // one.rescale(self.scale()); - // assert!(self >= &one, "wrong input for self"); - // one.rescale(target_base.scale()); - // assert!(target_base >= &one, "wrong input for target base"); - - // self.ln(&precision) - // * ManagedDecimal::from_raw_units(BigUint::from(num_decimals), num_decimals) - // / target_base.ln(&precision) - // //this should be done with precision - // } - - pub fn ln_temp( - self, - precision: ConstDecimals, - ) -> ManagedDecimal> { - let num_decimals = self.decimals.num_decimals() as u32; - // find the highest power of 2 less than or equal to self - let log2 = self.data.log2() - num_decimals * BigUint::::from(10u64).log2(); // most significant bit for the actual number - let divisor = 1 << log2; - let divisor_scaled = - BigUint::::from(divisor as u64) * self.decimals.scaling_factor().clone_value(); - let _normalized = self.data / divisor_scaled; // normalize to [1.0, 2.0] - let x_dec = ManagedDecimal::>::const_decimals_from_raw(_normalized); - let x = x_dec.rescale(precision.clone()); - - // approximating polynom to get the result - let mut result = ManagedDecimal::>::const_decimals_from_raw( - BigUint::from(56570851u64), // 0.056570851, 9 decimalsĖ - ) - .mul_with_precision(x.clone(), precision.clone()); - result = ManagedDecimal::>::const_decimals_from_raw(BigUint::from( - 44717955u64, // 0.44717955, 8 decimals - )) - .rescale(precision.clone()) - - result; - result = result.mul_with_precision(x.clone(), precision.clone()); - result -= ManagedDecimal::>::const_decimals_from_raw(BigUint::from( - 14699568u64, // 1.4699568, 7 decimals - )) - .rescale(precision.clone()); - result = result.mul_with_precision(x.clone(), precision.clone()); - result += ManagedDecimal::>::const_decimals_from_raw(BigUint::from( - 28212026u64, // 2.8212026, 7 decimals - )) - .rescale(precision.clone()); - result = result.mul_with_precision(x.clone(), precision.clone()); - result -= ManagedDecimal::>::const_decimals_from_raw(BigUint::from( - 17417939u64, // 1.7417939, 7 decimals - )) - .rescale(precision.clone()); +impl ManagedDecimal { + /// Natural logarithm of a number. + /// + /// Returns `None` for 0. + /// + /// Even though 9 decimals are returned, only around 6 decimals are actually useful. + pub fn ln(&self) -> Option>> { + compute_ln(&self.data, self.decimals.num_decimals()) + } +} - let log_2 = - ManagedDecimal::>::const_decimals_from_raw(BigUint::from(log2)); - let ln_of_2 = ManagedDecimal::>::const_decimals_from_raw( - BigUint::from(69314718u64), - ); // 0.69314718 8 decimals +impl ManagedDecimalSigned { + /// Natural logarithm of a number. + /// + /// Returns `None` for 0. + /// + /// Even though 9 decimals are returned, only around 6 decimals are actually useful. + pub fn ln(&self) -> Option>> { + if self.sign() != Sign::Plus { + return None; + } - result + log_2.mul_with_precision(ln_of_2, precision.clone()) + let bu = BigUint::from_handle(self.data.handle.clone()); + compute_ln(&bu, self.decimals.num_decimals()) } } diff --git a/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_signed.rs b/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_signed.rs index a412ae9705..9f7bba18c2 100644 --- a/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_signed.rs +++ b/framework/base/src/types/managed/wrapped/managed_decimal/managed_decimal_signed.rs @@ -5,7 +5,7 @@ use crate::{ ManagedBufferApiImpl, ManagedTypeApi, }, formatter::{FormatBuffer, FormatByteReceiver, SCDisplay}, - types::{BigFloat, BigInt, BigUint, ManagedBuffer, ManagedType}, + types::{BigFloat, BigInt, BigUint, ManagedBuffer, ManagedType, Sign}, }; use alloc::string::ToString; @@ -16,7 +16,10 @@ use multiversx_sc_codec::{ use core::{cmp::Ordering, ops::Deref}; -use super::decimals::{ConstDecimals, Decimals, NumDecimals}; +use super::{ + decimals::{ConstDecimals, Decimals, NumDecimals}, + ManagedDecimal, +}; use super::{ManagedBufferCachedBuilder, ManagedRef}; #[derive(Clone)] @@ -64,14 +67,64 @@ impl ManagedDecimalSigned { } } - pub fn rescale(&self, scale_to: T) -> ManagedDecimalSigned - where - M: ManagedTypeApi, - { + pub fn rescale(&self, scale_to: T) -> ManagedDecimalSigned { let scale_to_num_decimals = scale_to.num_decimals(); ManagedDecimalSigned::from_raw_units(self.rescale_data(scale_to_num_decimals), scale_to) } + pub fn into_unsigned_or_fail(self) -> ManagedDecimal { + ManagedDecimal { + data: self.data.into_big_uint_or_fail(), + decimals: self.decimals, + } + } + + pub fn sign(&self) -> Sign { + self.data.sign() + } +} + +impl + ManagedDecimalSigned> +{ + pub fn const_decimals_from_raw(data: BigInt) -> Self { + ManagedDecimalSigned { + data, + decimals: ConstDecimals, + } + } + + /// Converts from constant (compile-time) number of decimals to a variable number of decimals. + pub fn into_var_decimals(self) -> ManagedDecimalSigned { + ManagedDecimalSigned { + data: self.data, + decimals: DECIMALS, + } + } +} + +impl From> + for ManagedDecimalSigned> +{ + fn from(mut value: BigInt) -> Self { + let decimals = ConstDecimals; + value *= decimals.scaling_factor().as_big_int(); + ManagedDecimalSigned { + data: value, + decimals, + } + } +} + +impl From + for ManagedDecimalSigned> +{ + fn from(value: i64) -> Self { + Self::from(BigInt::from(value)) + } +} + +impl ManagedDecimalSigned { pub fn to_big_float(&self) -> BigFloat { let result = BigFloat::from_big_int(&self.data); let temp_handle: M::BigFloatHandle = use_raw_handle(const_handles::BIG_FLOAT_TEMPORARY); @@ -95,22 +148,36 @@ impl ManagedDecimalSigned { } } -impl - ManagedDecimalSigned> +impl From<&BigFloat> + for ManagedDecimalSigned> { - pub fn const_decimals_from_raw(data: BigInt) -> Self { - ManagedDecimalSigned { - data, - decimals: ConstDecimals, - } + fn from(value: &BigFloat) -> Self { + Self::from_big_float(value, ConstDecimals) } +} - /// Converts from constant (compile-time) number of decimals to a variable number of decimals. - pub fn into_var_decimals(self) -> ManagedDecimalSigned { - ManagedDecimalSigned { - data: self.data, - decimals: DECIMALS, - } +impl From> + for ManagedDecimalSigned> +{ + #[inline] + fn from(value: BigFloat) -> Self { + Self::from(&value) + } +} + +impl From + for ManagedDecimalSigned> +{ + fn from(x: f64) -> Self { + Self::from(BigFloat::from(x)) + } +} + +impl From + for ManagedDecimalSigned> +{ + fn from(x: f32) -> Self { + Self::from(x as f64) } } @@ -235,19 +302,6 @@ impl TopDecode for ManagedDecimalSigned { } } -impl From> - for ManagedDecimalSigned> -{ - fn from(mut value: BigInt) -> Self { - let decimals = ConstDecimals; - value *= decimals.scaling_factor().deref().as_big_int(); - ManagedDecimalSigned { - data: value, - decimals, - } - } -} - impl TypeAbiFrom for ManagedDecimalSigned {} impl TypeAbi for ManagedDecimalSigned { diff --git a/tools/plotter/src/logarithm_bf.rs b/tools/plotter/src/logarithm_bf.rs index e3e70f57ec..0958c22a6d 100644 --- a/tools/plotter/src/logarithm_bf.rs +++ b/tools/plotter/src/logarithm_bf.rs @@ -79,11 +79,8 @@ pub fn draw_bf_error( } fn big_float_ln(x: f32) -> f32 { - const PREC: i64 = 1_000_000; - let bf = BigFloat::::from_frac((x * PREC as f32) as i64, PREC); - if let Some(ln_dec) = bf.ln() { - let ln_units = (ln_dec * PREC.into()).trunc().to_i64().unwrap(); - (ln_units as f64 / PREC as f64) as f32 + if let Some(ln) = BigFloat::::from(x).ln() { + ln.to_f64() as f32 } else { 0.0 } diff --git a/tools/plotter/src/logarithm_bu.rs b/tools/plotter/src/logarithm_bu.rs index bd10efed86..c452d14a16 100644 --- a/tools/plotter/src/logarithm_bu.rs +++ b/tools/plotter/src/logarithm_bu.rs @@ -81,9 +81,7 @@ pub fn draw_bu_error( fn big_uint_ln(x: f32) -> f32 { let bu = BigUint::::from(x as u32); if let Some(ln_dec) = bu.ln() { - let ln_units = ln_dec.into_raw_units().to_u64().unwrap(); - let ln_sf = ln_dec.scaling_factor().to_u64().unwrap(); - (ln_units as f64 / ln_sf as f64) as f32 + ln_dec.into_signed().to_big_float().to_f64() as f32 } else { 0.0 } diff --git a/tools/plotter/src/logarithm_md.rs b/tools/plotter/src/logarithm_md.rs index d7ca5d9cdd..ef634840a1 100644 --- a/tools/plotter/src/logarithm_md.rs +++ b/tools/plotter/src/logarithm_md.rs @@ -1,5 +1,5 @@ use crate::DrawResult; -use multiversx_sc::types::{BigUint, ConstDecimals, ManagedDecimal}; +use multiversx_sc::types::{ConstDecimals, ManagedDecimalSigned}; use multiversx_sc_scenario::api::StaticApi; use plotters::prelude::*; use plotters_canvas::CanvasBackend; @@ -79,13 +79,9 @@ pub fn draw_md_error( } fn managed_decimal_ln(x: f32) -> f32 { - let bu = BigUint::::from((x * 1_000_000_000.0) as u64); - let dec = ManagedDecimal::>::const_decimals_from_raw(bu); + let dec = ManagedDecimalSigned::>::from(x); if let Some(ln_dec) = dec.ln() { - // let ln_units = ln_dec; - // let ln_sf = ln_dec.scaling_factor().to_u64().unwrap(); - // (ln_units as f64 / ln_sf as f64) as f32 - (ln_dec as f64 / 1_000_000_000f64) as f32 + ln_dec.to_big_float().to_f64() as f32 } else { 0.0 }