units.mojo (2726B)
1 from hyf_core.domain.quantity import Quantity, new_quantity 2 3 4 @fieldwise_init 5 struct ConversionRule(Copyable, Movable): 6 var from_unit: String 7 var to_unit: String 8 var numerator: Int 9 var denominator: Int 10 var version: String 11 12 13 def unit_dimension(unit: String) -> String: 14 var lowered = unit.lower() 15 if lowered == "kg" or lowered == "lb" or lowered == "g" or lowered == "oz": 16 return "mass" 17 if lowered == "l" or lowered == "ml": 18 return "volume" 19 if lowered == "each" or lowered == "box" or lowered == "bunch": 20 return "count" 21 return "unknown" 22 23 24 def _power_of_ten_exponent(denominator: Int) raises -> Int: 25 if denominator <= 0: 26 raise Error("conversion denominator must be positive") 27 var remaining = denominator 28 var exponent = 0 29 while remaining > 1: 30 if remaining % 10 != 0: 31 raise Error( 32 "unsupported conversion denominator (must be a power of ten)" 33 ) 34 remaining //= 10 35 exponent += 1 36 return exponent 37 38 39 def conversion_rule( 40 from_unit: String, 41 to_unit: String, 42 numerator: Int, 43 denominator: Int, 44 version: String, 45 ) raises -> ConversionRule: 46 if from_unit.strip() == "" or to_unit.strip() == "": 47 raise Error("conversion requires from_unit and to_unit") 48 if numerator <= 0: 49 raise Error("conversion numerator must be positive") 50 if unit_dimension(from_unit) != unit_dimension(to_unit): 51 raise Error("conversion rule crosses incompatible dimensions") 52 _ = _power_of_ten_exponent(denominator) 53 return ConversionRule( 54 from_unit=String(from_unit), 55 to_unit=String(to_unit), 56 numerator=numerator, 57 denominator=denominator, 58 version=String(version), 59 ) 60 61 62 def apply_conversion( 63 quantity: Quantity, rule: ConversionRule 64 ) raises -> Quantity: 65 if quantity.unit != rule.from_unit: 66 raise Error("conversion rule does not apply to unit " + quantity.unit) 67 if quantity.dimension != unit_dimension(rule.to_unit): 68 raise Error("conversion rule crosses incompatible dimensions") 69 var exponent = _power_of_ten_exponent(rule.denominator) 70 var result = quantity.copy() 71 result.value = quantity.value * rule.numerator 72 result.scale = quantity.scale + exponent 73 result.unit = String(rule.to_unit) 74 result.dimension = String(unit_dimension(rule.to_unit)) 75 return result^ 76 77 78 def normalize_quantity_with_rule( 79 quantity: Quantity, rule: ConversionRule 80 ) raises -> Quantity: 81 var result = apply_conversion(quantity, rule) 82 if result.qualifier != quantity.qualifier: 83 raise Error("normalization must preserve the approximation qualifier") 84 return result^