datex_core/datex_values/primitives/
quantity.rs1use 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 SECOND = 0x00,
19 METRE = 0x01,
20 GRAM = 0x02,
21 AMPERE = 0x03,
22 KELVIN = 0x04,
23 MOLE = 0x05,
24 CANDELA = 0x06,
25
26 EUR = 0xa0,
28 USD = 0xa1,
29 GBP = 0xa2,
30 RUB = 0xa3,
31 CNY = 0xa4,
32 JPY = 0xa5,
33
34 CMO = 0xc0, 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, 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 if alias_factor {
92 (numerator, denominator) = Quantity::normalize_fraction(numerator, denominator*BigInt::from_biguint(Sign::Plus, self.short_divisor.clone()));
93 }
94
95 if decimals.is_some() {
97 numerator.div(denominator).to_string()
99 }
100 else if Quantity::has_finite_decimal_rep(&mut denominator.to_biguint().unwrap()) {
102 self.finite_fraction_to_decimal_string(numerator, denominator)
104 }
105 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; if !Quantity::is_power_of_10(denominator.clone()) {
122
123 let mut found = false;
124 for _ in 0..10000 { let new_denominator = BigInt::from(10).pow(shift); if &new_denominator % &denominator == BigInt::from(0) {
132 numerator *= &new_denominator / &denominator;
133 found = true;
134 break;
135 }
136 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 }
205 }
206 *i += BigUint::from(2u8);
207 }
208 if *denominator > BigUint::from(2u8) { if (*denominator).ne(&BigUint::from(2u8)) && (*denominator).ne(&BigUint::from(5u8)) {
210 return false }
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 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 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 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 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 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}