From c3f0731f2ed528c12b993cfcaee307d6f655569e Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Thu, 13 Jun 2024 19:12:54 +0200 Subject: [PATCH 1/6] added more tests --- .../scenarios/big_uint_log2.json | 50 +++++++- .../scenarios/big_float_operators.scen.json | 22 +++- .../src/big_float_operators.rs | 4 + .../src/big_float_operators_wrapped.rs | 10 ++ .../tests/big_float_scenario_rs_test.rs | 58 +--------- .../base/src/types/managed/basic/big_float.rs | 4 + framework/scenario/tests/big_float_test.rs | 107 ++++++++++++++++++ 7 files changed, 196 insertions(+), 59 deletions(-) create mode 100644 framework/scenario/tests/big_float_test.rs diff --git a/contracts/feature-tests/basic-features/scenarios/big_uint_log2.json b/contracts/feature-tests/basic-features/scenarios/big_uint_log2.json index 18fea6a2c4..e4e6d5c974 100644 --- a/contracts/feature-tests/basic-features/scenarios/big_uint_log2.json +++ b/contracts/feature-tests/basic-features/scenarios/big_uint_log2.json @@ -42,6 +42,54 @@ "gas": "*", "refund": "*" } + }, + { + "step": "scCall", + "id": "log2 from 1", + "tx": { + "from": "address:an_account", + "to": "sc:basic-features", + "value": "0", + "function": "log2_big_uint", + "arguments": [ + "1" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "0" + ], + "status": "0", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "log2 from 0", + "tx": { + "from": "address:an_account", + "to": "sc:basic-features", + "value": "0", + "function": "log2_big_uint", + "arguments": [ + "0" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "0" + ], + "status": "0", + "logs": "*", + "gas": "*", + "refund": "*" + } } ] -} +} \ No newline at end of file diff --git a/contracts/feature-tests/big-float-features/scenarios/big_float_operators.scen.json b/contracts/feature-tests/big-float-features/scenarios/big_float_operators.scen.json index 30c020132b..3021715617 100644 --- a/contracts/feature-tests/big-float-features/scenarios/big_float_operators.scen.json +++ b/contracts/feature-tests/big-float-features/scenarios/big_float_operators.scen.json @@ -820,6 +820,26 @@ "-1" ] } + }, + { + "step": "scCall", + "id": "LnBigFloatRef - 1", + "tx": { + "from": "address:an_account", + "to": "sc:basic-features", + "function": "ln_big_float_ref_wrapped", + "arguments": [ + "23", + "9" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [ + "0x00000004bae4281800000009" + ] + } } ] -} +} \ No newline at end of file diff --git a/contracts/feature-tests/big-float-features/src/big_float_operators.rs b/contracts/feature-tests/big-float-features/src/big_float_operators.rs index abf239b52a..fcad578694 100644 --- a/contracts/feature-tests/big-float-features/src/big_float_operators.rs +++ b/contracts/feature-tests/big-float-features/src/big_float_operators.rs @@ -85,4 +85,8 @@ pub trait BigFloatOperators { r /= b; r } + #[endpoint] + fn ln_big_float_ref(&self, a: &BigFloat) -> BigFloat { + a.ln() + } } 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 74b6045d1c..14cf56510d 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 @@ -158,4 +158,14 @@ pub trait BigFloatWrappedOperators: big_float_operators::BigFloatOperators { let number = self.div_assign_big_float_ref(&BigFloat::from(a), &BigFloat::from(b)); number.to_fixed_point(&BigFloat::from(fixed_point_denominator)) } + + #[endpoint] + fn ln_big_float_ref_wrapped( + &self, + a: BigInt, + precision: usize, + ) -> ManagedDecimal { + let number = self.ln_big_float_ref(&BigFloat::from(a)); + number.to_managed_decimal(precision) + } } diff --git a/contracts/feature-tests/big-float-features/tests/big_float_scenario_rs_test.rs b/contracts/feature-tests/big-float-features/tests/big_float_scenario_rs_test.rs index 3879706111..60a9d0ed35 100644 --- a/contracts/feature-tests/big-float-features/tests/big_float_scenario_rs_test.rs +++ b/contracts/feature-tests/big-float-features/tests/big_float_scenario_rs_test.rs @@ -1,5 +1,4 @@ -use multiversx_sc::types::{BigFloat, BigUint}; -use multiversx_sc_scenario::{api::StaticApi, *}; +use multiversx_sc_scenario::*; fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); @@ -12,61 +11,6 @@ fn world() -> ScenarioWorld { blockchain } -#[test] -fn big_float_overflow_test_rs() { - let exp = 1_080i32; - - let first = BigFloat::::from_sci(1_005, -3) - .pow(exp) - .to_fixed_point(&(100_000_000_000_000_000i64.into())) - .into_big_uint(); - - let second = BigFloat::::from_sci(1_005, -3) - .pow(exp) - .to_fixed_point(&(10_000_000_000_000_000i64.into())) - .into_big_uint(); - - let third_float = BigFloat::::from_sci(1_005, -3) - .pow(exp) - .to_managed_decimal(17usize); - let third = third_float.into_raw_units(); - - let forth_float = BigFloat::::from_sci(1_005, -3) - .pow(exp) - .to_managed_decimal(16usize); - let forth = forth_float.into_raw_units(); - - assert_eq!( - first.unwrap_or_sc_panic("unwrap failed"), - /* overflow */ - BigUint::from(9223372036854775807u64) - ); - - assert_eq!( - second.unwrap_or_sc_panic("unwrap failed"), - BigUint::from(2184473079534488064u64) - ); - - assert_eq!( - third, - /* overflow */ - &BigUint::from(9223372036854775807u64) - ); - - assert_eq!(forth, &BigUint::from(2184473079534488064u64)); -} - -#[test] -fn big_float_ln_test_rs() { - let x = BigFloat::::from(23i64); - let ln_x = x.ln(); - assert_eq!(ln_x.to_managed_decimal(9usize).to_string(), "3.135514648"); - assert!(ln_x.is_close( - &BigFloat::from_frac(3135514648, 1_000_000_000), // 3.135514648 - &BigFloat::from_frac(1, 1_000_000_000) - )); -} - #[test] fn big_float_new_from_big_int_rs() { world().run("scenarios/big_float_new_from_big_int.scen.json"); diff --git a/framework/base/src/types/managed/basic/big_float.rs b/framework/base/src/types/managed/basic/big_float.rs index d618b7f1c7..bd45e5bccc 100644 --- a/framework/base/src/types/managed/basic/big_float.rs +++ b/framework/base/src/types/managed/basic/big_float.rs @@ -177,6 +177,10 @@ impl BigFloat { } pub fn ln(&self) -> Self { + if *self == BigFloat::from(1i64) { + return BigFloat::from(0i64); + } + // find the highest power of 2 less than or equal to self let trunc_val = self.trunc(); let trunc_val_unsigned = trunc_val diff --git a/framework/scenario/tests/big_float_test.rs b/framework/scenario/tests/big_float_test.rs new file mode 100644 index 0000000000..d5e0a16526 --- /dev/null +++ b/framework/scenario/tests/big_float_test.rs @@ -0,0 +1,107 @@ +use multiversx_sc::types::{BigFloat, BigUint}; +use multiversx_sc_scenario::api::StaticApi; + +#[test] +fn big_float_overflow_test_rs() { + let exp = 1_080i32; + + let first = BigFloat::::from_sci(1_005, -3) + .pow(exp) + .to_fixed_point(&(100_000_000_000_000_000i64.into())) + .into_big_uint(); + + let second = BigFloat::::from_sci(1_005, -3) + .pow(exp) + .to_fixed_point(&(10_000_000_000_000_000i64.into())) + .into_big_uint(); + + let third_float = BigFloat::::from_sci(1_005, -3) + .pow(exp) + .to_managed_decimal(17usize); + let third = third_float.into_raw_units(); + + let forth_float = BigFloat::::from_sci(1_005, -3) + .pow(exp) + .to_managed_decimal(16usize); + let forth = forth_float.into_raw_units(); + + assert_eq!( + first.unwrap_or_sc_panic("unwrap failed"), + /* overflow */ + BigUint::from(9223372036854775807u64) + ); + + assert_eq!( + second.unwrap_or_sc_panic("unwrap failed"), + BigUint::from(2184473079534488064u64) + ); + + assert_eq!( + third, + /* overflow */ + &BigUint::from(9223372036854775807u64) + ); + + assert_eq!(forth, &BigUint::from(2184473079534488064u64)); +} + +#[test] +fn big_float_ln_test_rs() { + let x = BigFloat::::from(23i64); + let ln_x = x.ln(); + assert_eq!(ln_x.to_managed_decimal(9usize).to_string(), "3.135514648"); + assert!(ln_x.is_close( + &BigFloat::from_frac(3135514648, 1_000_000_000), // 3.135514648 + &BigFloat::from_frac(1, 1_000_000_000) + )); + + let big = BigFloat::::from(382747812i64); + let ln_big = big.ln(); + assert_eq!( + ln_big.to_managed_decimal(9usize).to_string(), + "19.762913880" + ); + assert!(ln_big.is_close( + &BigFloat::from_frac(19762913880, 1_000_000_000), // 19.762913880 + &BigFloat::from_frac(1, 1_000_000_000) + )); + + let biggest = BigFloat::::from(999999999i64); + let ln_biggest = biggest.ln(); + assert_eq!( + ln_biggest.to_managed_decimal(9usize).to_string(), + "20.723319778" + ); + assert!(ln_biggest.is_close( + &BigFloat::from_frac(20723319778, 1_000_000_000), // 20.723319778 + &BigFloat::from_frac(1, 1_000_000_000) + )); + + let small = BigFloat::::from_frac(3i64, 2i64); + let ln_small = small.ln(); + assert_eq!( + ln_small.to_managed_decimal(9usize).to_string(), + "0.405448248" + ); + assert!(ln_small.is_close( + &BigFloat::from_frac(405448248, 1_000_000_000), // 0.405448248 + &BigFloat::from_frac(1, 1_000_000_000) + )); + + let smallest = BigFloat::::from(1i64); + let ln_smallest = smallest.ln(); + assert_eq!(ln_smallest.to_managed_decimal(9usize).to_string(), "0.0"); + assert!(ln_smallest.is_close( + &BigFloat::from(0i64), // 0.0 + &BigFloat::from_frac(1, 100_000_000) + )); + + // fails for now, accuracy is not great around 1.1 + // let y = BigFloat::::from_frac(11i64, 10i64); + // let ln_y = y.ln(); + // assert_eq!(ln_y.to_managed_decimal(9usize).to_string(), "0.095310179"); + // assert!(ln_y.is_close( + // &BigFloat::from_frac(95310179, 1_000_000_000), // 0.095310179 + // &BigFloat::from_frac(1, 1_000_000_000) + // )); +} From 8ed23e78f66eb34b481f321ed51c3381ded71a98 Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Fri, 14 Jun 2024 20:36:07 +0200 Subject: [PATCH 2/6] clippy --- contracts/feature-tests/big-float-features/wasm/src/lib.rs | 6 ++++-- framework/base/src/types/managed/basic/big_float.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/feature-tests/big-float-features/wasm/src/lib.rs b/contracts/feature-tests/big-float-features/wasm/src/lib.rs index 6ec41ff9e9..0993fe17ed 100644 --- a/contracts/feature-tests/big-float-features/wasm/src/lib.rs +++ b/contracts/feature-tests/big-float-features/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 70 +// Endpoints: 72 // Async Callback (empty): 1 -// Total number of exported functions: 72 +// Total number of exported functions: 74 #![no_std] @@ -54,6 +54,7 @@ multiversx_sc_wasm_adapter::endpoints! { mul_assign_big_float_ref => mul_assign_big_float_ref div_assign_big_float => div_assign_big_float div_assign_big_float_ref => div_assign_big_float_ref + ln_big_float_ref => ln_big_float_ref new_from_parts_big_float_wrapped => new_from_parts_big_float_wrapped new_from_frac_big_float_wrapped => new_from_frac_big_float_wrapped new_from_sci_big_float_wrapped => new_from_sci_big_float_wrapped @@ -88,6 +89,7 @@ multiversx_sc_wasm_adapter::endpoints! { mul_assign_big_float_ref_wrapped => mul_assign_big_float_ref_wrapped div_assign_big_float_wrapped => div_assign_big_float_wrapped div_assign_big_float_ref_wrapped => div_assign_big_float_ref_wrapped + ln_big_float_ref_wrapped => ln_big_float_ref_wrapped ) } diff --git a/framework/base/src/types/managed/basic/big_float.rs b/framework/base/src/types/managed/basic/big_float.rs index bd45e5bccc..e0859e97e8 100644 --- a/framework/base/src/types/managed/basic/big_float.rs +++ b/framework/base/src/types/managed/basic/big_float.rs @@ -177,7 +177,7 @@ impl BigFloat { } pub fn ln(&self) -> Self { - if *self == BigFloat::from(1i64) { + if self == &BigFloat::from(1i64) { return BigFloat::from(0i64); } From e3f443cc80811f64f7b9ef2407c3dc68dcf5f8fb Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Wed, 19 Jun 2024 14:22:39 +0300 Subject: [PATCH 3/6] BigUint ln optimization --- framework/base/src/types/managed/basic/big_uint.rs | 12 ++++++++++-- tools/plotter/src/logarithm.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/framework/base/src/types/managed/basic/big_uint.rs b/framework/base/src/types/managed/basic/big_uint.rs index 2ad3fd6701..4cd040b9e9 100644 --- a/framework/base/src/types/managed/basic/big_uint.rs +++ b/framework/base/src/types/managed/basic/big_uint.rs @@ -244,11 +244,12 @@ impl BigUint { /// /// Returns `None` for 0. pub fn ln(&self) -> Option>> { - if self == &0u32 { + let bit_log2 = self.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 bit_log2 = self.log2(); // aproximate, based on position of the most significant bit let scaling_factor_9 = ConstDecimals::<9>.scaling_factor(); let divisor = BigUint::from(1u64) << bit_log2 as usize; let normalized = self * &*scaling_factor_9 / divisor; @@ -290,6 +291,13 @@ impl BigUint { } } +// impl +// ManagedDecimal +// { +// pub fn ln(&self) -> Option>> { +// } +// } + impl Clone for BigUint { fn clone(&self) -> Self { let api = M::managed_type_impl(); diff --git a/tools/plotter/src/logarithm.rs b/tools/plotter/src/logarithm.rs index 93765864d2..bcb57c02dc 100644 --- a/tools/plotter/src/logarithm.rs +++ b/tools/plotter/src/logarithm.rs @@ -61,7 +61,7 @@ pub fn draw_error( .margin(20u32) .caption(format!("y=logarithm error, x=1..{max_x}"), font) .x_label_area_size(30u32) - .y_label_area_size(30u32) + .y_label_area_size(50u32) .build_cartesian_2d(0f32..max_x, -0.0001f32..0.0001f32)?; chart.configure_mesh().x_labels(3).y_labels(3).draw()?; From 6290dd06acb58661c0cc100cda198e2cd1a29440 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Wed, 19 Jun 2024 16:09:05 +0300 Subject: [PATCH 4/6] plotters - BigFloat logarithm, refactor --- .../base/src/types/managed/basic/big_float.rs | 51 ++++++--- tools/plotter/src/lib.rs | 27 ++++- tools/plotter/src/logarithm_bf.rs | 102 ++++++++++++++++++ .../src/{logarithm.rs => logarithm_bu.rs} | 4 +- tools/plotter/www/index.html | 6 +- tools/plotter/www/index.js | 16 ++- 6 files changed, 179 insertions(+), 27 deletions(-) create mode 100644 tools/plotter/src/logarithm_bf.rs rename tools/plotter/src/{logarithm.rs => logarithm_bu.rs} (98%) diff --git a/framework/base/src/types/managed/basic/big_float.rs b/framework/base/src/types/managed/basic/big_float.rs index e0859e97e8..e3180cd077 100644 --- a/framework/base/src/types/managed/basic/big_float.rs +++ b/framework/base/src/types/managed/basic/big_float.rs @@ -14,6 +14,9 @@ use crate::codec::{ NestedEncodeOutput, TopDecode, TopDecodeInput, TopEncode, TopEncodeOutput, TryStaticCast, }; +/// Denomiator used for initializing BigFloats from constants. +const DENOMINATOR: i64 = 1_000_000_000; + #[derive(Debug)] #[repr(transparent)] pub struct BigFloat { @@ -177,33 +180,53 @@ impl BigFloat { } pub fn ln(&self) -> Self { - if self == &BigFloat::from(1i64) { - return BigFloat::from(0i64); + let one = BigFloat::from(1i64); + match self.cmp(&one) { + core::cmp::Ordering::Less => { + let inv = &one / self; + debug_assert!(inv > one); + inv.ln_gt_one().neg() + }, + core::cmp::Ordering::Equal => BigFloat::from(0i64), + core::cmp::Ordering::Greater => self.ln_gt_one(), } + } + + /// Computes the natural logarithm for values between 1 and 2. Performs very poorly outside of this interval. + fn ln_between_one_and_two(&self) -> Self { + let mut result = BigFloat::from_frac(-56570851, DENOMINATOR); // -0.056570851 + result *= self; + result += BigFloat::from_frac(447179550, DENOMINATOR); // 0.44717955 + result *= self; + result += BigFloat::from_frac(-1469956800, DENOMINATOR); // -1.4699568 + result *= self; + result += BigFloat::from_frac(2821202600, DENOMINATOR); // 2.8212026 + result *= self; + result += BigFloat::from_frac(-1741793900, DENOMINATOR); // -1.7417939 + + result + } + /// Computes the natural logarithm for values > 1. + fn ln_gt_one(&self) -> Self { // find the highest power of 2 less than or equal to self let trunc_val = self.trunc(); let trunc_val_unsigned = trunc_val .into_big_uint() .unwrap_or_sc_panic("log argument must be positive"); - let bit_log2 = trunc_val_unsigned.log2(); // aproximate, based on position of the most significant bit + let bit_log2 = trunc_val_unsigned.log2(); // approximate, based on position of the most significant bit + if bit_log2 == u32::MAX { + // means the input was zero, TODO: change log2 return type + return BigFloat::from(0i64); + // return None; + } let divisor = BigFloat::from(1 << bit_log2); let x = self / &divisor; // normalize to [1.0, 2.0] debug_assert!(x >= 1); debug_assert!(x <= 2); - const DENOMINATOR: i64 = 1_000_000_000; - - let mut result = BigFloat::from_frac(-56570851, DENOMINATOR); // -0.056570851 - result *= &x; - result += BigFloat::from_frac(447179550, DENOMINATOR); // 0.44717955 - result *= &x; - result += BigFloat::from_frac(-1469956800, DENOMINATOR); // -1.4699568 - result *= &x; - result += BigFloat::from_frac(2821202600, DENOMINATOR); // 2.8212026 - result *= &x; - result += BigFloat::from_frac(-1741793900, DENOMINATOR); // -1.7417939 + let mut result = x.ln_between_one_and_two(); let ln_of_2 = BigFloat::from_frac(693147180, DENOMINATOR); // 0.69314718 result += BigFloat::from(bit_log2 as i32) * ln_of_2; diff --git a/tools/plotter/src/lib.rs b/tools/plotter/src/lib.rs index 2972f468fd..8b68884dfd 100644 --- a/tools/plotter/src/lib.rs +++ b/tools/plotter/src/lib.rs @@ -3,7 +3,8 @@ use wasm_bindgen::prelude::*; use web_sys::HtmlCanvasElement; -pub mod logarithm; +pub mod logarithm_bf; +pub mod logarithm_bu; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; @@ -29,15 +30,31 @@ pub struct Point { impl Chart { /// Draw provided functions on the canvas. /// Return `Chart` struct suitable for coordinate conversion. - pub fn logarithm(canvas: HtmlCanvasElement, max_x: f32) -> Result { - let map_coord = logarithm::draw_logs(canvas, max_x).map_err(|err| err.to_string())?; + pub fn ln_big_uint(canvas: HtmlCanvasElement, max_x: f32) -> Result { + let map_coord = logarithm_bu::draw_bu_logs(canvas, max_x).map_err(|err| err.to_string())?; Ok(Chart { convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x.into(), y.into()))), }) } - pub fn logarithm_error(canvas: HtmlCanvasElement, max_x: f32) -> Result { - let map_coord = logarithm::draw_error(canvas, max_x).map_err(|err| err.to_string())?; + pub fn ln_big_uint_error(canvas: HtmlCanvasElement, max_x: f32) -> Result { + let map_coord = + logarithm_bu::draw_bu_error(canvas, max_x).map_err(|err| err.to_string())?; + Ok(Chart { + convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x.into(), y.into()))), + }) + } + + pub fn ln_big_float(canvas: HtmlCanvasElement, max_x: f32) -> Result { + let map_coord = logarithm_bf::draw_bf_logs(canvas, max_x).map_err(|err| err.to_string())?; + Ok(Chart { + convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x.into(), y.into()))), + }) + } + + pub fn ln_big_float_error(canvas: HtmlCanvasElement, max_x: f32) -> Result { + let map_coord = + logarithm_bf::draw_bf_error(canvas, max_x).map_err(|err| err.to_string())?; Ok(Chart { convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x.into(), y.into()))), }) diff --git a/tools/plotter/src/logarithm_bf.rs b/tools/plotter/src/logarithm_bf.rs new file mode 100644 index 0000000000..8126f19c71 --- /dev/null +++ b/tools/plotter/src/logarithm_bf.rs @@ -0,0 +1,102 @@ +use crate::DrawResult; +use multiversx_sc::types::BigFloat; +use multiversx_sc_scenario::api::StaticApi; +use plotters::prelude::*; +use plotters_canvas::CanvasBackend; +use web_sys::HtmlCanvasElement; + +pub fn draw_bf_logs( + canvas: HtmlCanvasElement, + max_x: f32, +) -> DrawResult Option<(f32, f32)>> { + let root = CanvasBackend::with_canvas_object(canvas) + .unwrap() + .into_drawing_area(); + + let font: FontDesc = ("sans-serif", 20.0).into(); + + root.fill(&WHITE)?; + + let mut chart = ChartBuilder::on(&root) + .margin(20u32) + .caption(format!("y=ln(x), x=1..{max_x}"), font) + .x_label_area_size(30u32) + .y_label_area_size(30u32) + .build_cartesian_2d(0f32..max_x, -5f32..5f32)?; + + chart.configure_mesh().x_labels(3).y_labels(3).draw()?; + + const RANGE_MAX: i32 = 1000; + chart.draw_series(LineSeries::new( + (0..=RANGE_MAX) + .map(|x| x as f32 * max_x / RANGE_MAX as f32) + .map(|x| (x, ln_baseline(x))), + &RED, + ))?; + + chart.draw_series(LineSeries::new( + (0..=RANGE_MAX) + .map(|x| x as f32 * max_x / RANGE_MAX as f32) + .map(|x| (x, big_float_ln(x))), + &GREEN, + ))?; + + root.present()?; + return Ok(chart.into_coord_trans()); +} + +pub fn draw_bf_error( + canvas: HtmlCanvasElement, + max_x: f32, +) -> DrawResult Option<(f32, f32)>> { + let root = CanvasBackend::with_canvas_object(canvas) + .unwrap() + .into_drawing_area(); + + let font: FontDesc = ("sans-serif", 20.0).into(); + + root.fill(&WHITE)?; + + let mut chart = ChartBuilder::on(&root) + .margin(20u32) + .caption(format!("y=logarithm error, x=1..{max_x}"), font) + .x_label_area_size(30u32) + .y_label_area_size(50u32) + .build_cartesian_2d(0f32..max_x, -0.0001f32..0.0001f32)?; + + chart.configure_mesh().x_labels(3).y_labels(3).draw()?; + + const RANGE_MAX: i32 = 1000; + chart.draw_series(LineSeries::new( + (0..=RANGE_MAX) + .map(|x| x as f32 * max_x / RANGE_MAX as f32) + .map(|x| (x, big_float_ln(x) - ln_baseline(x))), + &RED, + ))?; + + root.present()?; + return Ok(chart.into_coord_trans()); +} + +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); + let ln_dec = bf.ln(); + let ln_units = (ln_dec * PREC.into()).trunc().to_i64().unwrap(); + (ln_units as f64 / PREC as f64) as f32 +} + +fn ln_baseline(x: f32) -> f32 { + x.ln() +} + +// #[cfg(test)] +// mod test { +// #[test] +// fn sc_ln_test() { +// assert_eq!(logarithm_bu::big_uint_ln(0.0), 0.0); +// assert!(logarithm_bu::big_uint_ln(1.0) > 0.0); +// assert!(logarithm_bu::big_uint_ln(1.0) < 0.01); +// assert!(logarithm_bu::big_uint_ln(2.0) > 0.6); +// } +// } diff --git a/tools/plotter/src/logarithm.rs b/tools/plotter/src/logarithm_bu.rs similarity index 98% rename from tools/plotter/src/logarithm.rs rename to tools/plotter/src/logarithm_bu.rs index bcb57c02dc..2488983d33 100644 --- a/tools/plotter/src/logarithm.rs +++ b/tools/plotter/src/logarithm_bu.rs @@ -5,7 +5,7 @@ use plotters::prelude::*; use plotters_canvas::CanvasBackend; use web_sys::HtmlCanvasElement; -pub fn draw_logs( +pub fn draw_bu_logs( canvas: HtmlCanvasElement, max_x: f32, ) -> DrawResult Option<(f32, f32)>> { @@ -45,7 +45,7 @@ pub fn draw_logs( return Ok(chart.into_coord_trans()); } -pub fn draw_error( +pub fn draw_bu_error( canvas: HtmlCanvasElement, max_x: f32, ) -> DrawResult Option<(f32, f32)>> { diff --git a/tools/plotter/www/index.html b/tools/plotter/www/index.html index 94054ef4d4..37e874280e 100644 --- a/tools/plotter/www/index.html +++ b/tools/plotter/www/index.html @@ -17,8 +17,10 @@

