Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BigFloat natural logarithm #1681

Merged
merged 7 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*"
}
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,9 @@ pub trait BigFloatOperators {
r /= b;
r
}
#[endpoint]
fn ln_big_float_ref(&self, a: &BigFloat) -> BigFloat {
a.ln()
.unwrap_or_else(|| sc_panic!("log argument must pe strictly positive"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self::Api, usize> {
let number = self.ln_big_float_ref(&BigFloat::from(a));
number.to_managed_decimal(precision)
}
}
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -12,61 +11,6 @@ fn world() -> ScenarioWorld {
blockchain
}

#[test]
fn big_float_overflow_test_rs() {
let exp = 1_080i32;

let first = BigFloat::<StaticApi>::from_sci(1_005, -3)
.pow(exp)
.to_fixed_point(&(100_000_000_000_000_000i64.into()))
.into_big_uint();

let second = BigFloat::<StaticApi>::from_sci(1_005, -3)
.pow(exp)
.to_fixed_point(&(10_000_000_000_000_000i64.into()))
.into_big_uint();

let third_float = BigFloat::<StaticApi>::from_sci(1_005, -3)
.pow(exp)
.to_managed_decimal(17usize);
let third = third_float.into_raw_units();

let forth_float = BigFloat::<StaticApi>::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::<StaticApi>::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");
Expand Down
6 changes: 4 additions & 2 deletions contracts/feature-tests/big-float-features/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
}

Expand Down
62 changes: 49 additions & 13 deletions framework/base/src/types/managed/basic/big_float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<M: ManagedTypeApi> {
Expand Down Expand Up @@ -176,30 +179,63 @@ impl<M: ManagedTypeApi> BigFloat<M> {
ManagedDecimal::<M, T>::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<Self> {
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);
Some(inv.ln_gt_one().neg())
},
core::cmp::Ordering::Equal => Some(BigFloat::from(0i64)),
core::cmp::Ordering::Greater => Some(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;
Expand Down
5 changes: 3 additions & 2 deletions framework/base/src/types/managed/basic/big_uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,12 @@ impl<M: ManagedTypeApi> BigUint<M> {
///
/// Returns `None` for 0.
pub fn ln(&self) -> Option<ManagedDecimal<M, ConstDecimals<9>>> {
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;
Expand Down
48 changes: 40 additions & 8 deletions framework/base/src/types/managed/wrapped/managed_decimal.rs
Original file line number Diff line number Diff line change
@@ -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},
};

Expand Down Expand Up @@ -410,12 +411,43 @@ impl<M: ManagedTypeApi, const DECIMALS: NumDecimals> TypeAbi
}
impl<M: ManagedTypeApi, D: Decimals> SCDisplay for ManagedDecimal<M, D> {
fn fmt<F: FormatByteReceiver>(&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(),
));
}
}
}

Expand Down
Loading
Loading