This repository has been archived by the owner on Aug 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[COMP-1165] fixed precision decimal implementation (wip)
As discussed bigint + u8 for decimal representation also avoiding the use of generics for the most part. For now the only imaginable way to have errors in my mind is decimal mismatch. Lots to fill in here and some open questions Do we want this in a separate crate? I imagine it is possible the eth-rpc crate may ultimately depend on this functionality. I just figured out that u128 is natively available in rust on all targets as LLVM has support for it rust simply exposes that. See [this pr](rust-lang/rust#38482) from late 2016. This has been [stablized in 1.26](https://blog.rust-lang.org/2018/05/10/Rust-1.26.html). The representable range here even with a token that is using 18 decimals of precision is 19 9s. If you have 10^19 of something I think it is alright to say that you need to do two transactions. I believe that u128 represents the best combination of speed flexibility and consistency. It is very unlikely to me that any token balance will natively overflow this representation.
- Loading branch information
Wayne Nilsen
committed
Nov 20, 2020
1 parent
2353bc9
commit 1a46e3f
Showing
4 changed files
with
121 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
use num_bigint::BigUint; | ||
|
||
/// The type of the decimal field. | ||
pub type DecimalType = u8; | ||
|
||
/// The type of the mantissa field | ||
pub type MantissaType = BigUint; | ||
|
||
/// Represents a decimal number in base 10 with fixed precision. The number can be as precise as | ||
/// the maximum value of the DecimalType and there is no upper bound on the numbers size. | ||
/// | ||
/// For example, if the mantissa is 123456789 and decimals is 4 the number that is represented is | ||
/// 12345.6789. | ||
#[derive(Clone, PartialEq, Debug)] | ||
pub struct FixedPrecision { | ||
mantissa: MantissaType, | ||
decimals: DecimalType, | ||
} | ||
|
||
/// Error type for fixed precision math. | ||
#[derive(Copy, Clone, PartialEq, Debug)] | ||
pub enum MathError { | ||
PrecisionMismatch, | ||
} | ||
|
||
impl FixedPrecision { | ||
/// Create a new FixedPrecision number from parts. The mantissa is used "raw" and not scaled | ||
/// in any way | ||
pub fn new<T: Into<MantissaType>>(mantissa: T, decimals: DecimalType) -> Self { | ||
let mantissa = mantissa.into(); | ||
FixedPrecision { mantissa, decimals } | ||
} | ||
|
||
/// A helper function for downstream math functions, returns an error if and only if the | ||
/// number of decimals do not match | ||
fn check_decimals(self: &Self, other: &Self) -> Result<(), MathError> { | ||
if self.decimals == other.decimals { | ||
Ok(()) | ||
} else { | ||
Err(MathError::PrecisionMismatch) | ||
} | ||
} | ||
|
||
/// Add two FixedPrecision numbers together. Note the signature uses borrowed values this is | ||
/// because the underlying storage is arbitrarily large and we do not want to copy the values. | ||
pub fn add(self: &Self, rhs: &Self) -> Result<Self, MathError> { | ||
self.check_decimals(rhs)?; | ||
// note - this cannot fail with BigUint but that will change based on underlying storage | ||
let new_mantissa = &self.mantissa + &rhs.mantissa; | ||
|
||
Ok(Self::new(new_mantissa, self.decimals)) | ||
} | ||
|
||
/// Create the representation of 1 in the number of decimals requested. For example one(3) | ||
/// will return a fixed precision number with 1000 as the mantissa and 3 as the number of decimals | ||
pub fn one<T: Into<DecimalType> + Copy>(decimals: T) -> Self { | ||
let ten: MantissaType = 10u8.into(); | ||
let decimals: DecimalType = decimals.into(); | ||
let new_mantissa = ten.pow(decimals as u32); | ||
Self::new(new_mantissa, decimals) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_one() { | ||
let expected = FixedPrecision::new(1000u32, 3); | ||
let actual = FixedPrecision::one(3); | ||
assert_eq!(expected, actual); | ||
} | ||
|
||
#[test] | ||
fn test_add_happy_path() -> Result<(), MathError> { | ||
let a = FixedPrecision::one(2); | ||
let b = FixedPrecision::one(2); | ||
// note - automatic borrow of `a` here (rust elides the (&a).add for you | ||
let actual = a.add(&b)?; | ||
|
||
// make sure nothing has changed | ||
assert_eq!(a, b); | ||
assert_eq!(a, FixedPrecision::one(2)); | ||
assert_eq!(b, FixedPrecision::one(2)); | ||
|
||
let expected = FixedPrecision::new(200u8, 2); | ||
assert_eq!(actual, expected); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn test_add_decimal_mismatch() { | ||
let a = FixedPrecision::one(2); | ||
let b = FixedPrecision::one(3); | ||
// note - automatic borrow of `a` here (rust elides the (&a).add for you | ||
let actual = a.add(&b); | ||
assert!(actual.is_err()); | ||
let actual = actual.err().unwrap(); | ||
let expected = MathError::PrecisionMismatch; | ||
assert_eq!(actual, expected); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ use frame_system::ensure_signed; | |
#[cfg(test)] | ||
mod mock; | ||
|
||
mod fixed_precision; | ||
#[cfg(test)] | ||
mod tests; | ||
|
||
|