datex_core/datex_values/primitives/
quantity.rs

1use std::{collections::HashMap, ops::{Div}};
2
3use num_bigint::{BigInt, BigUint, Sign};
4use num_integer::Integer;
5use lazy_static::lazy_static;
6use pad::PadStr;
7
8
9static EXPONENT_MIN:i8 = -128;
10
11static EXPONENT_MAX:i8 = 127;
12
13#[repr(u8)]
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub enum BaseUnit {
16
17	// SI base units 0x00 - 0x0f
18	SECOND  = 0x00,
19	METRE   = 0x01,
20	GRAM    = 0x02,
21	AMPERE  = 0x03,
22	KELVIN  = 0x04,
23	MOLE    = 0x05,
24	CANDELA = 0x06,
25
26	// Currency units with ISO codes 0xa0 - 0xdf
27	EUR    = 0xa0,
28	USD    = 0xa1,
29	GBP    = 0xa2,
30	RUB    = 0xa3,
31	CNY    = 0xa4,
32	JPY    = 0xa5,
33
34	CMO    = 0xc0, // calendar month
35
36	DIMENSIONLESS = 0xff,
37}
38
39type UnitFactor = (BaseUnit, i8);
40type Unit = Vec<UnitFactor>;
41
42
43
44lazy_static!{
45    static ref UNIT_SYMBOLS: HashMap<BaseUnit, &'static str> = [
46        (BaseUnit::SECOND, "s"),
47        (BaseUnit::METRE, "m"),
48        (BaseUnit::GRAM, "g"),
49        (BaseUnit::AMPERE, "A"),
50        (BaseUnit::KELVIN, "K"),
51        (BaseUnit::MOLE, "mol"),
52        (BaseUnit::CANDELA, "cd"),
53
54        (BaseUnit::EUR, "EUR"),
55        (BaseUnit::USD, "USD"),
56        (BaseUnit::GBP, "GBP"),
57        (BaseUnit::RUB, "RUB"),
58        (BaseUnit::CNY, "CNY"),
59        (BaseUnit::JPY, "JPY"),
60
61        (BaseUnit::CMO, "mo"),
62
63        (BaseUnit::DIMENSIONLESS, "x"),
64    ].iter().copied().collect();
65}
66
67
68
69#[derive(Clone)]
70pub struct Quantity {
71	pub sign: bool, // true = positive, false = negative
72	pub numerator: BigUint,
73	pub denominator: BigUint,
74
75    pub short_divisor: BigUint,
76
77	pub unit: Unit
78}
79
80impl Quantity {
81
82    pub fn to_string(&self, _colorized:bool) -> String {
83        self.value_to_string(true, None) + &self.get_unit_string()
84    }
85
86    pub fn value_to_string(&self, alias_factor:bool, decimals:Option<u8>) -> String {
87        
88        let (mut numerator, mut denominator) = (BigInt::from_biguint(Sign::Plus, self.numerator.clone()), BigInt::from_biguint(Sign::Plus, self.denominator.clone()));
89
90        // divide by short_divisor to match alias factor
91        if alias_factor {
92            (numerator, denominator) = Quantity::normalize_fraction(numerator, denominator*BigInt::from_biguint(Sign::Plus, self.short_divisor.clone()));
93        }
94
95        // fixed decimals
96        if decimals.is_some() {
97            // return numerator/denominator as fixed decimal string
98            numerator.div(denominator).to_string()
99        } 
100        // finite decimal representation
101        else if Quantity::has_finite_decimal_rep(&mut denominator.to_biguint().unwrap()) {
102            // this.#finiteFractionToDecimalString(numerator, denominator)
103            self.finite_fraction_to_decimal_string(numerator, denominator)
104        }
105        // fraction
106        else {
107            format!("{}/{}", numerator, denominator)
108        }
109    
110    }
111
112
113    fn finite_fraction_to_decimal_string(&self, mut numerator:BigInt, denominator:BigInt) -> String {
114
115        let mut shift = denominator.to_string().len() as u32; // absolute value
116
117        // TODO more efficient algorithm for this?
118
119        // get next higher denominator with power of 10
120
121        if !Quantity::is_power_of_10(denominator.clone()) {
122
123            let mut found = false;
124            for _ in 0..10000 { // only try 10000 iterations, works in first iteration in most cases
125
126                // d % 10^x = 0 => solve s
127
128                let new_denominator = BigInt::from(10).pow(shift); // new possible base 10 denominator
129
130                // is integer factor, can use as new denominator
131                if &new_denominator % &denominator == BigInt::from(0) {
132                    numerator *= &new_denominator / &denominator;
133                    found = true;
134                    break;
135                }
136                // try higher denominator 
137                else {
138                    shift += 1; 
139                }
140            }
141
142            if !found {
143                return "invalid".to_string();
144            }
145        }
146        else {
147            shift -= 1;
148        }
149
150        let string = (numerator).to_string().pad(
151            shift as usize, '0', pad::Alignment::Right, false
152        );
153        let comma_shift = string.len() - shift as usize;
154        let p1 = &string[0..comma_shift];
155        let p2 = &string[comma_shift..];
156
157        (if self.sign {""} else {"-"}).to_owned() + p1 + (if p2.len() > 0 {if p1.len() > 0 {"."} else {"0."}} else {""}) + p2
158    }
159
160
161    pub fn get_unit_string(&self) -> String {
162        let mut formatted = String::new();
163        let mut is_first = true;
164        let _format_divisor = 1;
165
166        for encoded in &self.unit {
167            if is_first {
168                formatted += if encoded.1 < 0 {"x/"} else {""}
169            }
170            else {
171                formatted += if encoded.1 < 0  {"/"} else {"*"}
172            }
173            formatted += &Quantity::get_unit_factor_string(encoded, true);
174
175            is_first = false;
176        }
177
178        formatted
179    }
180
181    fn get_unit_factor_string(unit: &UnitFactor, abs_exponent: bool) -> String {
182        let exponent = if abs_exponent {unit.1.abs()} else {unit.1};
183        if exponent == 1 {
184            Quantity::get_base_unit_string(unit.0)
185        } else {
186            format!("{}^{}", Quantity::get_base_unit_string(unit.0), exponent)
187        }
188    }
189
190    fn get_base_unit_string(base_unit: BaseUnit) -> String {
191        UNIT_SYMBOLS.get(&base_unit).unwrap().to_string()
192    }
193
194	fn has_finite_decimal_rep(denominator:&mut BigUint) -> bool {
195        while denominator.mod_floor(&BigUint::from(2u8)).eq(&BigUint::from(0u8)) {
196            *denominator /= BigUint::from(2u8);
197        }
198		let i = &mut BigUint::from(3u8);
199        while i.pow(2) <= *denominator {
200            while denominator.mod_floor(i).eq(&BigUint::from(0u8)) {
201                *denominator /= BigUint::clone(i);
202                if (*i).ne(&BigUint::from(2u8)) && (*i).ne(&BigUint::from(5u8)) {
203                    return false // not allowed
204                }
205            }
206			*i += BigUint::from(2u8);
207        }
208        if *denominator > BigUint::from(2u8) { // for primes larger than 2
209            if (*denominator).ne(&BigUint::from(2u8)) && (*denominator).ne(&BigUint::from(5u8)) {
210                return false // not allowed
211            }
212        }
213
214        true
215    }
216
217    fn raise_unit_to_power(unit:Unit, power:usize) -> Unit {
218        let mut new_exp:Unit = vec![];
219        for u in &unit {
220            new_exp.push((u.0, u.1 * power as i8));
221        }
222        new_exp
223    }
224
225    fn has_same_dimension(&self, other:&Quantity) -> bool {
226        // check if units are equal
227        for i in 0..self.unit.len() {
228            if self.unit[i].0 != other.unit[i].0 || self.unit[i].1 != other.unit[i].1 {return false}
229        }
230        true
231    }
232
233
234    fn equals(&self, other:&Quantity) -> bool {
235        return self.has_same_dimension(other) && self.denominator == self.denominator && self.numerator == other.numerator && self.sign == other.sign;
236    }
237
238
239    fn normalize_fraction<'a>(mut numerator: BigInt, mut denominator: BigInt) -> (BigInt, BigInt) {
240        let zero = &BigInt::from(0u8);
241        let one = &BigInt::from(1u8);
242        let minus_one = &BigInt::from(-1i8);
243
244        // denominator always positive, numerator has sign
245        if &numerator<zero && &denominator<zero {
246            numerator = numerator * minus_one;
247            denominator = denominator * minus_one;
248        }
249        else if &numerator>=zero && &denominator<zero {
250            numerator = numerator * BigInt::from(-1i8);
251            denominator = denominator * BigInt::from(-1i8);
252        }
253
254        // reduce to lowest terms
255        let gcd = Quantity::gcd(numerator.clone(), denominator.clone());
256
257        if &gcd > one {
258            numerator = numerator/&gcd;
259            denominator = denominator/&gcd;
260        }
261        else if &gcd < minus_one {
262            let gcd2 = gcd * minus_one;
263            numerator = numerator/&gcd2;
264            denominator = denominator/&gcd2;
265        }
266        
267        (numerator, denominator)
268    }
269
270
271    // greates common divisor (Euclidian algorithm)
272    fn gcd(mut n1: BigInt, mut n2: BigInt) -> BigInt {
273        let zero = &BigInt::from(0u8);
274
275        while &n2 != zero {
276            let t = n2;
277            n2 = &n1 % &t;
278            n1 = t;
279        }
280        n1
281    }
282    
283
284    // least common multiple
285    fn lcm (n1: BigInt, n2: BigInt) -> BigInt {
286        let prod = &n1 * &n2;
287        prod / Quantity::gcd(n1, n2)
288    }
289    
290
291    fn is_power_of_10(mut n: BigInt) -> bool {
292        let zero = &BigInt::from(0u8);
293        let one = &BigInt::from(1u8);
294        let ten = &BigInt::from(10u8);
295        while &n > one && &(&n % ten) == zero {
296            n = n/ten;
297        }
298        &n == one
299    }
300
301}