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

UFP64 #31

Merged
merged 24 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
267 changes: 267 additions & 0 deletions sway_libs/src/fixed_point/ufp/ufp64/ufp64.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
library ufp64;
//! A wrapper around u64 type for a library for Sway for mathematical functions operating with signed 32.32-bit fixed point numbers.
bitzoic marked this conversation as resolved.
Show resolved Hide resolved
use core::num::*;
use std::assert::assert;
use std::math::*;
use std::revert::revert;
use std::u128::U128;
use std::math::Exponentiate;
rostyslavtyshko marked this conversation as resolved.
Show resolved Hide resolved

pub struct UFP64 {
value: u64,
}

impl UFP64 {
rostyslavtyshko marked this conversation as resolved.
Show resolved Hide resolved
/// Convinience function to know the denominator
bitzoic marked this conversation as resolved.
Show resolved Hide resolved
pub fn denominator() -> u64 {
1 << 32
}

pub fn zero() -> Self {
Self { value: 0 }
}

/// Creates UFP64 from u64. Note that ~UFP64::from(1) is 1 / 2^32 and not 1.
pub fn from(value: u64) -> Self {
rostyslavtyshko marked this conversation as resolved.
Show resolved Hide resolved
Self { value }
}

/// The smallest value that can be represented by this type.
pub fn min() -> Self {
Self {
value: ~u64::min(),
}
}

/// The largest value that can be represented by this type,
rostyslavtyshko marked this conversation as resolved.
Show resolved Hide resolved
pub fn max() -> Self {
Self {
value: ~u64::max(),
}
}

/// The size of this type in bits.
pub fn bits() -> u32 {
64
}
}

impl core::ops::Eq for UFP64 {
fn eq(self, other: Self) -> bool {
self.value == other.value
}
}

impl core::ops::Ord for UFP64 {
fn gt(self, other: Self) -> bool {
self.value > other.value
}

fn lt(self, other: Self) -> bool {
self.value < other.value
}
}

impl core::ops::Add for UFP64 {
/// Add a UFP64 to a UFP64. Panics on overflow.
fn add(self, other: Self) -> Self {
Self {
value: self.value + other.value,
}
}
}

impl core::ops::Subtract for UFP64 {
/// Subtract a UFP64 from a UFP64. Panics of overflow.
fn subtract(self, other: Self) -> Self {
// If trying to subtract a larger number, panic.
assert(self.value >= other.value);

Self {
value: self.value - other.value,
}
}
}

impl core::ops::Multiply for UFP64 {
/// Multiply a UFP64 with a UFP64. Panics of overflow.
fn multiply(self, other: Self) -> Self {
let self_u128 = ~U128::from(0, self.value);
let other_u128 = ~U128::from(0, other.value);

let self_multiply_other = self_u128 * other_u128;
let res_u128 = self_multiply_other >> 32;
if res_u128.upper != 0 {
// panic on overflow
revert(0);
}

Self {
value: res_u128.lower,
}
}
}

impl core::ops::Divide for UFP64 {
/// Divide a UFP64 by a UFP64. Panics if divisor is zero.
fn divide(self, divisor: Self) -> Self {
let zero = ~UFP64::zero();
assert(divisor != zero);

let denominator = ~U128::from(0, ~Self::denominator());
// Conversion to U128 done to ensure no overflow happen
// and maximal precision is avaliable
// as it makes possible to multiply by the denominator in
// all cases
let self_u128 = ~U128::from(0, self.value);
let divisor_u128 = ~U128::from(0, divisor.value);

// Multiply by denominator to ensure accuracy
let res_u128 = self_u128 * denominator / divisor_u128;

if res_u128.upper != 0 {
// panic on overflow
revert(0);
}
Self {
value: res_u128.lower,
}
}
}

impl UFP64 {
/// Creates UFP64 that correponds to a unsigned integer
pub fn from_uint(uint: u64) -> Self {
Self {
value: ~Self::denominator() * uint,
}
}
}

impl UFP64 {
rostyslavtyshko marked this conversation as resolved.
Show resolved Hide resolved
/// Takes the reciprocal (inverse) of a number, `1/x`.
pub fn recip(number: UFP64) -> Self {
let one = ~UFP64::from_uint(1);

let res = one / number;
res
}

/// Returns the integer part of `self`.
/// This means that non-integer numbers are always truncated towards zero.
pub fn trunc(self) -> Self {
Self {
// first move to the right (divide by the denominator)
// to get rid of fractional part, than move to the
// left (multiply by the denominator), to ensure
// fixed-point structure
value: (self.value >> 32) << 32,
}
}
}

impl UFP64 {
/// Returns the largest integer less than or equal to `self`.
pub fn floor(self) -> Self {
return self.trunc();
}

/// Returns the fractional part of `self`.
pub fn fract(self) -> Self {
Self {
// first move to the left (multiply by the denominator)
// to get rid of integer part, than move to the
// right (divide by the denominator), to ensure
// fixed-point structure
value: (self.value << 32) >> 32,
}
}
}

impl UFP64 {
/// Returns the smallest integer greater than or equal to `self`.
pub fn ceil(self) -> Self {
if self.fract().value != 0 {
let res = self.trunc() + ~UFP64::from_uint(1);
return res;
}
return self;
}
}

