fixed_unsigned/
lib.rs

1use std::char::from_digit;
2use std::cmp::Ordering;
3use std::error::Error;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::marker::PhantomData;
7use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
8use std::str::FromStr;
9
10///
11/// fixed-unsigned - A crate for fixed-point unsigned big integers
12///
13/// This was written to behave like bignumber.js
14///
15/// Not all traits you would expect for a number are implemented. Some things are implemented
16/// just to work with Nimiq.
17///
18
19use num_bigint::BigUint;
20use num_traits::identities::{One, Zero};
21use num_traits::ToPrimitive;
22
23pub mod types;
24
25
26/// Maximum number of digits a decimal number can have in a `u64`
27const U64_MAX_DIGITS: u64 = 19u64;
28/// Maximum decimal number that can be represented with an `u64`
29/// `U64_MAX_DECIMAL` = 10<sup>`U64_MAX_DIGITS`</sup>
30/// NOTE: If we go with 15 decimal places, this can safely fit a Javascript Integer
31const U64_MAX_DECIMAL: u64 = 10_000_000_000_000_000_000u64;
32
33/// Trait for a fixed scale. It only has one associated constant that must be implemented:
34/// `SCALE`, which defines the place of the decimal point.
35pub trait FixedScale {
36    const SCALE: u64;
37}
38
39/// A trait for rounding when scaling down
40pub trait RoundingMode {
41    /// Scale on `int_value` where `carrier` is the last digit that was already dropped.
42    fn round(int_value: BigUint, carrier: u8) -> BigUint;
43}
44
45/// Round half up - i.e. 0.4 -> 0 and 0.5 -> 1
46pub struct RoundHalfUp {}
47impl RoundingMode for RoundHalfUp{
48    #[inline]
49    fn round(int_value: BigUint, carrier: u8) -> BigUint {
50        int_value + if carrier >= 5u8 { 1u64 } else { 0u64 }
51    }
52}
53
54/// Round down - i.e. truncate
55pub struct RoundDown {}
56impl RoundingMode for RoundDown {
57    #[inline]
58    fn round(int_value: BigUint, _: u8) -> BigUint {
59        int_value
60    }
61}
62
63
64
65/// Error returned when a string representation can't be parse into a FixedUnsigned.
66/// TODO: Attach string to it for error message
67#[derive(Debug, PartialEq, Eq)]
68pub struct ParseError(String);
69
70impl fmt::Display for ParseError {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        write!(f, "{}: {}", self.description(), self.0)
73    }
74}
75
76impl Error for ParseError {
77    fn description(&self) -> &str {
78        "Failed to parse fixed point decimal:"
79    }
80
81    fn cause(&self) -> Option<&dyn Error> {
82        None
83    }
84}
85
86/// A fixed point unsigned integer
87///
88/// This is a `num_bigint::BigUint` with a fixed scale.
89///
90/// The fixed scale is determined by the generic `S` which implements `FixedScale` and
91/// provides the constant `FixedScale::SCALE`.
92#[derive(Clone)]
93pub struct FixedUnsigned<S>
94    where S: FixedScale
95{
96    int_value: BigUint,
97    scale: PhantomData<S>
98}
99
100impl<S> FixedUnsigned<S>
101    where S: FixedScale
102{
103    fn new(int_value: BigUint) -> Self {
104        Self { int_value, scale: PhantomData }
105    }
106
107    /// Scales up a `BigUint` by `scale`
108    fn scale_up(mut int_value: BigUint, mut scale: u64) -> BigUint {
109        while scale >= U64_MAX_DIGITS {
110            int_value *= U64_MAX_DECIMAL;
111            scale -= U64_MAX_DIGITS;
112        }
113        while scale > 0u64 {
114            int_value *= 10u64;
115            scale -= 1;
116        }
117        int_value
118    }
119
120    /// Scales down a `BigUint` by `scale`
121    fn scale_down<R: RoundingMode>(mut int_value: BigUint, mut scale: u64) -> BigUint {
122        // scale down by 10<sup>19</sup> as long as possible
123        while scale >= U64_MAX_DIGITS {
124            int_value /= U64_MAX_DECIMAL;
125            scale -= U64_MAX_DIGITS;
126        }
127        // scale down by 10 until we have to potentially round
128        while scale > 1u64 {
129            int_value /= 10u64;
130            scale -= 1;
131        }
132        // round
133        if scale > 0u64 {
134            // unwrap is safe, since `(int_value % 10u64)` < 10
135            let carrier = (&int_value % 10u64).to_u8().unwrap();
136            int_value /= 10u64;
137            int_value = R::round(int_value, carrier);
138        }
139        int_value
140    }
141
142    /// Returns the integer part as `BigUint` (i.e. the part before the decimal point)
143    pub fn int_part(&self) -> BigUint {
144        Self::scale_down::<RoundDown>(self.int_value.clone(), S::SCALE)
145    }
146
147    pub fn frac_part(&self) -> BigUint {
148        unimplemented!();
149    }
150
151    /// Returns the scale of the `FixedUnsigned` as u64.
152    ///
153    /// Note that the scale is fixed by type, so for a `FixedUnsigned<S>` you can also get it as
154    /// constant by `S::Scale`. This function is only for convenience.
155    pub fn scale(&self) -> u64 {
156        S::SCALE
157    }
158
159    pub fn from_bytes_be(bytes: &[u8]) -> Self {
160        Self::new(BigUint::from_bytes_be(bytes))
161    }
162
163    pub fn from_bytes_le(bytes: &[u8]) -> Self {
164        Self::new(BigUint::from_bytes_be(bytes))
165    }
166
167    pub fn to_bytes_be(&self) -> Vec<u8> {
168        self.int_value.to_bytes_be()
169    }
170
171    pub fn to_bytes_le(&self) -> Vec<u8> {
172        self.int_value.to_bytes_le()
173    }
174
175    /// Converts the `FixedUnsigned` to a string representation in base `radix`.
176    ///
177    /// # Panics
178    ///
179    /// This function will panic if the radix is 0 or greater than 36.
180    pub fn to_radix_string(&self, radix: u8, uppercase: bool) -> String {
181        if radix == 0 || radix > 36 {
182            panic!("Radix too large: {}", radix);
183        }
184        let digits = self.int_value.to_radix_be(u32::from(radix));
185        let mut string: String = String::new();
186        let decimal_place = digits.len().checked_sub(S::SCALE as usize);
187        if let Some(0) = decimal_place {
188            string.push('0');
189        }
190        for (i, d) in digits.iter().enumerate() {
191            match decimal_place {
192                Some(dp) if dp == i => string.push('.'),
193                _ => ()
194            }
195            // This unwrap is safe, because we to_radix_be only gives us digits in the right radix
196            let c = from_digit(u32::from(*d), u32::from(radix)).unwrap();
197            string.push(if uppercase { c.to_ascii_uppercase() } else { c }); // NOTE: `form_digit` returns lower-case characters
198        }
199        string
200    }
201
202    /// Converts a string representation to a `FixedUnsigned` with base `radix`.
203    ///
204    /// NOTE: This function always rounds down.
205    ///
206    /// # Panics
207    ///
208    /// This function will panic if the radix is 0 or greater than 36.
209    pub fn from_radix_string(string: &str, radix: u8) -> Result<Self, ParseError> {
210        if radix == 0 || radix > 36 {
211            panic!("Radix too large: {}", radix);
212        }
213        let mut digits: Vec<u8> = Vec::new();
214        let mut decimal_place = None;
215        for (i, c) in string.chars().enumerate() {
216            if c == '.' {
217                if decimal_place.is_some() {
218                    return Err(ParseError(String::from(string)))
219                }
220                decimal_place = Some(i)
221            }
222            else {
223                digits.push(c.to_digit(u32::from(radix)).unwrap() as u8)
224            }
225        }
226        if digits.is_empty() {
227            return Err(ParseError(String::from(string)));
228        }
229        // unscaled `int_value`
230        let int_value = BigUint::from_radix_be(digits.as_slice(), u32::from(radix))
231            .ok_or_else(|| ParseError(String::from(string)))?;
232        // the scale of the string representation
233        // NOTE: `string.len() - 1` is the number of digits. One is being subtracted for the decimal point
234        let scale = decimal_place.map(|p| string.len() - p - 1).unwrap_or(0) as u64;
235        // scale the unscaled `int_value` to the correct scale
236        let int_value = if scale < S::SCALE {
237            Self::scale_up(int_value, S::SCALE - scale)
238        }
239        else if scale > S::SCALE {
240            Self::scale_down::<RoundDown>(int_value, scale - S::SCALE)
241        }
242        else {
243            int_value
244        };
245        Ok(Self::new(int_value))
246    }
247
248    /// Converts from a BigUint to FixedScale. This scales the value appropriately, thus the
249    /// result will have 0 for decimal places.
250    fn from_biguint(int_value: BigUint) -> Self {
251        Self::new(Self::scale_up(int_value, S::SCALE))
252    }
253
254    /// Converts to a BigUint losing the decimal places
255    ///
256    /// NOTE: This is not implemented as a `Into`/`From` trait to make the loss of precision implicit
257    pub fn into_biguint(self) -> BigUint {
258        Self::scale_down::<RoundDown>(self.int_value, S::SCALE)
259    }
260
261    pub fn into_biguint_without_scale(self) -> BigUint {
262        self.int_value
263    }
264
265    pub fn bits(&self) -> usize {
266        self.int_value.bits()
267    }
268
269    pub fn bytes(&self) -> usize {
270        let bits = self.bits();
271        bits / 8 + if bits % 8 == 0 {0} else {1}
272    }
273
274    /// Adds two `BigUint`s interpreted as `FixedUnsigned<S>` and returns the result.
275    #[inline]
276    fn add(a: &BigUint, b: &BigUint) -> BigUint {
277        a + b
278    }
279
280    /// Subtracts two `BigUint`s interpreted as `FixedUnsigned<S>` and returns the result.
281    #[inline]
282    fn sub(a: &BigUint, b: &BigUint) -> BigUint {
283        a - b
284    }
285
286    /// Multiplies two `BigUint`s interpreted as `FixedUnsigned<S>` and returns the result.
287    #[inline]
288    fn mul(a: &BigUint, b: &BigUint) -> BigUint {
289        Self::scale_down::<RoundHalfUp>((a * b).clone(), S::SCALE)
290    }
291
292    /// Divides two `BigUint`s interpreted as `FixedUnsigned<S>` and returns the result.
293    #[inline]
294    fn div(a: &BigUint, b: &BigUint) -> BigUint {
295        // scale up 1 digit more - to keep the carrier, then divide and scale down 1 digit. The scale down of 1 will round appropriately
296        Self::scale_down::<RoundHalfUp>(Self::scale_up(a.clone(), S::SCALE + 1u64) / b, 1u64)
297    }
298
299    /// Convert from scale `S` to scale `T`.
300    pub fn into_scale<T: FixedScale, R: RoundingMode>(self) -> FixedUnsigned<T> {
301        FixedUnsigned::<T>::new(if S::SCALE < T::SCALE {
302            Self::scale_up(self.int_value,T::SCALE - S::SCALE)
303        }
304        else {
305            Self::scale_down::<R>(self.int_value, S::SCALE - T::SCALE)
306        })
307    }
308}
309
310impl<S> Add for FixedUnsigned<S>
311    where S: FixedScale
312{
313    type Output = Self;
314
315    fn add(self, rhs: FixedUnsigned<S>) -> Self::Output {
316        Self::new(Self::add(&(self.int_value), &(rhs.int_value)))
317    }
318}
319
320impl<'a, 'b, S> Add<&'b FixedUnsigned<S>> for &'a FixedUnsigned<S>
321    where S: FixedScale
322{
323    type Output = FixedUnsigned<S>;
324
325    fn add(self, rhs: &'b FixedUnsigned<S>) -> FixedUnsigned<S> {
326        FixedUnsigned::new(FixedUnsigned::<S>::add(&(self.int_value), &(rhs.int_value)))
327    }
328}
329
330impl<S> AddAssign for FixedUnsigned<S>
331    where S: FixedScale
332{
333    fn add_assign(&mut self, rhs: Self) {
334        self.int_value = FixedUnsigned::<S>::add(&(self.int_value), &(rhs.int_value))
335    }
336}
337
338impl<S> Sub for FixedUnsigned<S>
339    where S: FixedScale
340{
341    type Output = Self;
342
343    fn sub(self, rhs: FixedUnsigned<S>) -> Self::Output {
344        Self::new(Self::sub(&(self.int_value), &(rhs.int_value)))
345    }
346}
347
348impl<'a, 'b, S> Sub<&'b FixedUnsigned<S>> for &'a FixedUnsigned<S>
349    where S: FixedScale
350{
351    type Output = FixedUnsigned<S>;
352
353    fn sub(self, rhs: &'b FixedUnsigned<S>) -> FixedUnsigned<S>  {
354        FixedUnsigned::new(FixedUnsigned::<S>::sub(&(self.int_value), &(rhs.int_value)))
355    }
356}
357
358impl<S> SubAssign for FixedUnsigned<S>
359    where S: FixedScale
360{
361    fn sub_assign(&mut self, rhs: Self) {
362        self.int_value = FixedUnsigned::<S>::sub(&(self.int_value), &(rhs.int_value))
363    }
364}
365
366impl<S> Mul for FixedUnsigned<S>
367    where S: FixedScale
368{
369    type Output = Self;
370
371    fn mul(self, rhs: FixedUnsigned<S>) -> Self::Output {
372        Self::new(Self::mul(&(self.int_value), &(rhs.int_value)))
373    }
374}
375
376impl<'a, 'b, S> Mul<&'b FixedUnsigned<S>> for &'a FixedUnsigned<S>
377    where S: FixedScale
378{
379    type Output = FixedUnsigned<S>;
380
381    fn mul(self, rhs: &'b FixedUnsigned<S>) -> FixedUnsigned<S>  {
382        FixedUnsigned::new(FixedUnsigned::<S>::mul(&(self.int_value), &(rhs.int_value)))
383    }
384}
385
386impl<S> MulAssign for FixedUnsigned<S>
387    where S: FixedScale
388{
389    fn mul_assign(&mut self, rhs: Self) {
390        self.int_value = FixedUnsigned::<S>::mul(&(self.int_value), &(rhs.int_value))
391    }
392}
393
394impl<S> Div for FixedUnsigned<S>
395    where S: FixedScale
396{
397    type Output = Self;
398
399    fn div(self, rhs: FixedUnsigned<S>) -> Self::Output {
400        Self::new(Self::div(&(self.int_value), &(rhs.int_value)))
401    }
402}
403
404impl<'a, 'b, S> Div<&'b FixedUnsigned<S>> for &'a FixedUnsigned<S>
405    where S: FixedScale
406{
407    type Output = FixedUnsigned<S>;
408
409    fn div(self, rhs: &'b FixedUnsigned<S>) -> FixedUnsigned<S>  {
410        FixedUnsigned::new(FixedUnsigned::<S>::div(&(self.int_value), &(rhs.int_value)))
411    }
412}
413
414impl<S> DivAssign for FixedUnsigned<S>
415    where S: FixedScale
416{
417    fn div_assign(&mut self, rhs: Self) {
418        self.int_value = FixedUnsigned::<S>::div(&(self.int_value), &(rhs.int_value))
419    }
420}
421
422impl<S> PartialEq for FixedUnsigned<S>
423    where S: FixedScale
424{
425    fn eq(&self, other: &Self) -> bool {
426        self.int_value.eq(&other.int_value)
427    }
428}
429
430impl<S> Hash for FixedUnsigned<S>
431    where S: FixedScale
432{
433    fn hash<H: Hasher>(&self, state: &mut H) {
434        self.int_value.hash(state)
435    }
436}
437
438impl<S> Eq for FixedUnsigned<S>
439    where S: FixedScale
440{
441
442}
443
444impl<S> PartialOrd for FixedUnsigned<S>
445    where S: FixedScale
446{
447    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
448        self.int_value.partial_cmp(&other.int_value)
449    }
450}
451
452impl<S> Ord for FixedUnsigned<S>
453    where S: FixedScale
454{
455    fn cmp(&self, other: &Self) -> Ordering {
456        self.int_value.cmp(&other.int_value)
457    }
458}
459
460/*
461NOTE: Conflicts with implementation from crate `alloc`, because we implemented `Display`
462
463impl<S> ToString for FixedUnsigned<S>
464    where S: FixedScale
465{
466    fn to_string(&self) -> String {
467        self.to_radix_string(10, false)
468    }
469}
470*/
471
472impl<S> FromStr for FixedUnsigned<S>
473    where S: FixedScale
474{
475    type Err = ParseError;
476
477    fn from_str(s: &str) -> Result<Self, Self::Err> {
478        Self::from_radix_string(s, 10)
479    }
480}
481
482impl<S> fmt::Debug for FixedUnsigned<S>
483    where S: FixedScale
484{
485    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
486        write!(f, "FixedUnsigned({}, scale={})", self.to_radix_string(10, false), S::SCALE)
487    }
488}
489
490impl<S> fmt::Display for FixedUnsigned<S>
491    where S: FixedScale
492{
493    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
494        write!(f, "{}", self.to_radix_string(10, false))
495    }
496}
497
498impl<S> From<BigUint> for FixedUnsigned<S>
499    where S: FixedScale
500{
501    fn from(int_value: BigUint) -> Self {
502        Self::from_biguint(int_value)
503    }
504}
505
506/*
507    XXX While this is a nice thing to have, it causes a lot of conflicts
508
509impl<S, T> From<T> for FixedUnsigned<S>
510    where S: FixedScale, BigUint: From<T>, T:
511{
512    fn from(x: T) -> Self {
513        Self::from_biguint(BigUint::from(x))
514    }
515}
516*/
517
518impl<S: FixedScale> From<u64> for FixedUnsigned<S> {
519    fn from(x: u64) -> Self {
520        Self::from_biguint(BigUint::from(x))
521    }
522}
523
524impl<S: FixedScale> From<u32> for FixedUnsigned<S> {
525    fn from(x: u32) -> Self {
526        Self::from_biguint(BigUint::from(x))
527    }
528}
529
530impl<S: FixedScale> From<u16> for FixedUnsigned<S> {
531    fn from(x: u16) -> Self {
532        Self::from_biguint(BigUint::from(x))
533    }
534}
535
536impl<S: FixedScale> From<u8> for FixedUnsigned<S> {
537    fn from(x: u8) -> Self {
538        Self::from_biguint(BigUint::from(x))
539    }
540}
541
542impl<S> Zero for FixedUnsigned<S>
543    where S: FixedScale
544{
545    fn zero() -> Self {
546        Self::new(BigUint::zero())
547    }
548
549    fn is_zero(&self) -> bool {
550        self.int_value.is_zero()
551    }
552}
553
554impl<S> One for FixedUnsigned<S>
555    where S: FixedScale
556{
557    fn one() -> Self {
558        Self::from(1u64)
559    }
560}
561
562impl<S> Default for FixedUnsigned<S>
563    where S: FixedScale
564{
565    fn default() -> Self {
566        Self::zero()
567    }
568}
569
570/// Converts a `f64` to a `FixedUnsigned`
571impl<S: FixedScale> From<f64> for FixedUnsigned<S> {
572    fn from(x: f64) -> Self {
573        // `bignumber.js` takes the `toString` of a `Number` to convert to `BigNumber`
574        // NOTE: JS and Rust seem both to use 16 decimal places
575        Self::from_str(&format!("{:.16}", x)).unwrap()
576    }
577}