Skip to content

Commit

Permalink
IFP256 (#107)
Browse files Browse the repository at this point in the history
## Type of change

<!--Delete points that do not apply-->

- New feature
Added `IFP256` - 256-bit signed fixed point number

## Changes

The following changes have been made:

-Added `IFP256` - 256-bit signed fixed point number

## Notes

Closes #114 

## Related Issues

<!--Delete everything after the "#" symbol and replace it with a number.
No spaces between hash and number-->

Closes #\<issue number\>
  • Loading branch information
rostyslavtyshko authored Apr 4, 2023
1 parent 715a90d commit 416da64
Show file tree
Hide file tree
Showing 15 changed files with 474 additions and 1 deletion.
2 changes: 1 addition & 1 deletion libs/fixed_point/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# Overview

The Fixed Point Number library provides a library to use fixed-point numbers in Sway. It has 3 distinct unsigned types: `UFP32`, `UFP64` and `UFP128` as well as 2 signed types - `IFP64` and `IFP128`. These types are stack allocated.
The Fixed Point Number library provides a library to use fixed-point numbers in Sway. It has 3 distinct unsigned types: `UFP32`, `UFP64` and `UFP128` as well as 3 signed types `IFP64`, `IFP128` and `IFP256`. These types are stack allocated.

This type is stored as a `u32`, `u64` or `U128` under the hood. Therefore the size can be known at compile time and the length is static.

Expand Down
306 changes: 306 additions & 0 deletions libs/fixed_point/src/ifp256.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
library ifp256;
// A wrapper library around the type for mathematical functions operating with signed 256-bit fixed point numbers.
use std::math::{Exponent, Power, Root};
use ::ufp128::UFP128;

pub struct IFP256 {
underlying: UFP128,
non_negative: bool,
}

impl From<UFP128> for IFP256 {
/// Creates IFP256 from UFP128. Note that IFP256::from(1) is 1 / 2^128 and not 1.
fn from(value: UFP128) -> Self {
Self {
underlying: value,
non_negative: true,
}
}

fn into(self) -> UFP128 {
self.underlying
}
}

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

/// The largest value that can be represented by this type.
pub fn max() -> Self {
Self::from(UFP128::max())
}

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

pub fn zero() -> Self {
Self::from(UFP128::zero())
}

pub fn sign_reverse(self) -> Self {
Self {
underlying: self.underlying,
non_negative: !self.non_negative,
}
}
}

impl core::ops::Eq for IFP256 {
fn eq(self, other: Self) -> bool {
self.underlying == other.underlying && (self.underlying == Self::zero().underlying || self.non_negative == other.non_negative)
}
}

impl core::ops::Ord for IFP256 {
fn gt(self, other: Self) -> bool {
if self.non_negative && !self.non_negative {
true
} else if !self.non_negative && self.non_negative {
false
} else if self.non_negative && self.non_negative {
self.underlying > other.underlying
} else {
self.underlying < other.underlying
}
}

fn lt(self, other: Self) -> bool {
if self.non_negative && !self.non_negative {
false
} else if !self.non_negative && self.non_negative {
true
} else if self.non_negative && self.non_negative {
self.underlying < other.underlying
} else {
self.underlying > other.underlying
}
}
}

impl core::ops::Add for IFP256 {
/// Add a IFP256 to a IFP256. Panics on overflow.
fn add(self, other: Self) -> Self {
let mut underlying = self.underlying;
let mut non_negative = self.non_negative;
if self.non_negative && !other.non_negative {
if self.underlying > other.underlying {
underlying = self.underlying - other.underlying;
} else {
underlying = other.underlying - self.underlying;
non_negative = false;
}
} else if !self.non_negative && other.non_negative {
if self.underlying > other.underlying {
underlying = self.underlying - other.underlying;
} else {
underlying = other.underlying - self.underlying;
non_negative = true;
}
} else {
// same sign
underlying = self.underlying + other.underlying;
}
Self {
underlying: underlying,
non_negative: non_negative,
}
}
}