impl UFP64 {
/// Returns the nearest integer to `self`. Round half-way cases away from
pub fn round(self) -> Self {
let floor = self.floor();
let ceil = self.ceil();
let diff_self_floor = self - floor;
let diff_ceil_self = ceil - self;

// check if we are nearer to the floor or to the ceiling
bitzoic marked this conversation as resolved.
Show resolved Hide resolved
if diff_self_floor < diff_ceil_self {
return floor;
} else {
return ceil;
}
}
}

impl Root for UFP64 {
/// Sqaure root for UFP64
fn sqrt(self) -> Self {
let nominator_root = self.value.sqrt();
// Need to multiple over 2 ^ 16, as the sqare root of the denominator
bitzoic marked this conversation as resolved.
Show resolved Hide resolved
// is also taken and we need to ensure that the denominator is constant
let nominator = nominator_root << 16;
Self {
value: nominator,
}
}
}

impl Exponentiate for UFP64 {
/// Power function. x ^ exponent
fn pow(self, exponent: Self) -> Self {
let demoninator_power = ~UFP64::denominator();
let exponent_int = exponent.value >> 32;
let nominator_pow = ~U128::from(0, self.value).pow(~U128::from(0, exponent_int));
// As we need to ensure the fixed point structure
// which means that the denominator is always 2 ^ 32
// we need to delete the nominator by 2 ^ (32 * exponent - 1)
// - 1 is the formula is due to denominator need to stay 2 ^ 32
let nominator = nominator_pow >> demoninator_power * (exponent_int - 1);

if nominator.upper != 0 {
// panic on overflow
revert(0);
}
Self {
value: nominator.lower,
}
}
}

trait Exponent {
rostyslavtyshko marked this conversation as resolved.
Show resolved Hide resolved
/// Exponent function. e ^ x
fn exp(exponent: Self) -> Self;
}

impl Exponent for UFP64 {
/// Exponent function. e ^ x
fn exp(exponent: Self) -> Self {
let one = ~UFP64::from_uint(1);

//coefficients in the Taylor series up to the seventh power
let p2 = ~UFP64::from(2147483648); // p2 == 1 / 2!
let p3 = ~UFP64::from(715827882); // p3 == 1 / 3!
let p4 = ~UFP64::from(178956970); // p4 == 1 / 4!
let p5 = ~UFP64::from(35791394); // p5 == 1 / 5!
let p6 = ~UFP64::from(5965232); // p6 == 1 / 6!
let p7 = ~UFP64::from(852176); // p7 == 1 / 7!
// common technique to counter loosing sugnifucant numbers in usual approximation
// Taylor series approximation of exponantiation function minus 1. The subtraction is done to deal with accuracy issues
let res_minus_1 = exponent + exponent * exponent * (p2 + exponent * (p3 + exponent * (p4 + exponent * (p5 + exponent * (p6 + exponent * p7)))));
let res = res_minus_1 + one;
res
}
}
2 changes: 2 additions & 0 deletions sway_libs/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ dep signed_integers/i128;
dep signed_integers/i256;
dep nft/nft;
dep string/string;

dep fixed_point/ufp/ufp64/ufp64;
bitzoic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "ufp64_div_test"

[dependencies]
sway_libs = { path = "../../../../../../../sway_libs" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
script;

use std::assert::assert;
use sway_libs::ufp64::UFP64;

fn main() -> bool {
let one = ~UFP64::from_uint(1);
let two = ~UFP64::from_uint(2);
let mut res = two / one;
assert(two == res);

let ufp_64_10 = ~UFP64::from_uint(10);
res = ufp_64_10 / two;
assert(~UFP64::from_uint(5) == res);

let ufp_64_48 = ~UFP64::from_uint(48);
let six = ~UFP64::from_uint(6);
res = ufp_64_48 / six;
assert(~UFP64::from_uint(8) == res);

let ufp_64_169 = ~UFP64::from_uint(169);
let ufp_64_13 = ~UFP64::from_uint(13);
res = ufp_64_169 / ufp_64_13;
assert(~UFP64::from_uint(13) == res);

let ufp_64_35 = ~UFP64::from_uint(35);
let ufp_64_5 = ~UFP64::from_uint(5);
res = ufp_64_35 / ufp_64_5;
assert(~UFP64::from_uint(7) == res);

true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "ufp64_exp_test"

[dependencies]
sway_libs = { path = "../../../../../../../sway_libs" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
script;

use std::assert::assert;
use sway_libs::ufp64::UFP64;


fn main() -> bool {
let one = ~UFP64::from_uint(1);
let mut res = ~UFP64::exp(one);
assert(res.value == 11674811894);

let two = ~UFP64::from_uint(2);
res = ~UFP64::exp(two);
assert(res.value == 31700949040);

let four = ~UFP64::from_uint(4);
res = ~UFP64::exp(four);
assert(res.value == 222506572928);

let seven = ~UFP64::from_uint(7);
res = ~UFP64::exp(seven);
assert(res.value == 2819944203710);

let ten = ~UFP64::from_uint(10);
res = ~UFP64::exp(ten);
assert(res.value == 20833521987056);

true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "ufp64_mul_test"

[dependencies]
sway_libs = { path = "../../../../../../../sway_libs" }
Loading