MultiversX SC function plots

diff --git a/tools/plotter/www/index.js b/tools/plotter/www/index.js index 478001eebc..f9dbafe4b1 100644 --- a/tools/plotter/www/index.js +++ b/tools/plotter/www/index.js @@ -74,13 +74,21 @@ function updatePlot() { chart = null; const start = performance.now(); switch (selected.value) { - case "logarithm": + case "ln-bu": logControl.classList.remove("hide"); - chart = Chart.logarithm(canvas, Number(logMax.value)); + chart = Chart.ln_big_uint(canvas, Number(logMax.value)); break; - case "logarithm-error": + case "ln-bu-error": logControl.classList.remove("hide"); - chart = Chart.logarithm_error(canvas, Number(logMax.value)); + chart = Chart.ln_big_uint_error(canvas, Number(logMax.value)); + break; + case "ln-bf": + logControl.classList.remove("hide"); + chart = Chart.ln_big_float(canvas, Number(logMax.value)); + break; + case "ln-bf-error": + logControl.classList.remove("hide"); + chart = Chart.ln_big_float_error(canvas, Number(logMax.value)); break; default: logControl.classList.add("hide"); From 423e5322ed58b0caf0dc41c56df44982ac4ac4f0 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Wed, 19 Jun 2024 16:35:56 +0300 Subject: [PATCH 5/6] BigFloat ln - fix for inputs <=0 --- .../base/src/types/managed/basic/big_float.rs | 17 +++++++--- tools/plotter/src/logarithm_bf.rs | 31 +++++++++++-------- tools/plotter/src/logarithm_bu.rs | 2 +- .../vh_managed_types/vh_big_float.rs | 10 +++--- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/framework/base/src/types/managed/basic/big_float.rs b/framework/base/src/types/managed/basic/big_float.rs index e3180cd077..020d6dec0a 100644 --- a/framework/base/src/types/managed/basic/big_float.rs +++ b/framework/base/src/types/managed/basic/big_float.rs @@ -179,16 +179,25 @@ impl BigFloat { ManagedDecimal::::from_big_float(self, decimals) } - pub fn ln(&self) -> Self { + /// Computes the natural logarithm of the current number. + /// + /// The error is around +/- 0.00006, for all inputs. + /// + /// Will return `None` for zero or negative numbers. + pub fn ln(&self) -> Option { + if self <= &0i64 { + return None; + } + let one = BigFloat::from(1i64); match self.cmp(&one) { core::cmp::Ordering::Less => { let inv = &one / self; debug_assert!(inv > one); - inv.ln_gt_one().neg() + Some(inv.ln_gt_one().neg()) }, - core::cmp::Ordering::Equal => BigFloat::from(0i64), - core::cmp::Ordering::Greater => self.ln_gt_one(), + core::cmp::Ordering::Equal => Some(BigFloat::from(0i64)), + core::cmp::Ordering::Greater => Some(self.ln_gt_one()), } } diff --git a/tools/plotter/src/logarithm_bf.rs b/tools/plotter/src/logarithm_bf.rs index 8126f19c71..e3e70f57ec 100644 --- a/tools/plotter/src/logarithm_bf.rs +++ b/tools/plotter/src/logarithm_bf.rs @@ -81,22 +81,27 @@ 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); - let 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_dec) = bf.ln() { + let ln_units = (ln_dec * PREC.into()).trunc().to_i64().unwrap(); + (ln_units as f64 / PREC as f64) as f32 + } else { + 0.0 + } } fn ln_baseline(x: f32) -> f32 { x.ln() } -// #[cfg(test)] -// mod test { -// #[test] -// fn sc_ln_test() { -// assert_eq!(logarithm_bu::big_uint_ln(0.0), 0.0); -// assert!(logarithm_bu::big_uint_ln(1.0) > 0.0); -// assert!(logarithm_bu::big_uint_ln(1.0) < 0.01); -// assert!(logarithm_bu::big_uint_ln(2.0) > 0.6); -// } -// } +#[cfg(test)] +mod test { + #[test] + fn sc_ln_test() { + assert_eq!(super::big_float_ln(0.0), 0.0); + assert_eq!(super::big_float_ln(1.0), 0.0); + assert!(super::big_float_ln(0.5) < -0.693); + assert!(super::big_float_ln(0.5) > -0.694); + assert!(super::big_float_ln(1.0) < 0.01); + assert!(super::big_float_ln(2.0) > 0.6); + } +} diff --git a/tools/plotter/src/logarithm_bu.rs b/tools/plotter/src/logarithm_bu.rs index 2488983d33..bd10efed86 100644 --- a/tools/plotter/src/logarithm_bu.rs +++ b/tools/plotter/src/logarithm_bu.rs @@ -103,7 +103,7 @@ mod test { #[test] fn sc_ln_test() { assert_eq!(super::big_uint_ln(0.0), 0.0); - assert!(super::big_uint_ln(1.0) > 0.0); + assert!(super::big_uint_ln(1.0) >= 0.0); assert!(super::big_uint_ln(1.0) < 0.01); assert!(super::big_uint_ln(2.0) > 0.6); } diff --git a/vm/src/vm_hooks/vh_handler/vh_managed_types/vh_big_float.rs b/vm/src/vm_hooks/vh_handler/vh_managed_types/vh_big_float.rs index 1e2410fc9c..4c2c06b49a 100644 --- a/vm/src/vm_hooks/vh_handler/vh_managed_types/vh_big_float.rs +++ b/vm/src/vm_hooks/vh_handler/vh_managed_types/vh_big_float.rs @@ -8,7 +8,7 @@ use core::{ ops::{Add, Div, Mul, Neg, Sub}, }; use num_bigint::BigInt; -use num_traits::ToPrimitive; +use num_traits::{ToPrimitive, Zero}; use std::convert::TryInto; macro_rules! binary_op_method { @@ -115,16 +115,18 @@ pub trait VMHooksBigFloat: VMHooksHandlerSource + VMHooksError { fn bf_sign(&self, x: RawHandle) -> i32 { let bf = self.m_types_lock().bf_get_f64(x); + if bf.is_zero() { + return 0; + } + if !bf.is_normal() { self.vm_error(vm_err_msg::NUMBER_IS_NOT_NORMAL) } if bf.is_sign_positive() { 1 - } else if bf.is_sign_negative() { - -1 } else { - 0 + -1 } } From 80cc2904c6d77ed6c50569d8281a0928f1eceb8d Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Wed, 19 Jun 2024 18:43:00 +0300 Subject: [PATCH 6/6] fixed ManagedDecimal fmt & BigFloat tests --- .../src/big_float_operators.rs | 1 + .../base/src/types/managed/basic/big_float.rs | 4 +- .../base/src/types/managed/basic/big_uint.rs | 7 --- .../types/managed/wrapped/managed_decimal.rs | 48 +++++++++++++++---- framework/scenario/tests/big_float_test.rs | 30 ++++++------ framework/scenario/tests/big_uint_test.rs | 4 +- 6 files changed, 61 insertions(+), 33 deletions(-) diff --git a/contracts/feature-tests/big-float-features/src/big_float_operators.rs b/contracts/feature-tests/big-float-features/src/big_float_operators.rs index fcad578694..a5fae8a720 100644 --- a/contracts/feature-tests/big-float-features/src/big_float_operators.rs +++ b/contracts/feature-tests/big-float-features/src/big_float_operators.rs @@ -88,5 +88,6 @@ pub trait BigFloatOperators { #[endpoint] fn ln_big_float_ref(&self, a: &BigFloat) -> BigFloat { a.ln() + .unwrap_or_else(|| sc_panic!("log argument must pe strictly positive")) } } diff --git a/framework/base/src/types/managed/basic/big_float.rs b/framework/base/src/types/managed/basic/big_float.rs index 020d6dec0a..e7989eaaa4 100644 --- a/framework/base/src/types/managed/basic/big_float.rs +++ b/framework/base/src/types/managed/basic/big_float.rs @@ -180,9 +180,9 @@ impl BigFloat { } /// Computes the natural logarithm of the current number. - /// + /// /// The error is around +/- 0.00006, for all inputs. - /// + /// /// Will return `None` for zero or negative numbers. pub fn ln(&self) -> Option { if self <= &0i64 { diff --git a/framework/base/src/types/managed/basic/big_uint.rs b/framework/base/src/types/managed/basic/big_uint.rs index 4cd040b9e9..658697c2ec 100644 --- a/framework/base/src/types/managed/basic/big_uint.rs +++ b/framework/base/src/types/managed/basic/big_uint.rs @@ -291,13 +291,6 @@ impl BigUint { } } -// impl -// ManagedDecimal -// { -// pub fn ln(&self) -> Option>> { -// } -// } - impl Clone for BigUint { fn clone(&self) -> Self { let api = M::managed_type_impl(); diff --git a/framework/base/src/types/managed/wrapped/managed_decimal.rs b/framework/base/src/types/managed/wrapped/managed_decimal.rs index 1775a9e75c..dbe5c6b16d 100644 --- a/framework/base/src/types/managed/wrapped/managed_decimal.rs +++ b/framework/base/src/types/managed/wrapped/managed_decimal.rs @@ -1,10 +1,11 @@ use crate::{ abi::{TypeAbi, TypeAbiFrom, TypeName}, api::{ - const_handles, use_raw_handle, BigFloatApiImpl, BigIntApiImpl, ManagedTypeApi, - StaticVarApiImpl, + const_handles, use_raw_handle, BigFloatApiImpl, BigIntApiImpl, HandleConstraints, + ManagedBufferApiImpl, ManagedTypeApi, StaticVarApiImpl, }, formatter::{FormatBuffer, FormatByteReceiver, SCDisplay}, + proxy_imports::{ManagedBuffer, ManagedType}, types::{BigFloat, BigUint}, }; @@ -410,12 +411,43 @@ impl TypeAbi } impl SCDisplay for ManagedDecimal { fn fmt(&self, f: &mut F) { - let sf = self.decimals.scaling_factor(); - let temp = &self.data / sf.deref(); - temp.fmt(f); - f.append_bytes(b"."); - let temp = &self.data % sf.deref(); - temp.fmt(f); + let full_str_handle: M::ManagedBufferHandle = + use_raw_handle(const_handles::MBUF_TEMPORARY_1); + M::managed_type_impl().bi_to_string(self.data.handle.clone(), full_str_handle.clone()); + let len = M::managed_type_impl().mb_len(full_str_handle.clone()); + let nr_dec = self.decimals.num_decimals(); + + if len > nr_dec { + let temp_str_handle: M::ManagedBufferHandle = + use_raw_handle(const_handles::MBUF_TEMPORARY_2); + let _ = M::managed_type_impl().mb_copy_slice( + full_str_handle.clone(), + 0, + len - nr_dec, + temp_str_handle.clone(), + ); + f.append_managed_buffer(&ManagedBuffer::from_raw_handle( + temp_str_handle.get_raw_handle(), + )); + f.append_bytes(b"."); + let _ = M::managed_type_impl().mb_copy_slice( + full_str_handle.clone(), + len - nr_dec, + nr_dec, + temp_str_handle.clone(), + ); + f.append_managed_buffer(&ManagedBuffer::from_raw_handle( + temp_str_handle.get_raw_handle(), + )); + } else { + f.append_bytes(b"0."); + for _ in len..nr_dec { + f.append_bytes(b"0"); + } + f.append_managed_buffer(&ManagedBuffer::from_raw_handle( + full_str_handle.get_raw_handle(), + )); + } } } diff --git a/framework/scenario/tests/big_float_test.rs b/framework/scenario/tests/big_float_test.rs index d5e0a16526..2b98cbae41 100644 --- a/framework/scenario/tests/big_float_test.rs +++ b/framework/scenario/tests/big_float_test.rs @@ -48,7 +48,7 @@ fn big_float_overflow_test_rs() { #[test] fn big_float_ln_test_rs() { let x = BigFloat::::from(23i64); - let ln_x = x.ln(); + let ln_x = x.ln().unwrap(); assert_eq!(ln_x.to_managed_decimal(9usize).to_string(), "3.135514648"); assert!(ln_x.is_close( &BigFloat::from_frac(3135514648, 1_000_000_000), // 3.135514648 @@ -56,7 +56,7 @@ fn big_float_ln_test_rs() { )); let big = BigFloat::::from(382747812i64); - let ln_big = big.ln(); + let ln_big = big.ln().unwrap(); assert_eq!( ln_big.to_managed_decimal(9usize).to_string(), "19.762913880" @@ -67,7 +67,7 @@ fn big_float_ln_test_rs() { )); let biggest = BigFloat::::from(999999999i64); - let ln_biggest = biggest.ln(); + let ln_biggest = biggest.ln().unwrap(); assert_eq!( ln_biggest.to_managed_decimal(9usize).to_string(), "20.723319778" @@ -78,7 +78,7 @@ fn big_float_ln_test_rs() { )); let small = BigFloat::::from_frac(3i64, 2i64); - let ln_small = small.ln(); + let ln_small = small.ln().unwrap(); assert_eq!( ln_small.to_managed_decimal(9usize).to_string(), "0.405448248" @@ -89,19 +89,21 @@ fn big_float_ln_test_rs() { )); let smallest = BigFloat::::from(1i64); - let ln_smallest = smallest.ln(); - assert_eq!(ln_smallest.to_managed_decimal(9usize).to_string(), "0.0"); + let ln_smallest = smallest.ln().unwrap(); + assert_eq!( + ln_smallest.to_managed_decimal(9usize).to_string(), + "0.000000000" + ); assert!(ln_smallest.is_close( &BigFloat::from(0i64), // 0.0 &BigFloat::from_frac(1, 100_000_000) )); - // fails for now, accuracy is not great around 1.1 - // let y = BigFloat::::from_frac(11i64, 10i64); - // let ln_y = y.ln(); - // assert_eq!(ln_y.to_managed_decimal(9usize).to_string(), "0.095310179"); - // assert!(ln_y.is_close( - // &BigFloat::from_frac(95310179, 1_000_000_000), // 0.095310179 - // &BigFloat::from_frac(1, 1_000_000_000) - // )); + let y = BigFloat::::from_frac(11i64, 10i64); + let ln_y = y.ln().unwrap(); + assert_eq!(ln_y.to_managed_decimal(9usize).to_string(), "0.095251830"); + assert!(ln_y.is_close( + &BigFloat::from_frac(95251830, 1_000_000_000), // 0.095310179 + &BigFloat::from_frac(1, 1_000_000_000) + )); } diff --git a/framework/scenario/tests/big_uint_test.rs b/framework/scenario/tests/big_uint_test.rs index fae86ecd6d..60ad0ab78f 100644 --- a/framework/scenario/tests/big_uint_test.rs +++ b/framework/scenario/tests/big_uint_test.rs @@ -11,9 +11,9 @@ fn assert_big_uint_ln(x: u32, ln_str: &str) { fn test_big_uint_ln() { assert_big_uint_ln(23, "3.135514649"); // vs. 3.1354942159291497 first 6 decimals are ok - assert_big_uint_ln(1, "0.60599"); + assert_big_uint_ln(1, "0.000060599"); assert_big_uint_ln(2, "0.693207779"); // vs. 0.6931471805599453 - assert_big_uint_ln(3, "1.98595430"); // vs. 1.0986122886681096 + assert_big_uint_ln(3, "1.098595430"); // vs. 1.0986122886681096 assert_big_uint_ln(4, "1.386354959"); // vs. 1.3862943611198906 assert_big_uint_ln(5, "1.609481340"); // vs. 1.6094379124341003 assert_big_uint_ln(6, "1.791742610"); // vs. 1.791759469228055