impl core::ops::Subtract for IFP256 {
/// Subtract a IFP256 from a IFP256. Panics of overflow.
fn subtract(self, other: Self) -> Self {
self + other.sign_reverse()
}
}

impl core::ops::Multiply for IFP256 {
/// Multiply a IFP256 with a IFP256. Panics of overflow.
fn multiply(self, other: Self) -> Self {
let non_negative = if (self.non_negative
&& !self.non_negative)
|| (!self.non_negative
&& self.non_negative)
{
false
} else {
true
};
Self {
underlying: self.underlying * other.underlying,
non_negative: non_negative,
}
}
}

impl core::ops::Divide for IFP256 {
/// Divide a IFP256 by a IFP256. Panics if divisor is zero.
fn divide(self, divisor: Self) -> Self {
let non_negative = if (self.non_negative
&& !self.non_negative)
|| (!self.non_negative
&& self.non_negative)
{
false
} else {
true
};
Self {
underlying: self.underlying / divisor.underlying,
non_negative: non_negative,
}
}
}

impl IFP256 {
/// Creates IFP256 that correponds to a unsigned integer
pub fn from_uint(uint: u64) -> Self {
Self::from(UFP128::from_uint(uint))
}
}

impl IFP256 {
/// Takes the reciprocal (inverse) of a number, `1/x`.
pub fn recip(number: IFP256) -> Self {
Self {
underlying: UFP128::recip(number.underlying),
non_negative: number.non_negative,
}
}

/// Returns the integer part of `self`.
/// This means that non-integer numbers are always truncated towards zero.
pub fn trunc(self) -> Self {
Self {
underlying: self.underlying.trunc(),
non_negative: self.non_negative,
}
}
}

impl IFP256 {
/// Returns the largest integer less than or equal to `self`.
pub fn floor(self) -> Self {
if self.non_negative {
self.trunc()
} else {
let trunc = self.underlying.trunc();
if trunc != UFP128::zero() {
self.trunc() - Self::from(UFP128::from((1, 0)))
} else {
self.trunc()
}
}
}

/// Returns the fractional part of `self`.
pub fn fract(self) -> Self {
Self {
underlying: self.underlying.fract(),
non_negative: self.non_negative,
}
}
}

impl IFP256 {
/// Returns the smallest integer greater than or equal to `self`.
pub fn ceil(self) -> Self {
let mut underlying = self.underlying;
let mut non_negative = self.non_negative;

if self.non_negative {
underlying = self.underlying.ceil();
} else {
let ceil = self.underlying.ceil();
if ceil != self.underlying {
underlying = ceil + UFP128::from((1, 0));
if ceil == UFP128::from((1, 0)) {
non_negative = true;
}
} else {
underlying = ceil;
}
}
Self {
underlying: underlying,
non_negative: self.non_negative,
}
}
}

impl IFP256 {
/// Returns the nearest integer to `self`. Round half-way cases away from
pub fn round(self) -> Self {
let mut underlying = self.underlying;
let mut non_negative = self.non_negative;

if self.non_negative {
underlying = self.underlying.round();
} else {
let floor = self.underlying.floor();
let ceil = self.underlying.ceil();
let diff_self_floor = self.underlying - floor;
let diff_ceil_self = ceil - self.underlying;
let underlying = if diff_self_floor > diff_ceil_self {
floor
} else {
ceil
};
}
Self {
underlying: underlying,
non_negative: self.non_negative,
}
}
}

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

// Coefficients in the Taylor series up to the seventh power
let p2 = IFP256::from(UFP128::from((0, 2147483648))); // p2 == 1 / 2!
let p3 = IFP256::from(UFP128::from((0, 715827882))); // p3 == 1 / 3!
let p4 = IFP256::from(UFP128::from((0, 178956970))); // p4 == 1 / 4!
let p5 = IFP256::from(UFP128::from((0, 35791394))); // p5 == 1 / 5!
let p6 = IFP256::from(UFP128::from((0, 5965232))); // p6 == 1 / 6!
let p7 = IFP256::from(UFP128::from((0, 852176))); // p7 == 1 / 7!
// Common technique to counter losing significant numbers in usual approximation
// Taylor series approximation of exponentiation 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
}
}

