Skip to content

Commit

Permalink
Polish fixed-point-wasm (#1281)
Browse files Browse the repository at this point in the history
* Comment nits

* Change `raw` to `bigint` in fixed-point-wasm

* Use string template in fixed-point example

* Convert wasm errors to JS `error` objects

* Add missing `track_caller` attribute

* Accept bigints in fixed point methods

* Replace `convertSharesToBase` in SDK w/ inline fixed-point math

* Use `bigint` property in fixed-point example

* Use `HyperdriveSdkError` in place of `Error`

* Fix conversion macro imports

* Make fixed-point arguments more flexible

* Add `toNumber` method to fixed-point

* Update example

* Build

* Delete previous fixedpoint file

* Update fixed-point usage in SDK

* Update example and README

* Adjust tests for new math lib

* nit

* Make `fixed` function arg required
  • Loading branch information
ryangoree authored Jul 17, 2024
1 parent 51df1cb commit 273506a
Show file tree
Hide file tree
Showing 21 changed files with 552 additions and 406 deletions.
27 changes: 11 additions & 16 deletions apps/sdk-sandbox/scripts/fixed-point.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import {
fixed,
initSync,
parseFixed,
wasmBuffer,
} from "@delvtech/fixed-point-wasm";
import { fixed, initSync, wasmBuffer } from "@delvtech/fixed-point-wasm";

// Initialize the WASM module
initSync(wasmBuffer);

// BigInt
// bigints

const totalSupply = fixed(22950342684077248430458n);
const sharePrice = fixed(1094205545459194143n);
const totalSupplyInBase = totalSupply.mulDown(sharePrice);
const totalSupply = 22950342684077248430458n;
const sharePrice = 1094205545459194143n;
const totalSupplyInBase = fixed(totalSupply).mulDown(sharePrice);

console.log("Total supply in base:", totalSupplyInBase.toString());
// => 25112.392235106171381320
console.log(`Total supply in base: ${totalSupplyInBase.bigint}`);
// => 25112392235106171381320

// String
// strings and numbers

const amount = parseFixed("1_000.123456789012345678e18");
const fee = parseFixed("0.025e18");
const amount = fixed("1_000.123456789012345678e18");
const fee = fixed(0.025e18);
const feeAmount = amount.mulUp(fee);

console.log("Fee amount:", feeAmount.toString());
console.log(`Fee amount: ${feeAmount}`);
// => 25.003086419725308642
6 changes: 4 additions & 2 deletions crates/delv-core/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl ToU256 for &str {
}

impl ToU256 for JsValue {
#[track_caller]
fn to_u256(&self) -> Result<U256, Error> {
self.to_bigint()?.to_u256()
}
Expand Down Expand Up @@ -193,15 +194,16 @@ macro_rules! try_bigint {
($value:expr) => {{
let location = std::panic::Location::caller();
let string = stringify!($value);
BigInt::from_str(string).map_err(|_| type_error_at!(location, "Invalid BigInt: {}", string))
BigInt::from_str(string)
.map_err(|_| $crate::type_error_at!(location, "Invalid BigInt: {}", string))
}};
}

/// Create a `BigInt` from an integer literal
#[macro_export]
macro_rules! bigint {
($value:expr) => {
try_bigint!($value).unwrap()
$crate::try_bigint!($value).unwrap()
};
}

Expand Down
2 changes: 1 addition & 1 deletion crates/delv-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ macro_rules! type_error {
// Convert a Error to a JsValue via `.into()` or `::from()`
impl From<Error> for JsValue {
fn from(error: Error) -> JsValue {
error.to_string().into()
js_sys::Error::new(&error.to_string()).into()
}
}

Expand Down
17 changes: 9 additions & 8 deletions crates/fixed-point-wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@ import {
// Initialize the WASM module
initSync(wasmBuffer);

// Use `fixed` with bigints
const amount = fixed(1_000123456789012345678e18n);

// Use `parseFixed` with strings
const fee = parseFixed("0.025e18");
// Use `fixed` with bigints, numbers, or strings
const amount = fixed(1_000123456789012345678n);
const fee = fixed(0.025e18);

// Perform fixed-point arithmetic
const feeAmount = amount.mulUp(fee);

console.log(feeAmount.bigint);
// => 25003086419725308642n

console.log(feeAmount.toString());
// => 25.003086419725308642
// => "25.003086419725308642"

console.log(feeAmount.raw);
// => 25003086419725308642n
console.log(feeAmount.toNumber());
// => 25.00308641972531
```

## Building
Expand Down
147 changes: 99 additions & 48 deletions crates/fixed-point-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use delv_core::{
error::{Error, ToResult},
};
use fixedpointmath::FixedPoint;
use js_sys::BigInt;
use js_sys::{parse_float, BigInt, JsString};
use rand::{thread_rng, Rng};
use wasm_bindgen::prelude::*;

Expand All @@ -30,7 +30,7 @@ pub fn getVersion() -> String {
env!("CARGO_PKG_VERSION").to_string()
}

/// A fixed-point number.
/// An 18-decimal fixed-point number.
#[wasm_bindgen]
pub struct Fixed(FixedPoint);

Expand All @@ -44,118 +44,169 @@ impl fmt::Display for Fixed {

#[wasm_bindgen]
impl Fixed {
/// Create a new `Fixed` instance from an 18-decimal scaled bigint.
#[wasm_bindgen(constructor)]
pub fn new(raw: Option<BigInt>) -> Result<Fixed, Error> {
Ok(Fixed(raw.to_fixed()?))
pub fn new(value: Option<RawValue>) -> Result<Fixed, Error> {
Ok(Fixed(match value {
Some(value) => value.to_fixed()?,
None => FixedPoint::default(),
}))
}

/// Get the scaled bigint representation of this fixed-point number.
pub fn valueOf(&self) -> Result<BigInt, Error> {
self.0.to_bigint()
}

/// Get the 18-decimal scaled bigint representation of this fixed-point number.
#[wasm_bindgen(getter)]
pub fn raw(&self) -> Result<BigInt, Error> {
pub fn bigint(&self) -> Result<BigInt, Error> {
self.0.to_bigint()
}

/// Get the float representation of this fixed-point number.
///
/// __Caution__: This method may lose precision.
pub fn toNumber(&self) -> f64 {
parse_float(&self.0.to_string())
}

/// Get the formatted string representation of this fixed-point number.
pub fn toString(&self) -> String {
self.0.to_string()
}

/// Add a fixed-point number to this one.
pub fn add(&self, other: &Fixed) -> Fixed {
Fixed(self.0 + other.0)
pub fn add(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0 + other.to_fixed()?))
}

/// Subtract a fixed-point number from this one.
pub fn sub(&self, other: &Fixed) -> Fixed {
Fixed(self.0 - other.0)
pub fn sub(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0 - other.to_fixed()?))
}

/// Multiply this fixed-point number by another.
pub fn mul(&self, other: &Fixed) -> Fixed {
Fixed(self.0 * other.0)
pub fn mul(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0 * other.to_fixed()?))
}

/// Divide this fixed-point number by another.
pub fn div(&self, other: &Fixed) -> Fixed {
Fixed(self.0 / other.0)
pub fn div(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0 / other.to_fixed()?))
}

/// Multiply this fixed-point number by another, then divide by a divisor,
/// rounding down.
pub fn mulDivDown(&self, other: &Fixed, divisor: &Fixed) -> Fixed {
Fixed(self.0.mul_div_down(other.0, divisor.0))
pub fn mulDivDown(&self, other: &Other, divisor: &Other) -> Result<Fixed, Error> {
Ok(Fixed(
self.0.mul_div_down(other.to_fixed()?, divisor.to_fixed()?),
))
}

/// Multiply this fixed-point number by another, then divide by a divisor,
/// rounding up.
pub fn mulDivUp(&self, other: &Fixed, divisor: &Fixed) -> Fixed {
Fixed(self.0.mul_div_up(other.0, divisor.0))
pub fn mulDivUp(&self, other: &Other, divisor: &Other) -> Result<Fixed, Error> {
Ok(Fixed(
self.0.mul_div_up(other.to_fixed()?, divisor.to_fixed()?),
))
}

/// Multiply this fixed-point number by another, rounding down.
pub fn mulDown(&self, other: &Fixed) -> Fixed {
Fixed(self.0.mul_down(other.0))
pub fn mulDown(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0.mul_down(other.to_fixed()?)))
}

/// Multiply this fixed-point number by another, rounding up.
pub fn mulUp(&self, other: &Fixed) -> Fixed {
Fixed(self.0.mul_up(other.0))
pub fn mulUp(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0.mul_up(other.to_fixed()?)))
}

/// Divide this fixed-point number by another, rounding down.
pub fn divDown(&self, other: &Fixed) -> Fixed {
Fixed(self.0.div_down(other.0))
pub fn divDown(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0.div_down(other.to_fixed()?)))
}

/// Divide this fixed-point number by another, rounding up.
pub fn divUp(&self, other: &Fixed) -> Fixed {
Fixed(self.0.div_up(other.0))
pub fn divUp(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0.div_up(other.to_fixed()?)))
}

/// Raise this fixed-point number to the power of another.
pub fn pow(&self, other: &Fixed) -> Result<Fixed, Error> {
Ok(Fixed(self.0.pow(other.0).to_result()?))
pub fn pow(&self, other: &Other) -> Result<Fixed, Error> {
Ok(Fixed(self.0.pow(other.to_fixed()?).to_result()?))
}
}

// Utils //

/// Create a new `Fixed` instance from a bigint.
/// Create a new `Fixed` instance from a raw value.
///
/// @example
//
/// ```js
/// const x = fixed(BigInt(15e17));
/// console.log(x.toString());
/// const fromBigint = fixed(1500000000000000000n);
/// const fromNumber = fixed(1.5e18);
/// const fromString = fixed('1.5e18');
///
/// console.log(fromBigint.toString());
/// // => 1.500000000000000000
/// ```
#[wasm_bindgen]
pub fn fixed(raw: Option<BigInt>) -> Result<Fixed, Error> {
Fixed::new(raw)
}

/// Parses a scaled string into a `Fixed` instance.
///
/// @example
/// console.log(fromNumber.toString());
/// // => 1.500000000000000000
///
/// ```js
/// const x = parseFixed('1.5e18');
/// console.log(x.toString());
/// console.log(fromString.toString());
/// // => 1.500000000000000000
/// ```
#[wasm_bindgen]
pub fn parseFixed(string: &str) -> Result<Fixed, Error> {
Ok(Fixed(FixedPoint::from_str(string).to_result()?))
pub fn fixed(raw: RawValue) -> Result<Fixed, Error> {
Fixed::new(Some(raw))
}

#[wasm_bindgen]
pub fn ln(x: BigInt) -> Result<Fixed, Error> {
pub fn randInRange(min: RawValue, max: RawValue) -> Result<Fixed, Error> {
let mut rng = thread_rng();
Ok(Fixed(rng.gen_range(min.to_fixed()?..max.to_fixed()?)))
}

#[wasm_bindgen]
pub fn ln(x: Other) -> Result<Fixed, Error> {
let int = FixedPoint::ln(x.to_i256()?).to_result()?;
Ok(Fixed(FixedPoint::try_from(int).to_result()?))
}

// Types //

#[wasm_bindgen]
pub fn randInRange(min: BigInt, max: BigInt) -> Result<Fixed, Error> {
let mut rng = thread_rng();
Ok(Fixed(rng.gen_range(min.to_fixed()?..max.to_fixed()?)))
extern "C" {
#[wasm_bindgen(typescript_type = "bigint | number | string")]
pub type RawValue;

#[wasm_bindgen(method)]
fn toString(this: &RawValue) -> JsString;

#[wasm_bindgen(method)]
fn valueOf(this: &RawValue) -> BigInt;

#[wasm_bindgen(typescript_type = "Fixed | bigint")]
pub type Other;

#[wasm_bindgen(method)]
fn valueOf(this: &Other) -> BigInt;
}

impl ToFixedPoint for RawValue {
fn to_fixed(&self) -> Result<FixedPoint, Error> {
let str = self.toString().as_string().unwrap_or_default();
if self.js_typeof() == "string" || self.js_typeof() == "number" {
FixedPoint::from_str(&str).to_result()
} else {
str.to_fixed()
}
}
}

impl ToFixedPoint for &Other {
fn to_fixed(&self) -> Result<FixedPoint, Error> {
self.valueOf().to_fixed()
}
}
17 changes: 9 additions & 8 deletions packages/fixed-point-wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@ import {
// Initialize the WASM module
initSync(wasmBuffer);

// Use `fixed` with bigints
const amount = fixed(1_000123456789012345678e18n);

// Use `parseFixed` with strings
const fee = parseFixed("0.025e18");
// Use `fixed` with bigints, numbers, or strings
const amount = fixed(1_000123456789012345678n);
const fee = fixed(0.025e18);

// Perform fixed-point arithmetic
const feeAmount = amount.mulUp(fee);

console.log(feeAmount.bigint);
// => 25003086419725308642n

console.log(feeAmount.toString());
// => 25.003086419725308642
// => "25.003086419725308642"

console.log(feeAmount.raw);
// => 25003086419725308642n
console.log(feeAmount.toNumber());
// => 25.00308641972531
```

## Building
Expand Down
Loading

0 comments on commit 273506a

Please sign in to comment.