From 416da643b80e25799ce299acda7f3e99a9173621 Mon Sep 17 00:00:00 2001 From: rostyslavtyshko <106978277+rostyslavtyshko@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:34:34 +0200 Subject: [PATCH] IFP256 (#107) ## Type of change - 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 Closes #\ --- libs/fixed_point/README.md | 2 +- libs/fixed_point/src/ifp256.sw | 306 ++++++++++++++++++ libs/fixed_point/src/lib.sw | 1 + tests/Forc.toml | 2 + .../ifp256_div_test/.gitignore | 2 + .../ifp256_div_test/Forc.toml | 8 + .../unsigned_numbers/ifp256_div_test/mod.rs | 1 + .../ifp256_div_test/src/main.sw | 26 ++ .../ifp256_div_test/tests/mod.rs | 22 ++ .../unsigned_numbers/ifp256_test/.gitignore | 2 + .../unsigned_numbers/ifp256_test/Forc.toml | 8 + tests/src/unsigned_numbers/ifp256_test/mod.rs | 1 + .../unsigned_numbers/ifp256_test/src/main.sw | 70 ++++ .../unsigned_numbers/ifp256_test/tests/mod.rs | 22 ++ tests/src/unsigned_numbers/mod.rs | 2 + 15 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 libs/fixed_point/src/ifp256.sw create mode 100644 tests/src/unsigned_numbers/ifp256_div_test/.gitignore create mode 100644 tests/src/unsigned_numbers/ifp256_div_test/Forc.toml create mode 100644 tests/src/unsigned_numbers/ifp256_div_test/mod.rs create mode 100644 tests/src/unsigned_numbers/ifp256_div_test/src/main.sw create mode 100644 tests/src/unsigned_numbers/ifp256_div_test/tests/mod.rs create mode 100644 tests/src/unsigned_numbers/ifp256_test/.gitignore create mode 100644 tests/src/unsigned_numbers/ifp256_test/Forc.toml create mode 100644 tests/src/unsigned_numbers/ifp256_test/mod.rs create mode 100644 tests/src/unsigned_numbers/ifp256_test/src/main.sw create mode 100644 tests/src/unsigned_numbers/ifp256_test/tests/mod.rs diff --git a/libs/fixed_point/README.md b/libs/fixed_point/README.md index 87373f55..83a1aa45 100644 --- a/libs/fixed_point/README.md +++ b/libs/fixed_point/README.md @@ -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. diff --git a/libs/fixed_point/src/ifp256.sw b/libs/fixed_point/src/ifp256.sw new file mode 100644 index 00000000..3285ed0b --- /dev/null +++ b/libs/fixed_point/src/ifp256.sw @@ -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 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, + } + } +} diff --git a/libs/fixed_point/src/lib.sw b/libs/fixed_point/src/lib.sw index 08e88a1c..290d74df 100644 --- a/libs/fixed_point/src/lib.sw +++ b/libs/fixed_point/src/lib.sw @@ -6,3 +6,4 @@ dep ufp128; dep ifp64; dep ifp128; +dep ifp256; diff --git a/tests/Forc.toml b/tests/Forc.toml index b34333bc..87c0e069 100644 --- a/tests/Forc.toml +++ b/tests/Forc.toml @@ -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", diff --git a/tests/src/unsigned_numbers/ifp256_div_test/.gitignore b/tests/src/unsigned_numbers/ifp256_div_test/.gitignore new file mode 100644 index 00000000..77d3844f --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_div_test/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/tests/src/unsigned_numbers/ifp256_div_test/Forc.toml b/tests/src/unsigned_numbers/ifp256_div_test/Forc.toml new file mode 100644 index 00000000..1797a5dd --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_div_test/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "ifp256_div_test" + +[dependencies] +fixed_point = { path = "../../../../libs/fixed_point" } diff --git a/tests/src/unsigned_numbers/ifp256_div_test/mod.rs b/tests/src/unsigned_numbers/ifp256_div_test/mod.rs new file mode 100644 index 00000000..14f00389 --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_div_test/mod.rs @@ -0,0 +1 @@ +mod tests; diff --git a/tests/src/unsigned_numbers/ifp256_div_test/src/main.sw b/tests/src/unsigned_numbers/ifp256_div_test/src/main.sw new file mode 100644 index 00000000..30321492 --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_div_test/src/main.sw @@ -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 +} diff --git a/tests/src/unsigned_numbers/ifp256_div_test/tests/mod.rs b/tests/src/unsigned_numbers/ifp256_div_test/tests/mod.rs new file mode 100644 index 00000000..b62af624 --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_div_test/tests/mod.rs @@ -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; + } +} diff --git a/tests/src/unsigned_numbers/ifp256_test/.gitignore b/tests/src/unsigned_numbers/ifp256_test/.gitignore new file mode 100644 index 00000000..77d3844f --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_test/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/tests/src/unsigned_numbers/ifp256_test/Forc.toml b/tests/src/unsigned_numbers/ifp256_test/Forc.toml new file mode 100644 index 00000000..9dacf045 --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_test/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "ifp256_test" + +[dependencies] +fixed_point = { path = "../../../../libs/fixed_point" } diff --git a/tests/src/unsigned_numbers/ifp256_test/mod.rs b/tests/src/unsigned_numbers/ifp256_test/mod.rs new file mode 100644 index 00000000..14f00389 --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_test/mod.rs @@ -0,0 +1 @@ +mod tests; diff --git a/tests/src/unsigned_numbers/ifp256_test/src/main.sw b/tests/src/unsigned_numbers/ifp256_test/src/main.sw new file mode 100644 index 00000000..af1419ce --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_test/src/main.sw @@ -0,0 +1,70 @@ +script; + +use fixed_point::ifp256::IFP256; +use fixed_point::ufp128::UFP128; + +fn main() -> bool { + // arithmetic + let one = IFP256::from(UFP128::from((1, 0))); + let two = IFP256::from(UFP128::from((2, 0))); + let mut res = two + one; + assert(IFP256::from(UFP128::from((3, 0))) == res); + + let ifp_256_10 = IFP256::from(UFP128::from((10, 0))); + res = ifp_256_10 + two; + assert(IFP256::from(UFP128::from((12, 0))) == res); + + let ifp_256_48 = IFP256::from(UFP128::from((48, 0))); + let six = IFP256::from(UFP128::from((6, 0))); + res = ifp_256_48 - six; + assert(IFP256::from(UFP128::from((42, 0))) == res); + + let ifp_256_169 = IFP256::from(UFP128::from((169, 0))); + let ifp_256_13 = IFP256::from(UFP128::from((13, 0))); + res = ifp_256_169 - ifp_256_13; + assert(IFP256::from(UFP128::from((156, 0))) == res); + + // recip + let mut value = IFP256::from(UFP128::from((1, 3))); + res = IFP256::recip(value); + assert(IFP256::from(UFP128::from((0, 18446744073709551613))) == res); + + // trunc + let mut value = IFP256::from(UFP128::from((1, 3))); + res = value.trunc(); + assert(IFP256::from_uint(1) == res); + + // floor + value = IFP256::from(UFP128::from((1, 3))); + res = value.floor(); + assert(IFP256::from_uint(1) == res); + + // fract + value = IFP256::from(UFP128::from((1, 3))); + res = value.fract(); + assert(IFP256::from(UFP128::from((0, 3))) == res); + + value = IFP256::from_uint(1); + res = value.fract(); + assert(IFP256::from_uint(0) == res); + + // ceil + value = IFP256::from(UFP128::from((1, 3))); + res = value.ceil(); + assert(IFP256::from_uint(2) == res); + + value = IFP256::from_uint(1); + res = value.ceil(); + assert(IFP256::from_uint(1) == res); + + // round + value = IFP256::from(UFP128::from((1, 3))); + res = value.round(); + assert(IFP256::from_uint(1) == res); + + value = IFP256::from(UFP128::from((1, (1 << 63) + 1))); + res = value.round(); + assert(IFP256::from_uint(2) == res); + + true +} diff --git a/tests/src/unsigned_numbers/ifp256_test/tests/mod.rs b/tests/src/unsigned_numbers/ifp256_test/tests/mod.rs new file mode 100644 index 00000000..a8531c2e --- /dev/null +++ b/tests/src/unsigned_numbers/ifp256_test/tests/mod.rs @@ -0,0 +1,22 @@ +use fuels::prelude::{abigen, launch_provider_and_get_wallet}; + +abigen!(Script( + name = "TestIfp256", + abi = "src/unsigned_numbers/ifp256_test/out/debug/ifp256_test-abi.json" +),); + +mod success { + + use super::*; + + #[tokio::test] + async fn runs_ifp256_test_script() { + let path_to_bin = "src/unsigned_numbers/ifp256_test/out/debug/ifp256_test.bin"; + + let wallet = launch_provider_and_get_wallet().await; + + let instance = TestIfp256::new(wallet, path_to_bin); + + let _result = instance.main().call().await; + } +} diff --git a/tests/src/unsigned_numbers/mod.rs b/tests/src/unsigned_numbers/mod.rs index ac858dd1..9c7bb6fb 100644 --- a/tests/src/unsigned_numbers/mod.rs +++ b/tests/src/unsigned_numbers/mod.rs @@ -1,5 +1,7 @@ mod ifp128_div_test; mod ifp128_test; +mod ifp256_div_test; +mod ifp256_test; mod ifp64_div_test; mod ifp64_exp_test; mod ifp64_mul_test;