├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── decimal_core ├── Cargo.toml └── src │ ├── base.rs │ ├── big_ops.rs │ ├── by_number.rs │ ├── checked_ops.rs │ ├── factories.rs │ ├── lib.rs │ ├── ops.rs │ ├── others.rs │ ├── structs.rs │ └── utils.rs ├── package-lock.json └── src ├── lib.rs ├── traits.rs ├── uint.rs └── walkthrough.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test project 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | rust-latest: 13 | runs-on: ubuntu-latest 14 | container: rust:latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | - name: Build and test with Rust latest 19 | run: | 20 | cargo build --verbose 21 | cargo test --verbose 22 | 23 | rust-nightly: 24 | runs-on: ubuntu-latest 25 | container: rustlang/rust:nightly 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | - name: Build and test with Rust nightly 30 | run: | 31 | cargo build --verbose 32 | cargo test --verbose 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | target/ 3 | Cargo.lock 4 | decimal_core/target -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "safe_decimal_macro" 3 | version = "0.1.4" 4 | edition = "2021" 5 | readme = "README.md" 6 | authors = ["Mateusz Zając "] 7 | repository = "https://github.com/invariant-labs/decimal" 8 | license = "MIT" 9 | description = "This is a Rust fixed-point numeric library targeting blockchain development. Originally created and used as a part of the Invariant Protocol. The current version leverages macros, traits and generics to exchange dozens of lines of error prone code with a single line and generating the rest." 10 | 11 | [dependencies] 12 | safe_decimal_core = { path = "decimal_core" } 13 | integer-sqrt = "0.1.5" 14 | uint = { version = "0.9", default-features = false } 15 | num-traits = { version = "0.2.14", default-features = false } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decimal library 2 | 3 | [![Decimal macro crate](https://img.shields.io/crates/v/safe_decimal_core.svg)](https://crates.io/crates/safe_decimal_core) 4 | 5 | This is a Rust fixed-point numeric library targeting blockchain. It was created purely for practical reasons as a fast and simple way to use checked math with given decimal precision. 6 | 7 | It has achieved present form over several iterations, first being implemented inside _Synthetify protocol_. 8 | The current version leverages macros, traits and generics to exchange dozens of lines of error prone code with a single line and generating the rest. In this form it is used inside of _Invariant protocol_ and was audited as a part of it. 9 | 10 | It allows a definition of multiple types with different precisions and primitive types and calculations in between them, see below for a quick example. 11 | 12 | ## Quickstart 13 | 14 | The library is used by adding a macro `#[decimal(k)]`, where _k_ is a desired decimal precision (number decimal places after the dot). 15 | 16 | This macro generates an implementation of several generic traits for each struct it is called on allowing basic operations in between them. 17 | 18 | ### Basic example 19 | 20 | Having imported the library you can declare a type like so: 21 | 22 | #[decimal(2)] 23 | #[derive(Default, PartialEq, Debug, Clone, Copy)] 24 | struct Percentage(u32); 25 | 26 | - `#[decimal(3, u128)]` - call to the macro generating the code for decimal 27 | - `#[derive(Default, Debug, Clone, Copy, PartialEq)]` - derivation of some common built-in traits (these five are needed) 28 | - `struct R(u32);` - declaration of the struct itself 29 | 30 | ### Deserialization 31 | 32 | Named structs can be deserialized without a problem like so: 33 | 34 | #[decimal(6)] 35 | #[zero_copy] 36 | #[derive(AnchorSerialize, AnchorDeserialize, ...)] 37 | pub struct Price { 38 | pub v: u128, 39 | } 40 | 41 | ### Basic operations 42 | 43 | All methods generated by the macro use _checked math_ and panic on overflow. Operators are overloaded where possible for ease of use. 44 | 45 | Basic example using types defined above would look like this: 46 | 47 | let price = Price::from_integer(10); // this corresponds with 10 * 10^k so 10^7 in this case 48 | 49 | let discount = Percentage::new(10); // using new doesn't account for decimal places so 0.10 here 50 | 51 | // addition expects being called for left and right values being of the same type 52 | // multiplication doesn't so you can be used like this: 53 | let price = price * (Percentage::from_integer(1) - discount); // the resulting type is always the type of the left value 54 | 55 | For more examples continue to the [`walkthrough.rs`](https://github.com/invariant-labs/decimal/blob/master/src/walkthrough.rs) 56 | 57 | ## Parameters for the macro 58 | 59 | As mentioned the first argument of macro controls the amount of places after the dot. It can range between 0 and 38. It can be read by `Price::scale()`. 60 | The second one is optional and can be a bit harder to grasp 61 | 62 | ### The Big Type 63 | 64 | The second argument taken has a weird name of a _big type_. It sets the type that is to be used when calling the methods with \__big_\_ in the name. Its purpose is to avoid _temporary overflows_ so an overflow that occurs while calculating theS return value despite that value fitting in the given type. Consider the example below 65 | 66 | #[decimal(2)] 67 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 68 | struct Percentage(u8, u128); 69 | 70 | let p = Percentage(110); // 110% or 1.1 71 | 72 | // assert_eq!(p * p, Percentage(121)); <- this would panic 73 | assert_eq!(p.big_mul(p), Percentage(121)); <- this will work fine 74 | 75 | To understand why it works like that look at the multiplication of decimal does under the hood: 76 | 77 | # What happens inside (on the math side) 78 | 79 | Most of this library uses really basic math, a few things that might not be obvious are listed below 80 | 81 | ## Keeping the scale 82 | 83 | An multiplication of two percentages (scale of 2) using just the values would look like this : 84 | 85 | `(x / 10^2) / (y / 10^2) = x/y` 86 | 87 | (God i hate gh for not allowing LaTeX) 88 | 89 | Using numbers it would look like this: 90 | 91 | `10% / 10% = 10 / 10 = 1` 92 | 93 | Which is obviously wrong. What we need is multiplying everything by `10^scale` at every division. So it should look like this 94 | 95 | `(x / 10^scale) / (y / 10^scale) × 10^scale = x / y × 10^scale` 96 | 97 | Which checks out with the example above 98 | 99 | In general at every multiplication of values there needs to be a division, and vice versa. This was the first purpose of this library - to abstract it away to make for less code, bugs and wasted time. 100 | 101 | The important thing here is that multiplication has to occur before division to keep the precision, but this is also abstracted away. 102 | 103 | ## Rounding errors 104 | 105 | By default every method rounds down but has a counterpart ending with _up_ rounding the opposite way. 106 | 107 | Rounding works by addition of `denominator - 1` to the numerator, so the _mul_up_ would look like so: 108 | 109 | `(x × y + 10^scale - 1) / 10^scale` 110 | 111 | For example for `10% × 1%` 112 | 113 | `(10 × 1 + (10^2 - 1)) / (10^2) = 109 / 100 = 1%` 114 | 115 | # What happens inside (on a code level) 116 | 117 | As you do know by this point the whole library is in a form of macro. Inside of it is an implementation of several traits in a generic form to allow calling methods between any two of the implementations. 118 | 119 | - `Decimal` - all other traits are dependent on it, and by implementing it you can you your implementation with any of the other traits. One of use cases my be implementing it on base 2 120 | - `type U: Debug + Default;` - an _associated type_, the primitive (or not) where value is kept, the type of first field in the struct on which macro was called 121 | - `fn get(&self) -> Self::U;` - the value of a decimal 122 | - `fn new(value: Self::U) -> Self;` - the constructor 123 | - `fn max_value() -> Self::U` - maximum value of underlying type 124 | - `fn max_instance() -> Self` - `max_value()` wrapped by decimal 125 | - `fn here>(&self) -> Y;` - same as get, but also 'tries into' the needed value 126 | - `fn scale() -> u8;` - the amount of decimal places (given in the macro) 127 | - `fn one>() -> T;` - basically `10^scale`, evaluated on the compile time 128 | - `fn almost_one>() -> T;` - same as above but `-1`, also on compile time 129 | - `std::ops` - addition, subtraction, multiplication and division together with there assignment counterparts (+=) 130 | - `pub trait BigOps` - same as above but with previously mentioned big types used when calculating 131 | - `pub trait Others` - trait for future operations if needed, right now with only two methods 132 | - `fn mul_up(self, rhs: T) -> Self;` - multiplication, rounding uo 133 | - `fn div_up(self, rhs: T) -> Self;` - division, rounding up 134 | - `pub trait Factories` - methods used as constructors (excluding new) 135 | 136 | - `fn from_integer(integer: T) -> Self;` - creates self with value of: `integer × 10^scale` 137 | - `fn from_scale(integer: T, scale: u8) -> Self;` - creates self with value of: `integer × 10^(scale - given\_scale)` 138 | - `fn checked_from_scale(val: T, scale: u8) -> ` - checked version of `from_scale` 139 | - `fn from_scale_up(integer: T, scale: u8) -> Self;` - same as above but with rounding up 140 | 141 | - `pub trait BetweenDecimals` - used for conversion between different types, possibly with different scales 142 | - `pub trait ToValue` and `pub trait ByNumber` - can be used together to take overflowing values outside of a type and then put it inside, shouldn't be needed often 143 | -------------------------------------------------------------------------------- /decimal_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "safe_decimal_core" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["Mateusz Zając "] 6 | repository = "https://github.com/invariant-labs/decimal" 7 | readme = "../README.md" 8 | description = "This is a Rust fixed-point numeric library targeting blockchain development. Originally created and used as a part of the Invariant Protocol. The current version leverages macros, traits and generics to exchange dozens of lines of error prone code with a single line and generating the rest." 9 | license = "MIT" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = { version = "1.0.67", default-features = false } 16 | quote = { version = "1.0.33", default-features = false } 17 | regex = { version = "1", default-features = false } 18 | syn = { version = "2.0.38", features = ["full"] } 19 | -------------------------------------------------------------------------------- /decimal_core/src/base.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | 3 | use crate::DecimalCharacteristics; 4 | 5 | pub fn generate_base(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 6 | let DecimalCharacteristics { 7 | struct_name, 8 | underlying_type, 9 | scale: parsed_scale, 10 | field_name, 11 | .. 12 | } = characteristics; 13 | 14 | let denominator = 10u128.pow(parsed_scale as u32); 15 | let almost_denominator = denominator.checked_sub(1).unwrap(); 16 | 17 | proc_macro::TokenStream::from(quote!( 18 | impl Decimal for #struct_name { 19 | type U = #underlying_type; 20 | 21 | fn get(&self) -> #underlying_type { 22 | self.#field_name 23 | } 24 | 25 | fn new(value: Self::U) -> Self { 26 | let mut created = #struct_name::default(); 27 | created.#field_name = value; 28 | created 29 | } 30 | 31 | fn max_value() -> Self::U { 32 | Self::U::MAX 33 | } 34 | 35 | fn max_instance() -> Self { 36 | Self::new(Self::max_value()) 37 | } 38 | 39 | fn here>(&self) -> T { 40 | match T::try_from(self.#field_name) { 41 | Ok(v) => v, 42 | Err(_) => core::panic!("could not parse {} to {}", "T", "u8"), 43 | } 44 | } 45 | 46 | fn scale() -> u8 { 47 | #parsed_scale 48 | } 49 | 50 | fn one>() -> T { 51 | match T::try_from(#denominator) { 52 | Ok(v) => v, 53 | Err(_) => core::panic!("denominator wouldn't fit into this type",), 54 | } 55 | } 56 | 57 | fn checked_one>() -> core::result::Result where 58 | T::Error: core::fmt::Display, 59 | { 60 | T::try_from(#denominator).map_err(|err| alloc::format!("checked_one: can not get one to type {} : {}", core::any::type_name::(), alloc::string::ToString::to_string(&err))) 61 | } 62 | 63 | fn almost_one>() -> T { 64 | match T::try_from(#almost_denominator) { 65 | Ok(v) => v, 66 | Err(_) => core::panic!("denominator wouldn't fit into this type",), 67 | } 68 | } 69 | } 70 | )) 71 | } 72 | -------------------------------------------------------------------------------- /decimal_core/src/big_ops.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use quote::quote; 3 | 4 | use crate::utils::string_to_ident; 5 | use crate::DecimalCharacteristics; 6 | 7 | pub fn generate_big_ops(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 8 | let DecimalCharacteristics { 9 | struct_name, 10 | big_type, 11 | underlying_type, 12 | .. 13 | } = characteristics; 14 | 15 | let name_str = &struct_name.to_string(); 16 | let underlying_str = &underlying_type.to_string(); 17 | let big_str = &big_type.to_string(); 18 | 19 | let module_name = string_to_ident("tests_big_ops_", &name_str); 20 | 21 | proc_macro::TokenStream::from(quote!( 22 | impl BigOps for #struct_name 23 | where 24 | T::U: TryInto<#big_type>, 25 | { 26 | fn big_mul(self, rhs: T) -> Self { 27 | Self::new( 28 | #big_type::try_from(self.get()) 29 | .unwrap_or_else(|_| core::panic!("decimal: lhs value can't fit into `{}` type in {}::big_mul()", #big_str, #name_str)) 30 | .checked_mul( 31 | rhs.get() 32 | .try_into() 33 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::big_mul()", #big_str, #name_str)) 34 | ) 35 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_mul()", #name_str)) 36 | .checked_div( 37 | T::one() 38 | ) 39 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_mul()", #name_str)) 40 | .try_into() 41 | .unwrap_or_else(|_| core::panic!("decimal: overflow casting result to `{}` type in method {}::big_mul()", #underlying_str, #name_str)) 42 | 43 | ) 44 | } 45 | 46 | fn big_mul_up(self, rhs: T) -> Self { 47 | Self::new( 48 | #big_type::try_from(self.get()) 49 | .unwrap_or_else(|_| core::panic!("decimal: lhs value can't fit into `{}` type in {}::big_mul_up()", #big_str, #name_str)) 50 | .checked_mul( 51 | rhs.get() 52 | .try_into() 53 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::big_mul_up()", #big_str, #name_str)) 54 | ) 55 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_mul_up()", #name_str)) 56 | .checked_add(T::almost_one()) 57 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_mul_up()", #name_str)) 58 | .checked_div( 59 | T::one() 60 | ) 61 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_mul_up()", #name_str)) 62 | .try_into() 63 | .unwrap_or_else(|_| core::panic!("decimal: overflow casting result to `{}` type in method {}::big_mul_up()", #underlying_str, #name_str)) 64 | ) 65 | } 66 | 67 | fn big_div(self, rhs: T) -> Self { 68 | Self::new( 69 | #big_type::try_from(self.get()) 70 | .unwrap_or_else(|_| core::panic!("decimal: lhs value can't fit into `{}` type in {}::big_div()", #big_str, #name_str)) 71 | .checked_mul( 72 | T::one() 73 | ) 74 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_div()", #name_str)) 75 | .checked_div( 76 | rhs.get() 77 | .try_into() 78 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::big_div()", #big_str, #name_str)) 79 | ) 80 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_div()", #name_str)) 81 | .try_into() 82 | .unwrap_or_else(|_| core::panic!("decimal: overflow casting result to `{}` type in method {}::big_div()", #underlying_str, #name_str)) 83 | ) 84 | } 85 | fn checked_big_div(self, rhs: T) -> core::result::Result { 86 | Ok( 87 | Self::new( 88 | #big_type::try_from(self.get()) 89 | .map_err(|_| alloc::format!("decimal: lhs value can't fit into `{}` type in {}::big_div()", #big_str, #name_str))? 90 | .checked_mul( 91 | T::one() 92 | ) 93 | .ok_or_else(|| alloc::format!("decimal: overflow in method {}::big_div()", #name_str))? 94 | .checked_div( 95 | rhs.get() 96 | .try_into() 97 | .map_err(|_| alloc::format!("decimal: rhs value can't fit into `{}` type in {}::big_div()", #big_str, #name_str))? 98 | ) 99 | .ok_or_else(|| alloc::format!("decimal: overflow in method {}::big_div()", #name_str))? 100 | .try_into() 101 | .map_err(|_| alloc::format!("decimal: overflow casting result to `{}` type in method {}::big_div()", #underlying_str, #name_str))? 102 | ) 103 | ) 104 | } 105 | 106 | fn big_div_up(self, rhs: T) -> Self { 107 | Self::new( 108 | #big_type::try_from(self.get()) 109 | .unwrap_or_else(|_| core::panic!("decimal: lhs value can't fit into `{}` type in {}::big_div_up()", #big_str, #name_str)) 110 | .checked_mul( 111 | T::one() 112 | ) 113 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_div_up()", #name_str)) 114 | .checked_add( 115 | rhs.get() 116 | .try_into() 117 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::big_div_up()", #big_str, #name_str)) 118 | .checked_sub(#big_type::from(1u128)).unwrap() 119 | ) 120 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_div_up()", #name_str)) 121 | .checked_div( 122 | rhs.get() 123 | .try_into().unwrap_or_else(|_| core::panic!("rhs value could not be converted to big type in `big_div_up`")), 124 | ) 125 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::big_div_up()", #name_str)) 126 | .try_into() 127 | .unwrap_or_else(|_| core::panic!("decimal: overflow casting result to `{}` type in method {}::big_div_up()", #underlying_str, #name_str)) 128 | ) 129 | } 130 | } 131 | 132 | #[cfg(test)] 133 | pub mod #module_name { 134 | use super::*; 135 | 136 | #[test] 137 | fn test_big_mul () { 138 | let a = #struct_name::new(2); 139 | let b = #struct_name::new(#struct_name::one()); 140 | assert_eq!(a.big_mul(b), #struct_name::new(2)); 141 | } 142 | 143 | #[test] 144 | fn test_big_mul_up () { 145 | let a = #struct_name::new(2); 146 | let b = #struct_name::new(#struct_name::one()); 147 | assert_eq!(a.big_mul_up(b), #struct_name::new(2)); 148 | } 149 | 150 | #[test] 151 | fn test_big_div () { 152 | let a = #struct_name::new(2); 153 | let b = #struct_name::new(#struct_name::one()); 154 | assert_eq!(a.big_div(b), #struct_name::new(2)); 155 | } 156 | 157 | #[test] 158 | fn test_checked_big_div () { 159 | let a = #struct_name::new(29); 160 | let b = #struct_name::new(#struct_name::one()); 161 | assert_eq!(a.big_div(b), #struct_name::new(29)); 162 | } 163 | 164 | #[test] 165 | fn test_big_div_up () { 166 | let a = #struct_name::new(2); 167 | let b = #struct_name::new(#struct_name::one()); 168 | assert_eq!(a.big_div_up(b), #struct_name::new(2)); 169 | } 170 | } 171 | )) 172 | } 173 | -------------------------------------------------------------------------------- /decimal_core/src/by_number.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use quote::quote; 3 | 4 | use crate::utils::string_to_ident; 5 | use crate::DecimalCharacteristics; 6 | 7 | pub fn generate_by_number(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 8 | let DecimalCharacteristics { 9 | struct_name, 10 | big_type, 11 | .. 12 | } = characteristics; 13 | 14 | let name_str = &struct_name.to_string(); 15 | 16 | let module_name = string_to_ident("tests_by_number_", &name_str); 17 | 18 | proc_macro::TokenStream::from(quote!( 19 | impl ByNumber<#big_type> for #struct_name { 20 | fn big_div_by_number(self, rhs: #big_type) -> Self { 21 | Self::new( 22 | #big_type::try_from(self.get()).unwrap() 23 | .checked_mul( 24 | Self::one() 25 | ).unwrap() 26 | .checked_div(rhs).unwrap() 27 | .try_into().unwrap() 28 | ) 29 | } 30 | 31 | fn checked_big_div_by_number(self, rhs: #big_type) -> core::result::Result { 32 | Ok(Self::new( 33 | #big_type::try_from(self.get()).map_err(|_| "checked_big_div_by_number: can't convert self to big_type")? 34 | .checked_mul(Self::checked_one()?).ok_or_else(|| "checked_big_div_by_number: (self * Self::one()) multiplication overflow")? 35 | .checked_div(rhs).ok_or_else(|| "checked_big_div_by_number: ((self * Self::one()) / rhs) division overflow")? 36 | .try_into().map_err(|_| "checked_big_div_by_number: can't convert to result")? 37 | )) 38 | } 39 | 40 | fn big_div_by_number_up(self, rhs: #big_type) -> Self { 41 | Self::new( 42 | #big_type::try_from(self.get()).unwrap() 43 | .checked_mul( 44 | Self::one() 45 | ).unwrap() 46 | .checked_add( 47 | rhs.checked_sub(#big_type::from(1u8)).unwrap() 48 | ).unwrap() 49 | .checked_div(rhs).unwrap() 50 | .try_into().unwrap() 51 | ) 52 | } 53 | 54 | fn checked_big_div_by_number_up(self, rhs: #big_type) -> core::result::Result { 55 | Ok(Self::new( 56 | #big_type::try_from(self.get()).map_err(|_| "checked_big_div_by_number_up: can't convert self to big_type")? 57 | .checked_mul(Self::checked_one()?).ok_or_else(|| "checked_big_div_by_number_up: (self * Self::one()) multiplication overflow")? 58 | .checked_add( 59 | rhs.checked_sub(#big_type::from(1u8)).ok_or_else(|| "checked_big_div_by_number_up: (rhs - 1) subtraction overflow")? 60 | ).ok_or_else(|| "checked_big_div_by_number_up: ((self * Self::one()) + (rhs - 1)) addition overflow")? 61 | .checked_div(rhs).ok_or_else(|| "checked_big_div_by_number_up: (((self * Self::one()) + (rhs - 1)) / rhs) division overflow")? 62 | .try_into().map_err(|_| "checked_big_div_by_number_up: can't convert to result")? 63 | )) 64 | } 65 | } 66 | 67 | impl ToValue for #struct_name 68 | where 69 | T::U: TryInto<#big_type>, 70 | { 71 | // mul(l/r) = U256::from(l) * U256::from(r) / U256::from(r::one()) 72 | fn big_mul_to_value(self, rhs: T) -> #big_type { 73 | #big_type::try_from(self.get()).unwrap() 74 | .checked_mul( 75 | rhs.get() 76 | .try_into().unwrap_or_else(|_| core::panic!("rhs value could not be converted to big type in `big_mul`")), 77 | ).unwrap() 78 | .checked_div( 79 | T::one() 80 | ).unwrap() 81 | } 82 | 83 | // mul_up(l/r) = U256::from(l) * U256::from(r) + U256::from(r::almost_one()) / U256::from(r::one()) 84 | fn big_mul_to_value_up(self, rhs: T) -> #big_type { 85 | #big_type::try_from(self.get()).unwrap() 86 | .checked_mul( 87 | rhs.get() 88 | .try_into().unwrap_or_else(|_| core::panic!("rhs value could not be converted to big type in `big_mul_up`")), 89 | ).unwrap() 90 | .checked_add(T::almost_one()).unwrap() 91 | .checked_div( 92 | T::one() 93 | ).unwrap() 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | pub mod #module_name { 99 | use super::*; 100 | 101 | #[test] 102 | fn test_big_div_up_by_number () { 103 | let a = #struct_name::new(2); 104 | let b: #big_type = #struct_name::one(); 105 | assert_eq!(a.big_div_by_number(b), #struct_name::new(2)); 106 | assert_eq!(a.big_div_by_number_up(b), #struct_name::new(2)); 107 | } 108 | 109 | #[test] 110 | fn test_checked_big_div_by_number() { 111 | let a = #struct_name::new(2); 112 | let b: #big_type = #struct_name::one(); 113 | assert_eq!(a.checked_big_div_by_number(b), Ok(#struct_name::new(2))); 114 | } 115 | 116 | #[test] 117 | fn checked_big_div_by_number_up() { 118 | let a = #struct_name::new(2); 119 | let b: #big_type = #struct_name::one(); 120 | assert_eq!(a.checked_big_div_by_number_up(b), Ok(#struct_name::new(2))); 121 | } 122 | 123 | #[test] 124 | fn test_big_mul_to_value () { 125 | let a = #struct_name::new(2); 126 | let b = #struct_name::from_integer(1); 127 | assert_eq!(a.big_mul_to_value(b), #big_type::from(a.get())); 128 | assert_eq!(a.big_mul_to_value_up(b), #big_type::from(a.get())); 129 | } 130 | } 131 | )) 132 | } 133 | -------------------------------------------------------------------------------- /decimal_core/src/checked_ops.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use quote::quote; 3 | 4 | use crate::utils::string_to_ident; 5 | use crate::DecimalCharacteristics; 6 | 7 | pub fn generate_checked_ops(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 8 | let DecimalCharacteristics { struct_name, .. } = characteristics; 9 | 10 | let name_str = &struct_name.to_string(); 11 | let module_name = string_to_ident("tests_checked_ops_", &name_str); 12 | 13 | proc_macro::TokenStream::from(quote!( 14 | impl CheckedOps for #struct_name { 15 | fn checked_add(self, rhs: Self) -> core::result::Result { 16 | Ok(Self::new( 17 | self.get().checked_add(rhs.get()) 18 | .ok_or_else(|| "checked_add: (self + rhs) additional overflow")? 19 | )) 20 | } 21 | 22 | fn checked_sub(self, rhs: Self) -> core::result::Result { 23 | Ok(Self::new( 24 | self.get().checked_sub(rhs.get()) 25 | .ok_or_else(|| "checked_sub: (self - rhs) subtraction underflow")? 26 | )) 27 | } 28 | 29 | fn checked_div(self, rhs: Self) -> core::result::Result { 30 | Ok(Self::new( 31 | self.get() 32 | .checked_mul(Self::one()) 33 | .ok_or_else(|| "checked_div: (self * Self::one()) multiplication overflow")? 34 | .checked_div(rhs.get()) 35 | .ok_or_else(|| "checked_div: ((self * Self::one()) / rhs) division by zero")? 36 | ) 37 | ) 38 | } 39 | } 40 | 41 | 42 | 43 | 44 | #[cfg(test)] 45 | pub mod #module_name { 46 | use super::*; 47 | 48 | #[test] 49 | fn test_checked_add() { 50 | let a = #struct_name::new(24); 51 | let b = #struct_name::new(11); 52 | 53 | assert_eq!(a.checked_add(b), Ok(#struct_name::new(35))); 54 | } 55 | 56 | #[test] 57 | fn test_overflow_checked_add() { 58 | let max = #struct_name::max_instance(); 59 | let result = max.checked_add(#struct_name::new(1)); 60 | 61 | assert_eq!(result, Err("checked_add: (self + rhs) additional overflow".to_string())); 62 | } 63 | 64 | #[test] 65 | fn test_checked_sub() { 66 | let a = #struct_name::new(35); 67 | let b = #struct_name::new(11); 68 | 69 | assert_eq!(a.checked_sub(b), Ok(#struct_name::new(24))); 70 | } 71 | 72 | #[test] 73 | fn test_checked_div() { 74 | let a = #struct_name::new(2); 75 | let b = #struct_name::new(#struct_name::one()); 76 | assert_eq!(a.checked_div(b), Ok(#struct_name::new(2))); 77 | } 78 | 79 | #[test] 80 | fn test_0_checked_div() { 81 | let a = #struct_name::new(47); 82 | let b = #struct_name::new(0); 83 | let result = a.checked_div(b); 84 | assert!(result.is_err()); 85 | } 86 | 87 | #[test] 88 | fn test_underflow_checked_sub() { 89 | let min = #struct_name::new(0); 90 | let result = min.checked_sub(#struct_name::new(1)); 91 | 92 | assert_eq!(result, Err("checked_sub: (self - rhs) subtraction underflow".to_string())); 93 | } 94 | } 95 | )) 96 | } 97 | -------------------------------------------------------------------------------- /decimal_core/src/factories.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use quote::quote; 3 | 4 | use crate::utils::string_to_ident; 5 | use crate::DecimalCharacteristics; 6 | 7 | pub fn generate_factories(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 8 | let DecimalCharacteristics { 9 | struct_name, 10 | underlying_type, 11 | scale, 12 | big_type, 13 | .. 14 | } = characteristics; 15 | 16 | let name_str = &struct_name.to_string(); 17 | let underlying_str = &underlying_type.to_string(); 18 | 19 | let module_name = string_to_ident("tests_factories_", &name_str); 20 | 21 | proc_macro::TokenStream::from(quote!( 22 | 23 | impl Factories for #struct_name 24 | where 25 | T: TryInto, 26 | T: TryFrom, 27 | T: TryInto<#underlying_type>, 28 | T: From, 29 | T: num_traits::ops::checked::CheckedDiv, 30 | T: num_traits::ops::checked::CheckedAdd, 31 | T: num_traits::ops::checked::CheckedSub 32 | { 33 | fn from_integer(integer: T) -> Self { 34 | Self::new({ 35 | let base: #underlying_type = integer.try_into() 36 | .unwrap_or_else(|_| core::panic!("decimal: integer value can't fit into `{}` type in {}::from_integer()", #underlying_str, #name_str)); 37 | base 38 | .checked_mul(Self::one()) 39 | .unwrap_or_else(|| core::panic!("decimal: overflow while adjusting scale in method {}::from_integer()", #name_str)) 40 | }) 41 | } 42 | 43 | fn from_scale(val: T, scale: u8) -> Self { 44 | Self::new( 45 | if #scale > scale { 46 | let base: #underlying_type = val.try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value")); 47 | let multiplier: u128 = 10u128.checked_pow((#scale - scale) as u32).unwrap(); 48 | base.checked_mul(multiplier.try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value"))).unwrap() 49 | } else { 50 | let denominator: u128 = 10u128.checked_pow((scale - #scale) as u32).unwrap(); 51 | val.checked_div( 52 | &denominator.try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value")) 53 | ).unwrap().try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value")) 54 | } 55 | ) 56 | } 57 | 58 | fn checked_from_scale(val: T, scale: u8) -> core::result::Result { 59 | Ok(Self::new( 60 | if #scale > scale { 61 | let base: #underlying_type = val.try_into().map_err(|_| "checked_from_scale: can't convert to base")?; 62 | let multiplier: u128 = 10u128.checked_pow((#scale - scale) as u32).ok_or_else(|| "checked_from_scale: multiplier overflow")?; 63 | base.checked_mul(multiplier.try_into().map_err(|_| "checked_from_scale: can't convert to multiplier")?).ok_or_else(|| "checked_from_scale: (multiplier * base) overflow")? 64 | } else { 65 | let denominator: u128 = 10u128.checked_pow((scale - #scale) as u32).ok_or_else(|| "checked_from_scale: denominator overflow")?; 66 | val.checked_div( 67 | &denominator.try_into().map_err(|_| "checked_from_scale: can't convert to denominator")? 68 | ).ok_or_else(|| "checked_from_scale: (base / denominator) overflow")? 69 | .try_into().map_err(|_| "checked_from_scale: can't convert to result")? 70 | } 71 | )) 72 | } 73 | 74 | fn from_scale_up(val: T, scale: u8) -> Self { 75 | Self::new( 76 | if #scale > scale { 77 | let base: #underlying_type = val.try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value")); 78 | let multiplier: u128 = 10u128.checked_pow((#scale - scale) as u32).unwrap(); 79 | base.checked_mul(multiplier.try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value"))).unwrap() 80 | } else { 81 | let multiplier: u128 = 10u128.checked_pow((scale - #scale) as u32).unwrap(); 82 | let denominator: T = multiplier.try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value")); 83 | val 84 | .checked_add( 85 | &denominator.checked_sub(&T::from(1u8)).unwrap() 86 | ).unwrap() 87 | .checked_div( 88 | &denominator 89 | ).unwrap() 90 | .try_into().unwrap_or_else(|_| core::panic!("decimal: can't convert value")) 91 | } 92 | ) 93 | } 94 | } 95 | 96 | impl BetweenDecimals for #struct_name 97 | where 98 | Self: Factories, 99 | { 100 | fn from_decimal(other: T) -> Self { 101 | Self::from_scale(other.get(), T::scale()) 102 | } 103 | 104 | fn checked_from_decimal(other: T) -> core::result::Result { 105 | Self::checked_from_scale(other.get(), T::scale()) 106 | } 107 | 108 | fn from_decimal_up(other: T) -> Self { 109 | Self::from_scale_up(other.get(), T::scale()) 110 | } 111 | } 112 | 113 | impl FactoriesToValue for #struct_name 114 | where 115 | T: TryInto<#underlying_type>, 116 | { 117 | 118 | fn checked_from_scale_to_value(val: T, scale: u8) -> core::result::Result<#big_type, alloc::string::String> { 119 | Ok( 120 | if #scale > scale { 121 | let base: #big_type = #big_type::try_from( 122 | val.try_into().map_err(|_| "checked_from_scale_to_value: can't convert val to base")?) 123 | .map_err(|_| "checked_from_scale_to_value: can't convert val to big_type" 124 | )?; 125 | // no possibility of overflow because of scale limit 126 | let multiplier: u128 = 10u128.checked_pow((#scale - scale) as u32).ok_or_else(|| "checked_from_scale_to_value: multiplier overflow")?; 127 | 128 | base.checked_mul(multiplier.try_into().map_err(|_| "checked_from_scale_to_value: can't convert multiplier to big_type")?) 129 | .ok_or_else(|| "checked_from_scale_to_value: (multiplier * base) overflow")? 130 | } else { 131 | // no possibility of overflow because of scale limit 132 | let denominator: u128 = 10u128.checked_pow((scale - #scale) as u32).ok_or_else(|| "checked_from_scale_to_value: denominator overflow")?; 133 | let base: #big_type = #big_type::try_from( 134 | val.try_into().map_err(|_| "checked_from_scale_to_value: can't convert val to base")?) 135 | .map_err(|_| "checked_from_scale_to_value: can't convert val to big_type" 136 | )?; 137 | 138 | base.checked_div( 139 | denominator.try_into().map_err(|_| "checked_from_scale_to_value: can't convert denominator to big_type")? 140 | ).ok_or_else(|| "checked_from_scale_to_value: (base / denominator) overflow")? 141 | .try_into().map_err(|_| "checked_from_scale_to_value: can't convert to result")? 142 | }) 143 | } 144 | } 145 | 146 | impl BetweenDecimalsToValue for #struct_name 147 | where 148 | Self: FactoriesToValue, 149 | { 150 | fn checked_from_decimal_to_value(other: T) -> core::result::Result<#big_type, alloc::string::String> { 151 | Self::checked_from_scale_to_value(other.get(), T::scale()) 152 | } 153 | } 154 | 155 | 156 | #[cfg(test)] 157 | pub mod #module_name { 158 | use super::*; 159 | 160 | #[test] 161 | fn test_from_integer() { 162 | assert_eq!( 163 | #struct_name::from_integer(0), 164 | #struct_name::new(0) 165 | ); 166 | } 167 | 168 | #[test] 169 | fn test_from_scale() { 170 | assert_eq!( 171 | #struct_name::from_scale(0, 0), 172 | #struct_name::new(0) 173 | ); 174 | assert_eq!( 175 | #struct_name::from_scale_up(0, 0), 176 | #struct_name::new(0) 177 | ); 178 | 179 | assert_eq!( 180 | #struct_name::from_scale(0, 3), 181 | #struct_name::new(0) 182 | ); 183 | assert_eq!( 184 | #struct_name::from_scale_up(0, 3), 185 | #struct_name::new(0) 186 | ); 187 | 188 | assert_eq!( 189 | #struct_name::from_scale(42, #scale), 190 | #struct_name::new(42) 191 | ); 192 | assert_eq!( 193 | #struct_name::from_scale_up(42, #scale), 194 | #struct_name::new(42) 195 | ); 196 | 197 | assert_eq!( 198 | #struct_name::from_scale(42, #scale + 1), 199 | #struct_name::new(4) 200 | ); 201 | assert_eq!( 202 | #struct_name::from_scale_up(42, #scale + 1), 203 | #struct_name::new(5) 204 | ); 205 | 206 | } 207 | 208 | #[test] 209 | fn test_checked_from_scale() { 210 | assert_eq!( 211 | #struct_name::checked_from_scale(0, 0).unwrap(), 212 | #struct_name::new(0) 213 | ); 214 | 215 | assert_eq!( 216 | #struct_name::checked_from_scale(0, 3).unwrap(), 217 | #struct_name::new(0) 218 | ); 219 | 220 | assert_eq!( 221 | #struct_name::checked_from_scale(42, #scale).unwrap(), 222 | #struct_name::new(42) 223 | ); 224 | 225 | assert_eq!( 226 | #struct_name::checked_from_scale(42, #scale + 1).unwrap(), 227 | #struct_name::new(4) 228 | ); 229 | 230 | let max_val = #struct_name::max_value(); 231 | assert_eq!( 232 | #struct_name::checked_from_scale(max_val, 100_000).is_err(), 233 | true 234 | ); 235 | } 236 | 237 | #[test] 238 | fn test_checked_from_scale_to_value() { 239 | let result: i32 = #struct_name::checked_from_scale_to_value(0, 0).unwrap().try_into().unwrap(); 240 | assert_eq!(result, 0); 241 | 242 | let result: i32 = #struct_name::checked_from_scale_to_value(0, 3).unwrap().try_into().unwrap(); 243 | assert_eq!(result, 0); 244 | 245 | let result: i32 = #struct_name::checked_from_scale_to_value(42, #scale).unwrap().try_into().unwrap(); 246 | assert_eq!(result, 42); 247 | 248 | let result: i32 = #struct_name::checked_from_scale_to_value(42, #scale + 1).unwrap().try_into().unwrap(); 249 | assert_eq!(result, 4); 250 | 251 | let max_val = #struct_name::max_value(); 252 | assert_eq!( 253 | #struct_name::checked_from_scale_to_value(max_val, 100_000).is_err(), 254 | true 255 | ); 256 | 257 | let result: i32 = #struct_name::checked_from_scale_to_value(1, 38).unwrap().try_into().unwrap(); 258 | assert_eq!(result, 0); 259 | } 260 | 261 | #[test] 262 | fn test_checked_from_decimal_to_value() { 263 | let result: i32 = #struct_name::checked_from_decimal_to_value(#struct_name::new(1)).unwrap().try_into().unwrap(); 264 | assert_eq!(result, 1); 265 | 266 | let result: i32 = #struct_name::checked_from_decimal_to_value(#struct_name::new(42)).unwrap().try_into().unwrap(); 267 | assert_eq!(result, 42); 268 | } 269 | } 270 | )) 271 | } 272 | -------------------------------------------------------------------------------- /decimal_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | use alloc::{string::ToString, vec::Vec}; 5 | use quote::{quote, ToTokens}; 6 | use syn::parse_macro_input; 7 | 8 | mod base; 9 | mod big_ops; 10 | mod by_number; 11 | mod checked_ops; 12 | mod factories; 13 | mod ops; 14 | mod others; 15 | mod structs; 16 | mod utils; 17 | 18 | use structs::DecimalCharacteristics; 19 | 20 | use crate::utils::string_to_ident; 21 | 22 | #[proc_macro_attribute] 23 | pub fn decimal( 24 | attr: proc_macro::TokenStream, 25 | item: proc_macro::TokenStream, 26 | ) -> proc_macro::TokenStream { 27 | let args_str = attr.to_string(); 28 | let args: Vec<&str> = args_str.split(',').collect(); 29 | 30 | let parsed_scale = match args[0].parse::() { 31 | Ok(scale) => scale, 32 | Err(_) => 0, 33 | }; 34 | 35 | let big_type = match args.len() { 36 | 1 => string_to_ident("", "U256"), 37 | 2 => string_to_ident("", args[1].trim()), 38 | _ => panic!("decimal: invalid number of parameters"), 39 | }; 40 | 41 | assert!(parsed_scale <= 38, "scale too big"); 42 | 43 | let k = item.clone(); 44 | let decimal_struct = parse_macro_input!(k as syn::ItemStruct); 45 | 46 | let fields = decimal_struct.fields; 47 | let first_field = fields.iter().next().unwrap(); 48 | 49 | let underlying_type = 50 | string_to_ident("", first_field.ty.to_token_stream().to_string().as_str()); 51 | 52 | let field_name = match first_field.ident.clone() { 53 | Some(ident) => quote! {#ident}, 54 | None => quote! {0}, 55 | }; 56 | 57 | let struct_name = decimal_struct.ident; 58 | 59 | let characteristics = DecimalCharacteristics { 60 | struct_name: struct_name.clone(), 61 | field_name: field_name.clone(), 62 | underlying_type: underlying_type.clone(), 63 | big_type: big_type.clone(), 64 | scale: parsed_scale, 65 | }; 66 | 67 | let mut result = proc_macro::TokenStream::from(quote! { 68 | // #[derive(Default, std::fmt::Debug, Clone, Copy, PartialEq, )] 69 | }); 70 | 71 | result.extend(item.clone()); 72 | 73 | result.extend(base::generate_base(characteristics.clone())); 74 | result.extend(ops::generate_ops(characteristics.clone())); 75 | result.extend(big_ops::generate_big_ops(characteristics.clone())); 76 | result.extend(by_number::generate_by_number(characteristics.clone())); 77 | result.extend(others::generate_others(characteristics.clone())); 78 | result.extend(factories::generate_factories(characteristics.clone())); 79 | result.extend(checked_ops::generate_checked_ops(characteristics.clone())); 80 | 81 | result.extend(proc_macro::TokenStream::from(quote! { 82 | impl #struct_name { 83 | pub fn is_zero(self) -> bool { 84 | self.#field_name == #underlying_type::try_from(0).unwrap() 85 | } 86 | } 87 | })); 88 | 89 | result 90 | } 91 | -------------------------------------------------------------------------------- /decimal_core/src/ops.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use quote::quote; 3 | 4 | use crate::utils::string_to_ident; 5 | use crate::DecimalCharacteristics; 6 | 7 | pub fn generate_ops(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 8 | let DecimalCharacteristics { 9 | struct_name, 10 | underlying_type, 11 | .. 12 | } = characteristics; 13 | 14 | let name_str = &struct_name.to_string(); 15 | let underlying_str = &underlying_type.to_string(); 16 | 17 | let module_name = string_to_ident("tests_", &name_str); 18 | 19 | proc_macro::TokenStream::from(quote!( 20 | impl core::ops::Add for #struct_name { 21 | type Output = Self; 22 | fn add(self, rhs: Self) -> Self { 23 | Self::new(self.get() 24 | .checked_add(rhs.get()) 25 | .unwrap_or_else(|| panic!("decimal: overflow in method {}::add()", #name_str)) 26 | ) 27 | } 28 | } 29 | 30 | impl core::ops::Sub for #struct_name { 31 | type Output = #struct_name; 32 | 33 | fn sub(self, rhs: Self) -> #struct_name { 34 | Self::new(self.get() 35 | .checked_sub(rhs.get()) 36 | .unwrap_or_else(|| panic!("decimal: overflow in method {}::sub()", #name_str)) 37 | ) 38 | } 39 | } 40 | 41 | impl core::ops::Mul for #struct_name 42 | where 43 | T::U: TryInto<#underlying_type>, 44 | { 45 | type Output = #struct_name; 46 | 47 | fn mul(self, rhs: T) -> Self { 48 | Self::new( 49 | self.get() 50 | .checked_mul( 51 | rhs.get() 52 | .try_into() 53 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::mul()", #underlying_str, #name_str)) 54 | ) 55 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::mul()", #name_str)) 56 | .checked_div(T::one()) 57 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::mul()", #name_str)) 58 | ) 59 | } 60 | } 61 | 62 | impl core::ops::Div for #struct_name 63 | where 64 | T::U: TryInto<#underlying_type>, 65 | { 66 | type Output = Self; 67 | 68 | fn div(self, rhs: T) -> Self { 69 | Self::new( 70 | self.get() 71 | .checked_mul(T::one()) 72 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::div()", #name_str)) 73 | .checked_div( 74 | rhs.get() 75 | .try_into() 76 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::div()", #underlying_str, #name_str)) 77 | ) 78 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::div()", #name_str)) 79 | ) 80 | } 81 | } 82 | 83 | impl core::ops::AddAssign for #struct_name { 84 | fn add_assign(&mut self, rhs: Self) { 85 | *self = *self + rhs 86 | } 87 | } 88 | 89 | impl core::ops::SubAssign for #struct_name { 90 | fn sub_assign(&mut self, rhs: Self) { 91 | *self = *self - rhs 92 | } 93 | } 94 | 95 | impl core::ops::MulAssign for #struct_name { 96 | fn mul_assign(&mut self, rhs: Self) { 97 | *self = *self * rhs 98 | } 99 | } 100 | 101 | impl core::ops::DivAssign for #struct_name { 102 | fn div_assign(&mut self, rhs: Self) { 103 | *self = *self / rhs 104 | } 105 | } 106 | 107 | 108 | #[cfg(test)] 109 | pub mod #module_name { 110 | use super::*; 111 | 112 | #[test] 113 | fn test_add () { 114 | let mut a = #struct_name::new(1); 115 | let b = #struct_name::new(1); 116 | assert_eq!(a + b, #struct_name::new(2)); 117 | a += b; 118 | assert_eq!(a, #struct_name::new(2)); 119 | } 120 | 121 | #[test] 122 | fn test_sub () { 123 | let mut a = #struct_name::new(1); 124 | let b = #struct_name::new(1); 125 | assert_eq!(a - b, #struct_name::new(0)); 126 | a -= b; 127 | assert_eq!(a, #struct_name::new(0)); 128 | } 129 | 130 | #[test] 131 | fn test_mul () { 132 | let mut a = #struct_name::new(2); 133 | let b = #struct_name::new(#struct_name::one()); 134 | assert_eq!(a * b, #struct_name::new(2)); 135 | a *= b; 136 | assert_eq!(a, #struct_name::new(2)); 137 | } 138 | 139 | #[test] 140 | fn test_div () { 141 | let mut a = #struct_name::new(2); 142 | let b = #struct_name::new(#struct_name::one()); 143 | assert_eq!(a / b, #struct_name::new(2)); 144 | a /= b; 145 | assert_eq!(a, #struct_name::new(2)); 146 | } 147 | } 148 | )) 149 | } 150 | -------------------------------------------------------------------------------- /decimal_core/src/others.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use quote::quote; 3 | 4 | use crate::utils::string_to_ident; 5 | use crate::DecimalCharacteristics; 6 | 7 | pub fn generate_others(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream { 8 | let DecimalCharacteristics { 9 | struct_name, 10 | underlying_type, 11 | .. 12 | } = characteristics; 13 | 14 | let name_str = &struct_name.to_string(); 15 | let underlying_str = &underlying_type.to_string(); 16 | 17 | let module_name = string_to_ident("tests_others_", &name_str); 18 | 19 | proc_macro::TokenStream::from(quote!( 20 | impl Others for #struct_name 21 | where 22 | T::U: TryInto<#underlying_type>, 23 | { 24 | // r::almost_one() = r::one() - 1 25 | // mul_up(l, r) = l * r + r::almost_one() / r::one(); 26 | fn mul_up(self, rhs: T) -> Self { 27 | Self::new( 28 | self.get() 29 | .checked_mul( 30 | rhs.get() 31 | .try_into() 32 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::mul_up()", #underlying_str, #name_str)) 33 | ) 34 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::mul_up()", #name_str)) 35 | .checked_add(T::almost_one()) 36 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::mul_up()", #name_str)) 37 | .checked_div(T::one()) 38 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::mul_up()", #name_str)) 39 | ) 40 | } 41 | 42 | // div_up(n/d) = n * d::one() + d - 1 / d; 43 | fn div_up(self, rhs: T) -> Self { 44 | Self::new( 45 | self.get() 46 | .checked_mul(T::one()) 47 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::div_up()", #name_str)) 48 | .checked_add( 49 | rhs.get() 50 | .try_into() 51 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::div_up()", #underlying_str, #name_str)) 52 | .checked_sub(#underlying_type::try_from(1u128).unwrap()) 53 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::div_up()", #name_str)) 54 | ) 55 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::div_up()", #name_str)) 56 | .checked_div( 57 | rhs.get() 58 | .try_into() 59 | .unwrap_or_else(|_| core::panic!("decimal: rhs value can't fit into `{}` type in {}::div_up()", #underlying_str, #name_str)) 60 | ) 61 | .unwrap_or_else(|| core::panic!("decimal: overflow in method {}::div_up()", #name_str)) 62 | ) 63 | } 64 | } 65 | 66 | impl OthersSameType for #struct_name { 67 | fn sub_abs(self, rhs: Self) -> Self { 68 | if self.get() > rhs.get() { 69 | self - rhs 70 | } else { 71 | rhs - self 72 | } 73 | } 74 | } 75 | 76 | impl core::fmt::Display for #struct_name { 77 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 78 | if Self::scale() > 0 { 79 | let mut decimal_places = self.get().checked_rem(Self::one()).unwrap(); 80 | let mut non_zero_tail = 0; 81 | 82 | while decimal_places > 0 { 83 | non_zero_tail += 1; 84 | decimal_places /= 10; 85 | } 86 | 87 | write!( 88 | f, 89 | "{}.{}{}", 90 | self.get().checked_div(Self::one()).unwrap(), 91 | "0".repeat((Self::scale() - non_zero_tail).into()), 92 | self.get().checked_rem(Self::one()).unwrap() 93 | ) 94 | } else { 95 | write!(f, "{}", self.get()) 96 | } 97 | } 98 | } 99 | 100 | 101 | #[cfg(test)] 102 | pub mod #module_name { 103 | use super::*; 104 | 105 | #[test] 106 | fn test_mul_up() { 107 | let a = #struct_name::new(1); 108 | let b = #struct_name::new(#struct_name::one()); 109 | assert_eq!(a.mul_up(b), a); 110 | } 111 | 112 | #[test] 113 | fn test_div_up() { 114 | let a = #struct_name::new(1); 115 | let b = #struct_name::new(#struct_name::one()); 116 | assert_eq!(a.div_up(b), a); 117 | } 118 | 119 | #[test] 120 | fn test_sub_abs() { 121 | let a = #struct_name::new(1); 122 | let b = #struct_name::new(2); 123 | assert_eq!(a.sub_abs(b), a); 124 | assert_eq!(b.sub_abs(a), a); 125 | } 126 | } 127 | )) 128 | } 129 | -------------------------------------------------------------------------------- /decimal_core/src/structs.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::Ident; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct DecimalCharacteristics { 6 | pub struct_name: Ident, 7 | pub field_name: TokenStream, // cannot be Ident because of tuple structs 8 | pub underlying_type: Ident, 9 | pub big_type: Ident, 10 | pub scale: u8, 11 | } 12 | -------------------------------------------------------------------------------- /decimal_core/src/utils.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use proc_macro2::Span; 3 | use syn::Ident; 4 | 5 | pub fn string_to_ident(prefix: &str, name: &str) -> Ident { 6 | let mut denominator_const_name = String::from(prefix); 7 | denominator_const_name.push_str(name); 8 | Ident::new(denominator_const_name.as_str(), Span::call_site()) 9 | } 10 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "decimal", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | 5 | mod traits; 6 | mod uint; 7 | pub use crate::uint::{checked_u320_to_u256, to_u256, u256_to_u320, U256, U320}; 8 | pub use num_traits; 9 | pub use safe_decimal_core::decimal; 10 | 11 | pub use traits::*; 12 | 13 | #[cfg(test)] 14 | pub mod tests { 15 | use super::*; 16 | use crate::alloc::string::ToString; 17 | 18 | #[cfg(test)] 19 | #[decimal(3, u128)] 20 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 21 | struct R(u32); 22 | 23 | #[cfg(test)] 24 | #[decimal(1)] 25 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 26 | struct Q { 27 | v: u16, 28 | } 29 | 30 | #[cfg(test)] 31 | #[decimal(0)] 32 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 33 | struct N(u8); 34 | 35 | #[test] 36 | fn test_from_decimal() { 37 | let r = R(42); 38 | let q = Q { v: 144 }; 39 | let n = N(3); 40 | 41 | assert_eq!(R::from_decimal(r), r); 42 | assert_eq!(R::from_decimal(q), R(14400)); 43 | assert_eq!(R::from_decimal(n), R(3000)); 44 | 45 | assert_eq!(Q::from_decimal(r), Q { v: 0 }); 46 | assert_eq!(Q::from_decimal(q), q); 47 | assert_eq!(Q::from_decimal(n), Q { v: 30 }); 48 | 49 | assert_eq!(N::from_decimal(n), n); 50 | assert_eq!(N::from_decimal(q), N(14)); 51 | } 52 | 53 | #[test] 54 | fn test_from_decimal_up() { 55 | let r = R(42); 56 | let q = Q { v: 144 }; 57 | let n = N(3); 58 | 59 | assert_eq!(R::from_decimal_up(r), r); 60 | assert_eq!(R::from_decimal_up(q), R(14400)); 61 | assert_eq!(R::from_decimal_up(n), R(3000)); 62 | 63 | assert_eq!(Q::from_decimal_up(r), Q { v: 1 }); 64 | assert_eq!(Q::from_decimal_up(q), q); 65 | assert_eq!(Q::from_decimal_up(n), Q { v: 30 }); 66 | 67 | assert_eq!(N::from_decimal_up(n), n); 68 | assert_eq!(N::from_decimal_up(q), N(15)); 69 | } 70 | 71 | #[test] 72 | fn test_ops() { 73 | assert_eq!(N(0) + N(0), N::new(0)); 74 | assert_eq!(N(1) + N(2), N::new(3)); 75 | assert_eq!(R(0) + R(0), R::new(0)); 76 | assert_eq!(R(1) + R(2), R::new(3)); 77 | 78 | assert_eq!(N(0) - N(0), N::new(0)); 79 | assert_eq!(N(2) - N(1), N::new(1)); 80 | assert_eq!(R(0) - R(0), R::new(0)); 81 | assert_eq!(R(2) - R(1), R::new(1)); 82 | 83 | assert_eq!(N(0) * N(0), N::new(0)); 84 | assert_eq!(N(2) * N::from_integer(1), N::new(2)); 85 | assert_eq!(R(0) * Q::new(0), R::new(0)); 86 | assert_eq!(R(2) * Q::from_integer(1), R::new(2)); 87 | 88 | assert_eq!(N(0) / N(1), N::new(0)); 89 | assert_eq!(N(4) / N::from_integer(2), N::new(2)); 90 | assert_eq!(R(0) / Q::new(1), R::new(0)); 91 | assert_eq!(R(4) / Q::from_integer(2), R::new(2)); 92 | } 93 | 94 | #[test] 95 | fn test_big_mul() { 96 | // precision 97 | { 98 | let a = Q::from_integer(1); 99 | let b = R::from_integer(1); 100 | let d = a.big_mul(b); 101 | let u = a.big_mul_up(b); 102 | assert_eq!(d, Q::from_integer(1)); 103 | assert_eq!(u, Q::from_integer(1)); 104 | } 105 | // simple 106 | { 107 | let a = Q::from_integer(2); 108 | let b = R::from_integer(3); 109 | let d = a.big_mul(b); 110 | let u = a.big_mul_up(b); 111 | assert_eq!(d, Q::from_integer(6)); 112 | assert_eq!(u, Q::from_integer(6)); 113 | } 114 | // big 115 | { 116 | let a = Q::new(2u16.pow(15)); 117 | let b = N::from_integer(1); 118 | let d = a.big_mul(b); 119 | let u = a.big_mul_up(b); 120 | 121 | let expected = Q::new(2u16.pow(15)); 122 | assert_eq!(d, expected); 123 | assert_eq!(u, expected); 124 | } 125 | // random 126 | { 127 | let a = R::new(879132); 128 | let b = Q::new(9383); 129 | let d = a.big_mul(b); 130 | let u = a.big_mul_up(b); 131 | 132 | let expected = R(824889555); 133 | assert_eq!(d, expected); 134 | assert_eq!(u, expected + R(1)); 135 | } 136 | } 137 | 138 | #[test] 139 | fn test_big_div() { 140 | // precision 141 | { 142 | let a = Q::from_integer(1); 143 | let b = R::from_integer(1); 144 | let d = a.big_div(b); 145 | let u = a.big_div_up(b); 146 | assert_eq!(d, Q::from_integer(1)); 147 | assert_eq!(u, Q::from_integer(1)); 148 | } 149 | // simple 150 | { 151 | let a = Q::from_integer(6); 152 | let b = R::from_integer(3); 153 | let d = a.big_div(b); 154 | let u = a.big_div_up(b); 155 | assert_eq!(d, Q::from_integer(2)); 156 | assert_eq!(u, Q::from_integer(2)); 157 | } 158 | // big 159 | { 160 | let a = Q::new(2u16.pow(15)); 161 | let b = R::from_integer(1); 162 | let d = a.big_div(b); 163 | let u = a.big_div_up(b); 164 | 165 | let expected = Q::new(2u16.pow(15)); 166 | assert_eq!(d, expected); 167 | assert_eq!(u, expected); 168 | } 169 | // random 170 | { 171 | let a = R::new(824889555); 172 | let b = Q::new(9383); 173 | let d = a.big_div(b); 174 | let u = a.big_div_up(b); 175 | 176 | let expected = R(879131); 177 | assert_eq!(d, expected); 178 | assert_eq!(u, expected + R(1)); 179 | } 180 | } 181 | 182 | #[test] 183 | fn tests_mul_to_number() { 184 | // basic 185 | { 186 | let a = Q::from_integer(1u8); 187 | let b = Q::from_integer(2u8); 188 | assert_eq!(a.big_mul_to_value(b), b.here()); 189 | assert_eq!(a.big_mul_to_value_up(b), b.here()); 190 | } 191 | // overflowing 192 | { 193 | let a = Q::new(u16::MAX); 194 | let b = Q::new(u16::MAX); 195 | // real 4.294836225 × 10^8 196 | // expected 429483622 197 | assert_eq!(a.big_mul_to_value(b), U256::from(429483622u64)); 198 | assert_eq!(a.big_mul_to_value_up(b), U256::from(429483623u64)); 199 | } 200 | } 201 | 202 | #[test] 203 | fn test_big_div_by_number() { 204 | // basic 205 | { 206 | let a = Q::from_integer(4u8); 207 | let b = Q::from_integer(2u8); 208 | let big_type = U256::from(b.get()); 209 | assert_eq!(a.big_div_by_number(big_type), b); 210 | assert_eq!(a.big_div_by_number_up(big_type), b); 211 | } 212 | // huge 213 | { 214 | let a = Q::new(u16::MAX); 215 | let b = U256::from(u16::MAX as u64 * 10 + 1); 216 | assert_eq!(a.big_div_by_number(b), Q::new(0)); 217 | assert_eq!(a.big_div_by_number_up(b), Q::new(1)); 218 | } 219 | // random 220 | { 221 | let a = Q::new(63424); 222 | let b = U256::from(157209); 223 | // real 0.403437462.. 224 | // expected 4 225 | assert_eq!(a.big_div_by_number(b), Q::new(4)); 226 | assert_eq!(a.big_div_by_number_up(b), Q::new(5)); 227 | } 228 | } 229 | 230 | #[test] 231 | fn test_mul_up() { 232 | // mul of little 233 | { 234 | let a = Q::new(1); 235 | let b = Q::new(1); 236 | assert_eq!(a.mul_up(b), Q::new(1)); 237 | } 238 | // mul calculable without precision loss 239 | { 240 | let a = Q::from_integer(1); 241 | let b = Q::from_integer(3) / Q::new(10); 242 | assert_eq!(a.mul_up(b), b); 243 | } 244 | { 245 | let a = N(1); 246 | let b = Q::from_integer(1); 247 | assert_eq!(a.mul_up(b), N(1)); 248 | } 249 | { 250 | let a = N(3); 251 | let b = Q::from_integer(3) / Q::from_integer(10); 252 | assert_eq!(a.mul_up(b), N(1)); 253 | } 254 | } 255 | 256 | #[test] 257 | fn test_div_up() { 258 | // div of zero 259 | { 260 | let a = Q::new(0); 261 | let b = Q::new(1); 262 | assert_eq!(a.div_up(b), Q::new(0)); 263 | } 264 | // div check rounding up 265 | { 266 | let a = Q::new(1); 267 | let b = Q::from_integer(2); 268 | assert_eq!(a.div_up(b), Q::new(1)); 269 | } 270 | // div big number 271 | { 272 | let a = R::new(201); 273 | let b = R::from_integer(2); 274 | assert_eq!(a.div_up(b), R::new(101)); 275 | } 276 | { 277 | let a = Q::new(42); 278 | let b = R::from_integer(10); 279 | assert_eq!(a.div_up(b), Q::new(5)); 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Debug, Display}; 2 | 3 | use alloc::string::String; 4 | 5 | pub trait Decimal { 6 | type U: Debug + Default; 7 | 8 | fn get(&self) -> Self::U; 9 | fn new(value: Self::U) -> Self; 10 | fn max_instance() -> Self; 11 | fn max_value() -> Self::U; 12 | fn here>(&self) -> Y; 13 | fn scale() -> u8; 14 | fn one>() -> T; 15 | fn checked_one>() -> Result 16 | where 17 | T::Error: Display; 18 | fn almost_one>() -> T; 19 | } 20 | 21 | pub trait BigOps: Sized { 22 | fn big_mul(self, rhs: T) -> Self; 23 | fn big_mul_up(self, rhs: T) -> Self; 24 | fn big_div(self, rhs: T) -> Self; 25 | fn checked_big_div(self, rhs: T) -> Result; 26 | fn big_div_up(self, rhs: T) -> Self; 27 | } 28 | 29 | pub trait Others { 30 | fn mul_up(self, rhs: T) -> Self; 31 | fn div_up(self, rhs: T) -> Self; 32 | } 33 | 34 | pub trait OthersSameType { 35 | fn sub_abs(self, rhs: Self) -> Self; 36 | } 37 | 38 | pub trait Factories: Sized { 39 | fn from_integer(integer: T) -> Self; 40 | fn from_scale(integer: T, scale: u8) -> Self; 41 | fn checked_from_scale(integer: T, scale: u8) -> Result; 42 | fn from_scale_up(integer: T, scale: u8) -> Self; 43 | } 44 | 45 | pub trait BetweenDecimals: Sized { 46 | fn from_decimal(other: T) -> Self; 47 | fn checked_from_decimal(other: T) -> Result; 48 | fn from_decimal_up(other: T) -> Self; 49 | } 50 | 51 | pub trait ToValue { 52 | fn big_mul_to_value(self, value: T) -> B; 53 | fn big_mul_to_value_up(self, value: T) -> B; 54 | } 55 | 56 | pub trait FactoriesToValue { 57 | fn checked_from_scale_to_value(integer: T, scale: u8) -> Result; 58 | } 59 | 60 | pub trait BetweenDecimalsToValue { 61 | fn checked_from_decimal_to_value(other: T) -> Result; 62 | } 63 | 64 | pub trait ByNumber: Sized { 65 | fn big_div_by_number(self, number: B) -> Self; 66 | fn big_div_by_number_up(self, number: B) -> Self; 67 | fn checked_big_div_by_number(self, number: B) -> Result; 68 | fn checked_big_div_by_number_up(self, number: B) -> Result; 69 | } 70 | 71 | pub trait CheckedOps: Sized { 72 | fn checked_add(self, rhs: Self) -> Result; 73 | fn checked_sub(self, rhs: Self) -> Result; 74 | fn checked_div(self, rhs: Self) -> Result; 75 | } 76 | -------------------------------------------------------------------------------- /src/uint.rs: -------------------------------------------------------------------------------- 1 | //! Large uint types 2 | 3 | // required for clippy 4 | #![allow(clippy::assign_op_pattern)] 5 | #![allow(clippy::ptr_offset_with_cast)] 6 | #![allow(clippy::manual_range_contains)] 7 | 8 | use uint::construct_uint; 9 | 10 | construct_uint! { 11 | pub struct U320(5); 12 | } 13 | construct_uint! { 14 | pub struct U256(4); 15 | } 16 | construct_uint! { 17 | pub struct U192(3); 18 | } 19 | 20 | #[allow(dead_code)] 21 | pub fn checked_u320_to_u256(n: U320) -> Option { 22 | if !(n >> 256).is_zero() { 23 | return None; 24 | } 25 | 26 | Some(U256([ 27 | n.low_u64(), 28 | (n >> 64).low_u64(), 29 | (n >> 128).low_u64(), 30 | (n >> 192).low_u64(), 31 | ])) 32 | } 33 | 34 | #[allow(dead_code)] 35 | pub fn u320_to_u256(n: U320) -> U256 { 36 | checked_u320_to_u256(n).unwrap() 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub const fn to_u256(n: u128) -> U256 { 41 | U256([n as u64, (n >> 64) as u64, 0, 0]) 42 | } 43 | 44 | #[allow(dead_code)] 45 | pub fn u256_to_u320(n: U256) -> U320 { 46 | U320([ 47 | n.low_u64(), 48 | (n >> 64).low_u64(), 49 | (n >> 128).low_u64(), 50 | (n >> 192).low_u64(), 51 | 0, 52 | ]) 53 | } 54 | 55 | #[allow(dead_code)] 56 | pub fn to_u320(n: u128) -> U320 { 57 | u256_to_u320(to_u256(n)) 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn test_to_u256() { 66 | { 67 | let from = 0; 68 | let result = to_u256(from); 69 | let back = result.as_u128(); 70 | assert_eq!(from, back); 71 | } 72 | { 73 | let from = 1; 74 | let result = to_u256(from); 75 | let back = result.as_u128(); 76 | assert_eq!(from, back); 77 | } 78 | { 79 | let from = 1324342342433342342; 80 | let result = to_u256(from); 81 | let back = result.as_u128(); 82 | assert_eq!(from, back); 83 | } 84 | { 85 | let from = u64::MAX as u128; 86 | let result = to_u256(from); 87 | let back = result.as_u128(); 88 | assert_eq!(from, back); 89 | } 90 | { 91 | let from = u64::MAX as u128 + 1; 92 | let result = to_u256(from); 93 | let back = result.as_u128(); 94 | assert_eq!(from, back); 95 | } 96 | { 97 | let from = u64::MAX as u128 + 2; 98 | let result = to_u256(from); 99 | let back = result.as_u128(); 100 | assert_eq!(from, back); 101 | } 102 | { 103 | let from = u128::MAX; 104 | let result = to_u256(from); 105 | let back = result.as_u128(); 106 | assert_eq!(from, back); 107 | } 108 | } 109 | 110 | #[test] 111 | fn test_to_u320() { 112 | { 113 | let from = 0; 114 | let result = to_u320(from); 115 | let back = result.as_u128(); 116 | assert_eq!(from, back); 117 | } 118 | { 119 | let from = 1; 120 | let result = to_u320(from); 121 | let back = result.as_u128(); 122 | assert_eq!(from, back); 123 | } 124 | { 125 | let from = 1324342342433342342; 126 | let result = to_u320(from); 127 | let back = result.as_u128(); 128 | assert_eq!(from, back); 129 | } 130 | { 131 | let from = u64::MAX as u128; 132 | let result = to_u320(from); 133 | let back = result.as_u128(); 134 | assert_eq!(from, back); 135 | } 136 | { 137 | let from = u64::MAX as u128 + 1; 138 | let result = to_u320(from); 139 | let back = result.as_u128(); 140 | assert_eq!(from, back); 141 | } 142 | { 143 | let from = u64::MAX as u128 + 2; 144 | let result = to_u320(from); 145 | let back = result.as_u128(); 146 | assert_eq!(from, back); 147 | } 148 | { 149 | let from = u128::MAX; 150 | let result = to_u320(from); 151 | let back = result.as_u128(); 152 | assert_eq!(from, back); 153 | } 154 | } 155 | 156 | #[test] 157 | fn test_u320_methods() { 158 | let _max = U320::MAX; 159 | let _from = U320::from(10); 160 | let zero = U320::zero(); 161 | let is_zero = zero.is_zero(); 162 | assert!(is_zero); 163 | } 164 | 165 | #[test] 166 | fn test_u320_to_u256() { 167 | let max_u256 = U320([u64::MAX, u64::MAX, u64::MAX, u64::MAX, 0]); 168 | let max_u320 = U320([u64::MAX, u64::MAX, u64::MAX, u64::MAX, u64::MAX]); 169 | // sample example 170 | { 171 | let u320: U320 = U320::from_dec_str("456974").unwrap(); 172 | let u256: U256 = u320_to_u256(u320); 173 | assert_eq!(u256, U256::from_dec_str("456974").unwrap()); 174 | } 175 | 176 | // max value fits into U256 177 | { 178 | let u320: U320 = max_u256.clone(); 179 | let u256: U256 = u320_to_u256(u320); 180 | assert_eq!(u320, U320::from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639935").unwrap()); 181 | assert_eq!(u256, U256::from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639935").unwrap()); 182 | } 183 | // max value + 1 does not fit into U256 184 | { 185 | let u320: U320 = max_u256.clone() + 1; 186 | let u256: Option = checked_u320_to_u256(u320); 187 | assert_eq!(u256, None); 188 | } 189 | // max u320 value 190 | { 191 | let u320: U320 = max_u320.clone(); 192 | let u256: Option = checked_u320_to_u256(u320); 193 | assert_eq!( 194 | u320, 195 | U320::from_dec_str("2135987035920910082395021706169552114602704522356652769947041607822219725780640550022962086936575").unwrap() 196 | ); 197 | assert_eq!(u256, None); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/walkthrough.rs: -------------------------------------------------------------------------------- 1 | // Welcome in walkthrough file of the decimal library 2 | // Below you can find a short example of use of this library 3 | // For more visit the `Invariant protocol's` repository 4 | 5 | #[cfg(test)] 6 | mod walkthrough { 7 | use crate::decimal; 8 | use crate::traits::*; 9 | use crate::U256; // used as the default type 10 | 11 | #[decimal(2, u128)] // second argument is the `big type`, checkout readme to know more 12 | #[derive(Default, PartialEq, Debug, Clone, Copy)] 13 | struct Percentage(u8); 14 | 15 | #[decimal(4)] 16 | #[derive(Default, PartialEq, Debug, Clone, Copy)] 17 | struct Price(u128); 18 | 19 | #[test] 20 | fn example_price_after_discount() { 21 | let price = Price::from_integer(10); // this corresponds with 10 * 10^k so 10^7 in this case 22 | let discount = Percentage::new(10); // using new doesn't account for decimal places so 0.10 here 23 | 24 | // addition expects being called for left and right values being of the same type 25 | // multiplication doesn't so you can be used like this: 26 | let price = price * (Percentage::from_integer(1) - discount); // the resulting type is always the type of the left value 27 | 28 | assert_eq!(price, Price::from_integer(9)); // asserts work without a problem 29 | } 30 | 31 | #[test] 32 | fn example_find_discount() { 33 | let original_price = Price::from_integer(10); 34 | let price_after_discount = Price::from_integer(8); 35 | 36 | let ratio = (original_price - price_after_discount) / original_price; 37 | let ratio = Percentage::from_decimal(ratio); // this will change scale to 2 so ratio is a percentage 38 | assert_eq!(ratio, Percentage(20)); // other way to declare a tuple struct, works same as `Percentage::new()` 39 | } 40 | 41 | #[test] 42 | fn example_price_rounding() { 43 | // Rounding is easier to show on small values 44 | let original_price = Price(5); // corresponds to 0.0005 45 | 46 | // There is one more useful way to declare a decimal: from_scale() 47 | // First argument is the value, second one is the shift of it to the right, like so: 48 | let half_price_coupon = Percentage::from_scale(5u8, 1); // corresponds 5 / 10^1, so 0.5 49 | 50 | // let price_after_discount = original_price * half_price_coupon; // expects 0.0003 51 | // The line above would round down, not great for our shop, let's round up: 52 | 53 | let price_after_discount = original_price.mul_up(half_price_coupon); 54 | assert_eq!(price_after_discount, Price(3)); 55 | 56 | let price_after_discount = price_after_discount.mul_up(half_price_coupon); // corresponds to 0.0002 57 | let price_after_discount = price_after_discount.mul_up(half_price_coupon); // result will be exact if rounding is not needed 58 | assert_eq!(price_after_discount, Price(1)); 59 | } 60 | 61 | #[test] 62 | fn example_handle_overflow() { 63 | let max_price = Price::max_instance(); 64 | let max_price_value = Price::max_value(); 65 | let price_scale = Price::scale(); 66 | 67 | // checked_add 68 | { 69 | let price = Price::new(27).checked_add(Price::new(479)); 70 | assert_eq!(price, Ok(Price::new(506))); 71 | 72 | let percentage = Percentage::max_instance() 73 | .checked_add(Percentage::new(1)) 74 | .unwrap_err(); 75 | assert_eq!(percentage, "checked_add: (self + rhs) additional overflow") 76 | } 77 | // checked_sub 78 | { 79 | let price = Price::new(479).checked_sub(Price::new(2597457)); 80 | assert_eq!( 81 | price, 82 | Err("checked_sub: (self - rhs) subtraction underflow".to_string()) 83 | ); 84 | 85 | let percentage = Percentage::from_integer(1); 86 | let result = percentage.checked_sub(Percentage::new(10)); 87 | assert_eq!(result, Ok(Percentage::new(90))); 88 | } 89 | // checked_div 90 | { 91 | let result = Price::from_integer(99).checked_div(Price::from_scale(5, 1)); 92 | assert_eq!(result, Ok(Price::from_integer(198))); 93 | } 94 | // checked big div 95 | { 96 | let price = Price::max_instance().checked_big_div(Price::new(50000)); 97 | assert_eq!( 98 | price, 99 | Ok(Price::new(68056473384187692692674921486353642291)) 100 | ); 101 | } 102 | // checked_from_scale 103 | { 104 | let overflow_err = 105 | Price::checked_from_scale(max_price_value, price_scale - 1).unwrap_err(); 106 | assert_eq!( 107 | overflow_err, 108 | "checked_from_scale: (multiplier * base) overflow" 109 | ); 110 | 111 | let result = Price::checked_from_scale(max_price_value, price_scale + 1).unwrap(); 112 | assert_eq!( 113 | result, 114 | Price::new(34028236692093846346337460743176821145u128) 115 | ); 116 | } 117 | // checked_from_scale_to_value 118 | { 119 | let result = 120 | Price::checked_from_scale_to_value(max_price_value, price_scale - 1).unwrap(); 121 | assert_eq!( 122 | result, 123 | U256::from_dec_str("3402823669209384634633746074317682114550").unwrap() 124 | ); 125 | } 126 | // checked_from_decimal 127 | { 128 | let price = Price::checked_from_decimal(Percentage::from_integer(1)).unwrap(); 129 | assert_eq!(price, Price::new(10000)); 130 | 131 | let convert_err = Percentage::checked_from_decimal(Price::max_instance()).unwrap_err(); 132 | assert_eq!(convert_err, "checked_from_scale: can't convert to result"); 133 | } 134 | // checked_from_decimal_to_value 135 | { 136 | let result = Price::checked_from_decimal_to_value(Price::max_instance()).unwrap(); 137 | assert_eq!(result, U256::from(Price::max_value())); 138 | } 139 | // checked_big_div_by_number & checked_big_div_by_number_up 140 | { 141 | let three = U256::from(Price::from_integer(3).get()); 142 | let result = Price::new(132_493).checked_big_div_by_number(three); 143 | assert_eq!(result, Ok(Price::new(44_164))); 144 | let result = Price::new(132_493).checked_big_div_by_number_up(three); 145 | assert_eq!(result, Ok(Price::new(44_165))); 146 | 147 | let convert_err = max_price 148 | .checked_big_div_by_number(U256::from(1)) 149 | .unwrap_err(); 150 | // checked_from_scale 151 | assert_eq!( 152 | convert_err, 153 | "checked_big_div_by_number: can't convert to result" 154 | ); 155 | let convert_err = max_price 156 | .checked_big_div_by_number_up(U256::from(1)) 157 | .unwrap_err(); 158 | assert_eq!( 159 | convert_err, 160 | "checked_big_div_by_number_up: can't convert to result" 161 | ); 162 | } 163 | } 164 | 165 | #[test] 166 | #[should_panic] 167 | fn example_overflow_without_being_too_big() { 168 | let percentage = Percentage(110); // 110% 169 | 170 | // The line below will panic, check out readme to understand why 171 | let _squared = percentage * percentage; 172 | } 173 | 174 | #[test] 175 | fn example_prepare_for_overflow() { 176 | let percentage = Percentage(110); // 110% 177 | 178 | // using `big type` for calculations (more on that in the readme) 179 | let squared = percentage.big_mul(percentage); 180 | assert_eq!(squared, Percentage(121)); 181 | } 182 | 183 | #[test] 184 | fn example_implement_additional_feature() { 185 | // Additional features can be easily added like so: 186 | impl Percentage { 187 | fn square(self) -> Self { 188 | self.big_mul(self) 189 | } 190 | } 191 | 192 | let percentage = Percentage(110); // 110% 193 | let squared = percentage.square(); 194 | assert_eq!(squared, Percentage(121)); 195 | } 196 | 197 | #[test] 198 | fn example_extract_overflowing_value() { 199 | // This would be rarely needed, but it's possible to handle overflow like so: 200 | let percentage = Percentage::from_integer(2); // 200% 201 | 202 | // let squared = percentage.big_mul(percentage); 203 | // this wouldn't help here, as 400 > u8::MAX 204 | 205 | let squared = percentage.big_mul_to_value(percentage); 206 | // now we a value and can use it for example like so: 207 | let inverse = Percentage(100).big_div_by_number(squared); 208 | 209 | assert_eq!(inverse, Percentage(25)); 210 | } 211 | } 212 | --------------------------------------------------------------------------------