[READ-ONLY] Mirror of https://github.com/probablykasper/cpc. Text calculator with support for units and conversion cpc.kasper.space
calculator cli conversion converter library math package units units-converter wasm website
0

Configure Feed

Select the types of activity you want to include in your feed.

Add tests

Kasper (Jun 24, 2026, 11:49 AM +0200) b9ee8b3d fff4fef5

+86 -100
+3 -3
src/evaluator.rs
··· 249 249 #[cfg(test)] 250 250 mod tests { 251 251 use super::*; 252 - use crate::eval; 252 + use crate::{eval, units::Unit::*}; 253 253 254 254 fn eval_default<'a>(input: &'a str) -> Number { 255 255 let result = eval(input, true, false).unwrap(); ··· 257 257 } 258 258 fn eval_num<'a>(input: &'a str) -> String { 259 259 let result = eval(input, true, false).unwrap(); 260 - assert_eq!(result.unit, Unit::NoUnit); 260 + assert!(result.is_unitless()); 261 261 262 262 result.to_string() 263 263 } ··· 269 269 assert_eq!(eval_num("(3)-2"), "1"); 270 270 assert_eq!( 271 271 eval_default("-1km to m"), 272 - Number::new(d!(-1000), Unit::Meter) 272 + Number::with_basic_unit(d!(-1000), Meter) 273 273 ); 274 274 assert_eq!(eval_num("2*-3*0.5"), "-3"); 275 275 assert_eq!(eval_num("-3^2"), "-9");
+31 -20
src/lib.rs
··· 22 22 //! } 23 23 //! ``` 24 24 25 - use crate::units::{Unit, UnitType, primitive_unit}; 25 + use crate::units::{Unit, UnitType, primitive_unit, sort_units}; 26 26 use fastnum::{D128, dec128 as d}; 27 - use std::{ 28 - cmp::Reverse, 29 - fmt::{self, Display}, 30 - }; 27 + use std::fmt::{self, Debug, Display}; 31 28 use web_time::Instant; 32 29 33 30 /// Turns an [`AstNode`](parser::AstNode) into a [`Number`] 34 31 pub mod evaluator; 35 32 /// Turns a string into [`Token`]s 36 - #[rustfmt::skip] 37 33 pub mod lexer; 38 - #[rustfmt::skip] 39 34 mod lookup; 40 35 /// Turns [`Token`]s into an [`AstNode`](parser::AstNode) 41 36 pub mod parser; 42 37 /// Units, and functions you can use with them 43 - #[rustfmt::skip] 44 38 pub mod units; 45 39 46 - #[derive(Clone, Debug, PartialEq)] 40 + #[derive(Clone)] 47 41 /// A number with a `Unit`. 48 42 /// 49 43 /// Example: ··· 52 46 /// use cpc::units::Unit; 53 47 /// use fastnum::dec128; 54 48 /// 55 - /// let x = Number { 56 - /// value: dec128!(100), 57 - /// unit: Unit::Meter, 58 - /// }; 49 + /// let x = Number::with_basic_unit(dec128!(100), Unit::Meter); 59 50 /// ``` 60 51 pub struct Number { 61 52 /// The value of the number ··· 149 140 _ => format!("{value} {word}"), 150 141 }; 151 142 write!(f, "{output}") 143 + } 144 + } 145 + impl Debug for Number { 146 + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 147 + let unit_strings: Vec<_> = self 148 + .unit 149 + .iter() 150 + .map(|u| format!("{:?}^{}", u.0, u.1)) 151 + .collect(); 152 + write!( 153 + f, 154 + "Number({} {})", 155 + self.get_simplified_value(), 156 + unit_strings.join(" ") 157 + ) 158 + } 159 + } 160 + impl PartialEq for Number { 161 + fn eq(&self, other: &Self) -> bool { 162 + self.value == other.value && self.primitive_unit() == other.primitive_unit() 152 163 } 153 164 } 154 165 ··· 397 408 398 409 #[test] 399 410 fn test_evaluations() { 400 - assert_eq!(default_eval("-2(-3)"), Number::new(d!(6), Unit::NoUnit)); 401 - assert_eq!(default_eval("-2(3)"), Number::new(d!(-6), Unit::NoUnit)); 402 - assert_eq!(default_eval("(3)-2"), Number::new(d!(1), Unit::NoUnit)); 411 + assert_eq!(default_eval("-2(-3)"), Number::new_unitless(d!(6))); 412 + assert_eq!(default_eval("-2(3)"), Number::new_unitless(d!(-6))); 413 + assert_eq!(default_eval("(3)-2"), Number::new_unitless(d!(1))); 403 414 assert_eq!( 404 415 default_eval("-1km to m"), 405 - Number::new(d!(-1000), Unit::Meter) 416 + Number::with_basic_unit(d!(-1000), Unit::Meter) 406 417 ); 407 - assert_eq!(default_eval("2*-3*0.5"), Number::new(d!(-3), Unit::NoUnit)); 408 - assert_eq!(default_eval("-3^2"), Number::new(d!(-9), Unit::NoUnit)); 409 - assert_eq!(default_eval("-1+2"), Number::new(d!(1), Unit::NoUnit)); 418 + assert_eq!(default_eval("2*-3*0.5"), Number::new_unitless(d!(-3))); 419 + assert_eq!(default_eval("-3^2"), Number::new_unitless(d!(-9))); 420 + assert_eq!(default_eval("-1+2"), Number::new_unitless(d!(1))); 410 421 } 411 422 }
+3 -2
src/lookup.rs
··· 1 + use crate::NamedNumber; 1 2 use crate::NamedNumber::*; 2 - use crate::NamedNumber; 3 - use fastnum::{dec128 as d, D128}; 3 + use fastnum::{D128, dec128 as d}; 4 4 5 5 /// Returns the corresponding [`D128`] of a [`NamedNumber`] 6 + #[rustfmt::skip] 6 7 pub fn lookup_named_number(named_number: &NamedNumber) -> D128 { 7 8 match named_number { 8 9 Hundred => d!(100),
+49 -75
src/units.rs
··· 108 108 // and evaluator 109 109 macro_rules! create_units { 110 110 ( $( $variant:ident : $properties:expr ),*, ) => { 111 - #[derive(Clone, Copy, PartialEq, Debug)] 111 + #[derive(Clone, Copy, PartialEq, Debug, Eq, PartialOrd, Ord)] 112 112 /// A Unit enum. Note that it can also be [`NoUnit`]. 113 113 pub enum Unit { 114 114 $($variant),* ··· 671 671 let new_number = to_ideal_unit(new_number); 672 672 Ok(new_number) 673 673 } 674 - // todo: 8 megabytes per second * 1 minute 675 - // todo: 8 megaFLOP per second * 1 minute 676 - 677 - // } else if lcat == DataTransferRate && rcat == Time { 678 - // // 8 megabytes per second * 1 minute 679 - // let data_rate_value = left.value * left.unit.weight(); 680 - // let seconds = convert(right, Second)?; 681 - // let result = data_rate_value * seconds.value; 682 - // let final_unit = match left.unit { 683 - // BitsPerSecond => Bit, 684 - // KilobitsPerSecond => Kilobit, 685 - // MegabitsPerSecond => Megabit, 686 - // GigabitsPerSecond => Gigabit, 687 - // TerabitsPerSecond => Terabit, 688 - // PetabitsPerSecond => Petabit, 689 - // ExabitsPerSecond => Exabit, 690 - // ZettabitsPerSecond => Zettabit, 691 - // YottabitsPerSecond => Yottabit, 692 - // KibibitsPerSecond => Kibibit, 693 - // MebibitsPerSecond => Mebibit, 694 - // GibibitsPerSecond => Gibibit, 695 - // TebibitsPerSecond => Tebibit, 696 - // PebibitsPerSecond => Pebibit, 697 - // ExbibitsPerSecond => Exbibit, 698 - // ZebibitsPerSecond => Zebibit, 699 - // YobibitsPerSecond => Yobibit, 700 - // BytesPerSecond => Byte, 701 - // KilobytesPerSecond => Kilobyte, 702 - // MegabytesPerSecond => Megabyte, 703 - // GigabytesPerSecond => Gigabyte, 704 - // TerabytesPerSecond => Terabyte, 705 - // PetabytesPerSecond => Petabyte, 706 - // ExabytesPerSecond => Exabyte, 707 - // ZettabytesPerSecond => Zettabyte, 708 - // YottabytesPerSecond => Yottabyte, 709 - // KibibytesPerSecond => Kibibyte, 710 - // MebibytesPerSecond => Mebibyte, 711 - // GibibytesPerSecond => Gibibyte, 712 - // TebibytesPerSecond => Tebibyte, 713 - // PebibytesPerSecond => Pebibyte, 714 - // ExbibytesPerSecond => Exbibyte, 715 - // ZebibytesPerSecond => Zebibyte, 716 - // YobibytesPerSecond => Yobibyte, 717 - // _ => Bit, 718 - // }; 719 - // let data_storage = Number::new(result, Bit); 720 - // Ok(convert(data_storage, final_unit)?) 721 - // } else if lcat == FlopRate && rcat == Time { 722 - // // 8 megaFLOP per second * 1 minute 723 - // let compute_perf_value = left.value * left.unit.weight(); 724 - // let seconds = convert(right, Second)?; 725 - // let result = compute_perf_value * seconds.value; 726 - // let final_unit = match left.unit { 727 - // FlopPerSecond => Flop, 728 - // KiloFlopPerSecond => KiloFlop, 729 - // MegaFlopPerSecond => MegaFlop, 730 - // GigaFlopPerSecond => GigaFlop, 731 - // TeraFlopPerSecond => TeraFlop, 732 - // PetaFlopPerSecond => PetaFlop, 733 - // ExaFlopPerSecond => ExaFlop, 734 - // ZettaFlopPerSecond => ZettaFlop, 735 - // YottaFlopPerSecond => YottaFlop, 736 - // RonnaFlopPerSecond => RonnaFlop, 737 - // QuettaFlopPerSecond => QuettaFlop, 738 - // _ => Flop, 739 - // }; 740 - // let compute_work = Number::new(result, Flop); 741 - // Ok(convert(compute_work, final_unit)?) 742 - // } 743 674 } 744 675 745 676 /// Divide a [`Number`] by another [`Number`]. ··· 835 766 836 767 let value_string = &value.to_string(); 837 768 let value_d128 = D128::from_str(value_string, Context::default()).unwrap(); 838 - let number = Number::new(value_d128, unit); 769 + let number = Number::with_basic_unit(value_d128, unit); 839 770 840 - let result = convert(number, to_unit); 771 + let result = convert(number, vec![(to_unit, 1)]); 841 772 let string_result = &result.unwrap().value.to_string(); 842 773 let float_result = f64::from_str(string_result).unwrap(); 843 774 ··· 1182 1113 assert_float_eq!(convert_test(1000.0, Terahertz, Petahertz), 1.0); 1183 1114 assert_float_eq!(convert_test(60.0, Hertz, RevolutionsPerMinute), 1.0); 1184 1115 1185 - assert_float_eq!(convert_test(3.6, KilometersPerHour, MetersPerSecond), 1.0); 1116 + // assert_float_eq!(convert_test(3.6, KilometersPerHour, MetersPerSecond), 1.0); 1186 1117 assert_float_eq!(convert_test(0.3048, MetersPerSecond, FeetPerSecond), 1.0); 1187 - assert_float_eq!(convert_test(1.609344, KilometersPerHour, MilesPerHour), 1.0); 1188 - assert_float_eq!(convert_test(1.852, KilometersPerHour, Knot), 1.0); 1118 + // assert_float_eq!(convert_test(1.609344, KilometersPerHour, MilesPerHour), 1.0); 1119 + // assert_float_eq!(convert_test(1.852, KilometersPerHour, Knot), 1.0); 1189 1120 1190 1121 assert_float_eq!(convert_test(274.15, Kelvin, Celsius), 1.0); 1191 1122 assert_float_eq!(convert_test(300.0, Kelvin, Fahrenheit), 80.33); ··· 1193 1124 assert_float_eq!(convert_test(-15.0, Celsius, Fahrenheit), 5.0); 1194 1125 assert_float_eq!(convert_test(80.33, Fahrenheit, Kelvin), 300.0); 1195 1126 assert_float_eq!(convert_test(5.0, Fahrenheit, Celsius), -15.0); 1127 + } 1128 + 1129 + #[track_caller] 1130 + fn eval_test(a: &str, b: &str) { 1131 + let result_a = crate::eval(a, true, false).unwrap(); 1132 + let result_b = crate::eval(b, true, false).unwrap(); 1133 + assert_eq!(result_a, result_b, "{a} != {b}"); 1134 + } 1135 + #[track_caller] 1136 + fn eval_approx_test(a: &str, b: &str) { 1137 + let result_a = crate::eval(a, true, false).unwrap(); 1138 + let result_b = crate::eval(b, true, false).unwrap(); 1139 + let diff_pct: D128 = result_a.value / result_b.value - 1; 1140 + let diff_pct = diff_pct.abs(); 1141 + assert!(diff_pct <= d!(0.00000001), "{a} !≈ {b}") 1142 + } 1143 + 1144 + #[test] 1145 + fn test_unit_evals() { 1146 + eval_test("3.6km/1h", "3.6 kph"); 1147 + eval_test("0.3048 m/s to ft/s", "1 ft/s"); 1148 + eval_test("1.609344 km/1h to mph", "1 mph"); 1149 + eval_approx_test("1.852 kph to knots", "1 knots"); 1150 + eval_test("120 seconds to minutes", "2 minutes"); 1151 + eval_test("100 cm to m", "1 m"); 1152 + eval_test("1 km2 to m2", "1000000 m2"); 1153 + eval_test("1 liter to ml", "1000 ml"); 1154 + eval_test("1 kg to g", "1000 g"); 1155 + eval_test("1 KB to bytes", "1000 bytes"); 1156 + eval_test("1 MBps to KBps", "1000 KBps"); 1157 + eval_test("1 KFLOP to FLOP", "1000 FLOP"); 1158 + eval_test("1 KFLOPs to FLOPs", "1000 FLOPs"); 1159 + eval_test("1 kWh to Wh", "1000 Wh"); 1160 + eval_test("1 kW to W", "1000 W"); 1161 + eval_test("1000 mA to A", "1 A"); 1162 + eval_test("1000 mΩ to Ω", "1 Ω"); 1163 + eval_test("1000 mV to V", "1 V"); 1164 + eval_test("1 bar to Pa", "100000 Pa"); 1165 + eval_test("1 kHz to Hz", "1000 Hz"); 1166 + eval_approx_test("1 km/h to m/s", "0.27777777777777777777 m/s"); 1167 + eval_test("0 C to K", "273.15 K"); 1168 + eval_test("8 megabytes per second * 1 minute", "480mb"); 1169 + eval_test("8 megaFLOP per second * 1 minute", "480megaFLOP"); 1196 1170 } 1197 1171 }