impl Power for IFP256 {
/// Power function. x ^ exponent
fn pow(self, exponent: Self) -> Self {
let non_negative = if !self.non_negative {
// roots of negative numbers are complex numbers which we lack for now
assert(exponent.underlying.floor() == exponent.underlying);

let div_2 = exponent.underlying / UFP128::from((2, 0));
div_2.floor() == div_2
} else {
true
};
let mut underlying = self.underlying.pow(exponent.underlying);
if !exponent.non_negative {
underlying = UFP128::recip(underlying);
}
Self {
underlying: underlying,
non_negative: non_negative,
}
}
}
1 change: 1 addition & 0 deletions libs/fixed_point/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ dep ufp128;

dep ifp64;
dep ifp128;
dep ifp256;
2 changes: 2 additions & 0 deletions tests/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ members = [
"./src/unsigned_numbers/ifp64_test",
"./src/unsigned_numbers/ufp128_div_test",
"./src/unsigned_numbers/ufp128_test",
"./src/unsigned_numbers/ifp256_div_test",
"./src/unsigned_numbers/ifp256_test",
"./src/unsigned_numbers/ifp128_div_test",
"./src/unsigned_numbers/ifp128_test",
"./src/reentrancy/reentrancy_attacker_abi",
Expand Down
2 changes: 2 additions & 0 deletions tests/src/unsigned_numbers/ifp256_div_test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
8 changes: 8 additions & 0 deletions tests/src/unsigned_numbers/ifp256_div_test/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "ifp256_div_test"

[dependencies]
fixed_point = { path = "../../../../libs/fixed_point" }
1 change: 1 addition & 0 deletions tests/src/unsigned_numbers/ifp256_div_test/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod tests;
26 changes: 26 additions & 0 deletions tests/src/unsigned_numbers/ifp256_div_test/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
script;

use fixed_point::ifp256::IFP256;
use fixed_point::ufp128::UFP128;

fn main() -> bool {
let zero = IFP256::from(UFP128::from((0, 0)));
let mut up = IFP256::from(UFP128::from((1, 0)));
let mut down = IFP256::from(UFP128::from((2, 0)));
let mut res = up / down;
assert(res == IFP256::from(UFP128::from((0, 9223372036854775808))));

up = IFP256::from(UFP128::from((4, 0)));
down = IFP256::from(UFP128::from((2, 0)));
res = up / down;

assert(res == IFP256::from(UFP128::from((2, 0))));

up = IFP256::from(UFP128::from((9, 0)));
down = IFP256::from(UFP128::from((4, 0)));
res = up / down;

assert(res == IFP256::from(UFP128::from((2, 4611686018427387904))));

true
}
22 changes: 22 additions & 0 deletions tests/src/unsigned_numbers/ifp256_div_test/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use fuels::prelude::{abigen, launch_provider_and_get_wallet};

abigen!(Script(
name = "TestIfp256Div",
abi = "src/unsigned_numbers/ifp256_div_test/out/debug/ifp256_div_test-abi.json"
),);

mod success {

use super::*;

#[tokio::test]
async fn runs_ifp256_div_test_script() {
let path_to_bin = "src/unsigned_numbers/ifp256_div_test/out/debug/ifp256_div_test.bin";

let wallet = launch_provider_and_get_wallet().await;

let instance = TestIfp256Div::new(wallet, path_to_bin);

let _result = instance.main().call().await;
}
}
2 changes: 2 additions & 0 deletions tests/src/unsigned_numbers/ifp256_test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Loading

0 comments on commit 416da64

Please sign in to comment.