From 8823332d936be4f28df5f38a7cf7f1c5e4b1b09f Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 23 Jun 2026 14:53:57 +0200 Subject: [PATCH 01/15] WIP units with exponents --- src/evaluator.rs | 134 ++-- src/lexer.rs | 1989 +++++++++++++++++++++++++++++++++------------- src/lib.rs | 99 ++- src/units.rs | 1171 ++++++++++++++++----------- 4 files changed, 2339 insertions(+), 1054 deletions(-) diff --git a/src/evaluator.rs b/src/evaluator.rs index 94e3bdb..f7cab1a 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -1,13 +1,13 @@ -use crate::lookup::{lookup_factorial, lookup_named_number}; -use crate::parser::AstNode; -use crate::units::{add, convert, divide, modulo, multiply, pow, subtract, Unit, UnitType}; -use crate::Constant::{Pi, E}; +use crate::Constant::{E, Pi}; use crate::FunctionIdentifier::*; use crate::Operator::{Caret, Divide, Minus, Modulo, Multiply, Plus}; use crate::TextOperator::{Of, To}; use crate::UnaryOperator::{Factorial, Percent}; +use crate::lookup::{lookup_factorial, lookup_named_number}; +use crate::parser::AstNode; +use crate::units::{add, convert, divide, modulo, multiply, pow, subtract}; use crate::{Number, Token}; -use fastnum::{dec128 as d, decimal::RoundingMode, D128}; +use fastnum::{D128, dec128 as d, decimal::RoundingMode}; /// Evaluate an [`AstNode`] into a [`Number`] pub fn evaluate(ast: &AstNode) -> Result { @@ -61,57 +61,51 @@ fn evaluate_node(ast_node: &AstNode) -> Result { let token = &ast_node.token; let children = &ast_node.children; match token { - Token::Number(number) => Ok(Number::new(*number, Unit::NoUnit)), + Token::Number(number) => Ok(Number::new_unitless(*number)), Token::Constant(constant) => match constant { - Pi => Ok(Number::new( - D128::PI, - Unit::NoUnit, - )), - E => Ok(Number::new( - D128::E, - Unit::NoUnit, - )), + Pi => Ok(Number::new_unitless(D128::PI)), + E => Ok(Number::new_unitless(D128::E)), }, Token::FunctionIdentifier(function) => { let child_node = children.get(0).ok_or("Paren has no child[0]")?; let child_answer = evaluate_node(child_node)?; match function { Cbrt => { - if child_answer.unit.category() == UnitType::NoType { + if child_answer.is_unitless() { let result = cbrt(child_answer.value); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } else { Err("log() only accepts UnitType::NoType".to_string()) } } Sqrt => { - if child_answer.unit.category() == UnitType::NoType { + if child_answer.is_unitless() { let result = sqrt(child_answer.value); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } else { Err("log() only accepts UnitType::NoType".to_string()) } } Log => { - if child_answer.unit.category() == UnitType::NoType { + if child_answer.is_unitless() { let result = child_answer.value.log10(); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } else { Err("log() only accepts UnitType::NoType".to_string()) } } Ln => { - if child_answer.unit.category() == UnitType::NoType { + if child_answer.is_unitless() { let result = child_answer.value.ln(); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } else { Err("ln() only accepts UnitType::NoType".to_string()) } } Exp => { - if child_answer.unit.category() == UnitType::NoType { + if child_answer.is_unitless() { let result = child_answer.value.exp(); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } else { Err("exp() only accepts UnitType::NoType".to_string()) } @@ -122,43 +116,43 @@ fn evaluate_node(ast_node: &AstNode) -> Result { .value .with_rounding_mode(RoundingMode::HalfUp) .round(0); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Ceil => { let result = child_answer.value.ceil(); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Floor => { let result = child_answer.value.floor(); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Abs => { let result = child_answer.value.abs(); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Sin => { let result = sin(child_answer.value); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Cos => { let result = cos(child_answer.value); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Tan => { let result = tan(child_answer.value); - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } } } Token::Unit(unit) => { let child_node = children.get(0).ok_or("Unit has no child[0]")?; let child_answer = evaluate_node(child_node)?; - Ok(Number::new(child_answer.value, *unit)) + Ok(Number::with_basic_unit(child_answer.value, *unit)) } Token::Negative => { let child_node = children.get(0).ok_or("Negative has no child[0]")?; let child_answer = evaluate_node(child_node)?; - Ok(Number::new(-child_answer.value, child_answer.unit)) + Ok(Number::with_unit(-child_answer.value, child_answer.unit)) } Token::Paren => { let child_node = children.get(0).ok_or("Paren has no child[0]")?; @@ -170,7 +164,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { .ok_or(format!("Token {:?} has no child[0]", token))?; let child_answer = evaluate_node(child_node)?; match operator { - Percent => Ok(Number::new( + Percent => Ok(Number::with_unit( child_answer.value / d!(100), child_answer.unit, )), @@ -181,7 +175,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { "Can only perform factorial on integers from 0 to 1000".to_string() ); } - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } } } @@ -198,7 +192,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { } let child_answer = evaluate_node(child_node)?; let result = child_answer.value * named_number_value; - Ok(Number::new(result, child_answer.unit)) + Ok(Number::with_unit(result, child_answer.unit)) } Token::TextOperator(operator) => { let left_child = children @@ -212,7 +206,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { To => { if let Token::Unit(right_unit) = right_child.token { let left = evaluate_node(left_child)?; - let result = convert(left, right_unit)?; + let result = convert(left, vec![(right_unit, 1)])?; Ok(result) } else { Err("Right side of To operator needs to be a unit".to_string()) @@ -221,8 +215,8 @@ fn evaluate_node(ast_node: &AstNode) -> Result { Of => { let left = evaluate_node(left_child)?; let right = evaluate_node(right_child)?; - if left.unit == Unit::NoUnit { - Ok(Number::new(left.value * right.value, right.unit)) + if left.is_unitless() { + Ok(Number::with_unit(left.value * right.value, right.unit)) } else { Err("Left side of the Of operator must be NoUnit".to_string()) } @@ -254,8 +248,8 @@ fn evaluate_node(ast_node: &AstNode) -> Result { #[cfg(test)] mod tests { - use crate::eval; use super::*; + use crate::eval; fn eval_default<'a>(input: &'a str) -> Number { let result = eval(input, true, false).unwrap(); @@ -273,31 +267,52 @@ mod tests { assert_eq!(eval_num("-2(-3)"), "6"); assert_eq!(eval_num("-2(3)"), "-6"); assert_eq!(eval_num("(3)-2"), "1"); - assert_eq!(eval_default("-1km to m"), Number::new(d!(-1000), Unit::Meter)); + assert_eq!( + eval_default("-1km to m"), + Number::new(d!(-1000), Unit::Meter) + ); assert_eq!(eval_num("2*-3*0.5"), "-3"); assert_eq!(eval_num("-3^2"), "-9"); assert_eq!(eval_num("e^2"), "7.3890560989306502272304274605750078132"); - assert_eq!(eval_num("e^2.5"), "12.1824939607034734380701759511679661832"); + assert_eq!( + eval_num("e^2.5"), + "12.1824939607034734380701759511679661832" + ); assert_eq!(eval_num("-1+2"), "1"); } #[test] fn test_functions() { assert_eq!(eval_num("cbrt(125)"), "5"); - assert_eq!(eval_num("cbrt(2)"), "1.25992104989487316476721060727822835057"); + assert_eq!( + eval_num("cbrt(2)"), + "1.25992104989487316476721060727822835057" + ); assert_eq!(eval_num("sqrt(25)"), "5"); - assert_eq!(eval_num("sqrt(2)"), "1.41421356237309504880168872420969807857"); + assert_eq!( + eval_num("sqrt(2)"), + "1.41421356237309504880168872420969807857" + ); assert_eq!(eval_num("log(100)"), "2"); - assert_eq!(eval_num("log(2)"), "0.301029995663981195213738894724493026768"); + assert_eq!( + eval_num("log(2)"), + "0.301029995663981195213738894724493026768" + ); assert_eq!(eval_num("ln(1)"), "0"); - assert_eq!(eval_num("ln(2)"), "0.69314718055994530941723212145817656808"); + assert_eq!( + eval_num("ln(2)"), + "0.69314718055994530941723212145817656808" + ); assert_eq!(eval_num("ln(e)"), "1"); assert_eq!(eval_num("ln(e^2)"), "2"); - assert_eq!(eval_num("exp(1)"), "2.71828182845904523536028747135266249776"); + assert_eq!( + eval_num("exp(1)"), + "2.71828182845904523536028747135266249776" + ); assert_eq!(eval_num("round(1.4)"), "1"); assert_eq!(eval_num("round(1.6)"), "2"); @@ -312,11 +327,26 @@ mod tests { assert_eq!(eval_num("abs(-3)"), "3"); - assert_eq!(eval_num("sin(2)"), "0.9092974268256816953960198659117448427"); - assert_eq!(eval_num("sin(-2)"), "-0.9092974268256816953960198659117448427"); + assert_eq!( + eval_num("sin(2)"), + "0.9092974268256816953960198659117448427" + ); + assert_eq!( + eval_num("sin(-2)"), + "-0.9092974268256816953960198659117448427" + ); - assert_eq!(eval_num("cos(2)"), "-0.41614683654714238699756822950076218977"); - assert_eq!(eval_num("cos(-2)"), "-0.41614683654714238699756822950076218977"); - assert_eq!(eval_num("tan(2)"), "-2.18503986326151899164330610231368254343"); + assert_eq!( + eval_num("cos(2)"), + "-0.41614683654714238699756822950076218977" + ); + assert_eq!( + eval_num("cos(-2)"), + "-0.41614683654714238699756822950076218977" + ); + assert_eq!( + eval_num("tan(2)"), + "-2.18503986326151899164330610231368254343" + ); } } diff --git a/src/lexer.rs b/src/lexer.rs index 97972e1..e83cd3d 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,32 +1,35 @@ -use std::iter::Peekable; -use fastnum::decimal::Context; -use fastnum::D128; -use crate::Token; +use crate::Constant::{E, Pi}; +use crate::FunctionIdentifier::{Abs, Cbrt, Ceil, Cos, Exp, Floor, Ln, Log, Round, Sin, Sqrt, Tan}; +use crate::LexerKeyword::{ + DoubleQuotes, Force, Hg, In, Mercury, Per, PercentChar, PoundForce, Revolution, +}; +use crate::NamedNumber::*; use crate::Operator::{Caret, Divide, LeftParen, Minus, Modulo, Multiply, Plus, RightParen}; -use crate::UnaryOperator::{Percent, Factorial}; use crate::TextOperator::{Of, To}; -use crate::NamedNumber::*; -use crate::Constant::{E, Pi}; -use crate::LexerKeyword::{In, PercentChar, Per, Mercury, Hg, PoundForce, Force, DoubleQuotes, Revolution}; -use crate::FunctionIdentifier::{Cbrt, Ceil, Cos, Exp, Abs, Floor, Ln, Log, Round, Sin, Sqrt, Tan}; +use crate::Token; +use crate::UnaryOperator::{Factorial, Percent}; use crate::units::Unit::*; +use fastnum::D128; +use fastnum::decimal::Context; +use std::iter::Peekable; use unicode_segmentation::{Graphemes, UnicodeSegmentation}; fn is_word_char_str(input: &str) -> bool { match input { - "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" - | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" - | "Y" | "Z" => true, - "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" - | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" - | "y" | "z" => true, + "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" + | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" => true, + "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" + | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" => true, "Ω" | "Ω" | "µ" | "μ" => true, _ => false, } } fn is_numeric_str(input: &str) -> bool { - matches!(input, "." | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9") + matches!( + input, + "." | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + ) } /// Read next characters as a word, otherwise return empty string. @@ -70,12 +73,12 @@ fn read_word(first_c: &str, lexer: &mut Lexer) -> String { "2" | "²" => { word += "2"; chars.next(); - }, + } "3" | "³" => { word += "3"; chars.next(); - }, - _ => {}, + } + _ => {} } } word @@ -84,10 +87,10 @@ fn read_word(first_c: &str, lexer: &mut Lexer) -> String { fn parse_token(c: &str, lexer: &mut Lexer) -> Result<(), String> { let tokens = &mut lexer.tokens; match c { - value if value.trim().is_empty() => {}, + value if value.trim().is_empty() => {} value if is_word_char_str(value) => { parse_word(read_word(c, lexer).as_str(), lexer)?; - }, + } value if is_numeric_str(value) => { let mut number_string = value.to_owned(); while let Some(number_char) = lexer.chars.peek() { @@ -101,12 +104,12 @@ fn parse_token(c: &str, lexer: &mut Lexer) -> Result<(), String> { match D128::from_str(&number_string, Context::default()) { Ok(number) => { tokens.push(Token::Number(number)); - }, + } Err(_e) => { return Err(format!("Error lexing d128 number: {}", number_string)); } }; - }, + } "+" => tokens.push(Token::Operator(Plus)), "-" => tokens.push(Token::Operator(Minus)), "*" => tokens.push(Token::Operator(Multiply)), @@ -117,17 +120,17 @@ fn parse_token(c: &str, lexer: &mut Lexer) -> Result<(), String> { "(" => { lexer.left_paren_count += 1; tokens.push(Token::Operator(LeftParen)); - }, + } ")" => { lexer.right_paren_count += 1; tokens.push(Token::Operator(RightParen)); - }, + } "π" => tokens.push(Token::Constant(Pi)), "'" => tokens.push(Token::Unit(Foot)), "\"" | "“" | "”" | "″" => tokens.push(Token::LexerKeyword(DoubleQuotes)), _ => { return Err(format!("Invalid character: {}", c)); - }, + } } Ok(()) } @@ -135,7 +138,7 @@ fn parse_token(c: &str, lexer: &mut Lexer) -> Result<(), String> { fn parse_word_if_non_empty(word: &str, lexer: &mut Lexer) -> Result<(), String> { match word { "" => Ok(()), - _ => parse_word(word, lexer) + _ => parse_word(word, lexer), } } @@ -175,17 +178,13 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "plus" => Token::Operator(Plus), "minus" => Token::Operator(Minus), "times" => Token::Operator(Multiply), - "multiplied" => { - match read_word("", lexer).as_str() { - "by" => Token::Operator(Multiply), - string => return Err(format!("Invalid string: {}", string)), - } + "multiplied" => match read_word("", lexer).as_str() { + "by" => Token::Operator(Multiply), + string => return Err(format!("Invalid string: {}", string)), }, - "divided" => { - match read_word("", lexer).as_str() { - "by" => Token::Operator(Divide), - string => return Err(format!("Invalid string: {}", string)), - } + "divided" => match read_word("", lexer).as_str() { + "by" => Token::Operator(Divide), + string => return Err(format!("Invalid string: {}", string)), }, "mod" => Token::Operator(Modulo), @@ -210,8 +209,12 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "ns" | "nanosec" | "nanosecs" | "nanosecond" | "nanoseconds" => Token::Unit(Nanosecond), // µ and μ are two different characters - "µs" | "μs" | "microsec" | "microsecs" | "microsecond" | "microseconds" => Token::Unit(Microsecond), - "ms" | "millisec" | "millisecs" | "millisecond" | "milliseconds" => Token::Unit(Millisecond), + "µs" | "μs" | "microsec" | "microsecs" | "microsecond" | "microseconds" => { + Token::Unit(Microsecond) + } + "ms" | "millisec" | "millisecs" | "millisecond" | "milliseconds" => { + Token::Unit(Millisecond) + } "s" | "sec" | "secs" | "second" | "seconds" => Token::Unit(Second), "min" | "mins" | "minute" | "minutes" => Token::Unit(Minute), "h" | "hr" | "hrs" | "hour" | "hours" => Token::Unit(Hour), @@ -224,8 +227,12 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "century" | "centuries" => Token::Unit(Century), "millenium" | "millenia" | "milleniums" => Token::Unit(Millennium), - "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => Token::Unit(Millimeter), - "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => Token::Unit(Centimeter), + "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => { + Token::Unit(Millimeter) + } + "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => { + Token::Unit(Centimeter) + } "dm" | "decimeter" | "decimeters" | "decimetre" | "decimetres" => Token::Unit(Decimeter), "m" | "meter" | "meters" | "metre" | "metres" => Token::Unit(Meter), "km" | "kilometer" | "kilometers" | "kilometre" | "kilometres" => Token::Unit(Kilometer), @@ -236,104 +243,126 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "mi" | "mile" | "miles" => Token::Unit(Mile), "marathon" | "marathons" => Token::Unit(Marathon), "nmi" => Token::Unit(NauticalMile), - "nautical" => { - match read_word("", lexer).as_str() { - "mile" | "miles" => Token::Unit(NauticalMile), - string => return Err(format!("Invalid string: {}", string)), - } + "nautical" => match read_word("", lexer).as_str() { + "mile" | "miles" => Token::Unit(NauticalMile), + string => return Err(format!("Invalid string: {}", string)), }, "ly" | "lightyear" | "lightyears" => Token::Unit(LightYear), "lightsec" | "lightsecs" | "lightsecond" | "lightseconds" => Token::Unit(LightSecond), - "light" => { - match read_word("", lexer).as_str() { - "yr" | "yrs" | "year" | "years" => Token::Unit(LightYear), - "sec" | "secs" | "second" | "seconds" => Token::Unit(LightSecond), - string => return Err(format!("Invalid string: {}", string)), - } - } + "light" => match read_word("", lexer).as_str() { + "yr" | "yrs" | "year" | "years" => Token::Unit(LightYear), + "sec" | "secs" | "second" | "seconds" => Token::Unit(LightSecond), + string => return Err(format!("Invalid string: {}", string)), + }, - "sqmm" | "mm2" | "millimeter2" | "millimeters2" | "millimetre2" | "millimetres2" => Token::Unit(SquareMillimeter), - "sqcm" | "cm2" | "centimeter2" | "centimeters2" | "centimetre2" | "centimetres2" => Token::Unit(SquareCentimeter), - "sqdm" | "dm2" | "decimeter2" | "decimeters2" | "decimetre2" | "decimetres2" => Token::Unit(SquareDecimeter), + "sqmm" | "mm2" | "millimeter2" | "millimeters2" | "millimetre2" | "millimetres2" => { + Token::Unit(SquareMillimeter) + } + "sqcm" | "cm2" | "centimeter2" | "centimeters2" | "centimetre2" | "centimetres2" => { + Token::Unit(SquareCentimeter) + } + "sqdm" | "dm2" | "decimeter2" | "decimeters2" | "decimetre2" | "decimetres2" => { + Token::Unit(SquareDecimeter) + } "sqm" | "m2" | "meter2" | "meters2" | "metre2" | "metres2" => Token::Unit(SquareMeter), - "sqkm" | "km2" | "kilometer2" | "kilometers2" | "kilometre2" | "kilometres2" => Token::Unit(SquareKilometer), + "sqkm" | "km2" | "kilometer2" | "kilometers2" | "kilometre2" | "kilometres2" => { + Token::Unit(SquareKilometer) + } "sqin" | "in2" | "inch2" | "inches2" => Token::Unit(SquareInch), "sqft" | "ft2" | "foot2" | "feet2" => Token::Unit(SquareFoot), "sqyd" | "yd2" | "yard2" | "yards2" => Token::Unit(SquareYard), "sqmi" | "mi2" | "mile2" | "miles2" => Token::Unit(SquareMile), - "sq" | "square" => { - match read_word("", lexer).as_str() { - "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => Token::Unit(SquareMillimeter), - "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => Token::Unit(SquareCentimeter), - "dm" | "decimeter" | "decimeters" | "decimetre" | "decimetres" => Token::Unit(SquareDecimeter), - "m" | "meter" | "meters" | "metre" | "metres" => Token::Unit(SquareMeter), - "km" | "kilometer" | "kilometers" | "kilometre" | "kilometres" => Token::Unit(SquareKilometer), - "in" | "inch" | "inches" => Token::Unit(SquareInch), - "ft" | "foot" | "feet" => Token::Unit(SquareFoot), - "yd" | "yard" | "yards" => Token::Unit(SquareYard), - "mi" | "mile" | "miles" => Token::Unit(SquareMile), - string => return Err(format!("Invalid string: {}", string)), + "sq" | "square" => match read_word("", lexer).as_str() { + "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => { + Token::Unit(SquareMillimeter) } - } + "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => { + Token::Unit(SquareCentimeter) + } + "dm" | "decimeter" | "decimeters" | "decimetre" | "decimetres" => { + Token::Unit(SquareDecimeter) + } + "m" | "meter" | "meters" | "metre" | "metres" => Token::Unit(SquareMeter), + "km" | "kilometer" | "kilometers" | "kilometre" | "kilometres" => { + Token::Unit(SquareKilometer) + } + "in" | "inch" | "inches" => Token::Unit(SquareInch), + "ft" | "foot" | "feet" => Token::Unit(SquareFoot), + "yd" | "yard" | "yards" => Token::Unit(SquareYard), + "mi" | "mile" | "miles" => Token::Unit(SquareMile), + string => return Err(format!("Invalid string: {}", string)), + }, "are" | "ares" => Token::Unit(Are), "decare" | "decares" => Token::Unit(Decare), "ha" | "hectare" | "hectares" => Token::Unit(Hectare), "acre" | "acres" => Token::Unit(Acre), - "mm3" | "millimeter3" | "millimeters3" | "millimetre3" | "millimetres3" => Token::Unit(CubicMillimeter), - "cm3" | "centimeter3" | "centimeters3" | "centimetre3" | "centimetres3" => Token::Unit(CubicCentimeter), - "dm3" | "decimeter3" | "decimeters3" | "decimetre3" | "decimetres3" => Token::Unit(CubicDecimeter), + "mm3" | "millimeter3" | "millimeters3" | "millimetre3" | "millimetres3" => { + Token::Unit(CubicMillimeter) + } + "cm3" | "centimeter3" | "centimeters3" | "centimetre3" | "centimetres3" => { + Token::Unit(CubicCentimeter) + } + "dm3" | "decimeter3" | "decimeters3" | "decimetre3" | "decimetres3" => { + Token::Unit(CubicDecimeter) + } "m3" | "meter3" | "meters3" | "metre3" | "metres3" => Token::Unit(CubicMeter), - "km3" | "kilometer3" | "kilometers3" | "kilometre3" | "kilometres3" => Token::Unit(CubicKilometer), + "km3" | "kilometer3" | "kilometers3" | "kilometre3" | "kilometres3" => { + Token::Unit(CubicKilometer) + } "inc3" | "inch3" | "inches3" => Token::Unit(CubicInch), "ft3" | "foot3" | "feet3" => Token::Unit(CubicFoot), "yd3" | "yard3" | "yards3" => Token::Unit(CubicYard), "mi3" | "mile3" | "miles3" => Token::Unit(CubicMile), - "cubic" => { - match read_word("", lexer).as_str() { - "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => Token::Unit(CubicMillimeter), - "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => Token::Unit(CubicCentimeter), - "dm" | "decimeter" | "decimeters" | "decimetre" | "decimetres" => Token::Unit(CubicDecimeter), - "m" | "meter" | "meters" | "metre" | "metres" => Token::Unit(CubicMeter), - "km" | "kilometer" | "kilometers" | "kilometre" | "kilometres" => Token::Unit(CubicKilometer), - "in" | "inch" | "inches" => Token::Unit(CubicInch), - "ft" | "foot" | "feet" => Token::Unit(CubicFoot), - "yd" | "yard" | "yards" => Token::Unit(CubicYard), - "mi" | "mile" | "miles" => Token::Unit(CubicMile), - string => return Err(format!("Invalid string: {}", string)), + "cubic" => match read_word("", lexer).as_str() { + "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => { + Token::Unit(CubicMillimeter) + } + "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => { + Token::Unit(CubicCentimeter) } + "dm" | "decimeter" | "decimeters" | "decimetre" | "decimetres" => { + Token::Unit(CubicDecimeter) + } + "m" | "meter" | "meters" | "metre" | "metres" => Token::Unit(CubicMeter), + "km" | "kilometer" | "kilometers" | "kilometre" | "kilometres" => { + Token::Unit(CubicKilometer) + } + "in" | "inch" | "inches" => Token::Unit(CubicInch), + "ft" | "foot" | "feet" => Token::Unit(CubicFoot), + "yd" | "yard" | "yards" => Token::Unit(CubicYard), + "mi" | "mile" | "miles" => Token::Unit(CubicMile), + string => return Err(format!("Invalid string: {}", string)), }, - "ml" | "milliliter" | "milliliters" | "millilitre" | "millilitres" => Token::Unit(Milliliter), - "cl" | "centiliter" | "centiliters" | "centilitre" | "centilitres" => Token::Unit(Centiliter), + "ml" | "milliliter" | "milliliters" | "millilitre" | "millilitres" => { + Token::Unit(Milliliter) + } + "cl" | "centiliter" | "centiliters" | "centilitre" | "centilitres" => { + Token::Unit(Centiliter) + } "dl" | "deciliter" | "deciliters" | "decilitre" | "decilitres" => Token::Unit(Deciliter), "l" | "liter" | "liters" | "litre" | "litres" => Token::Unit(Liter), "ts" | "tsp" | "tspn" | "tspns" | "teaspoon" | "teaspoons" => Token::Unit(Teaspoon), "tbs" | "tbsp" | "tablespoon" | "tablespoons" => Token::Unit(Tablespoon), "floz" => Token::Unit(FluidOunce), - "fl" | "fluid" => { - match read_word("", lexer).as_str() { - "oz" | "ounce" | "ounces" => Token::Unit(FluidOunce), - string => return Err(format!("Invalid string: {}", string)), - } + "fl" | "fluid" => match read_word("", lexer).as_str() { + "oz" | "ounce" | "ounces" => Token::Unit(FluidOunce), + string => return Err(format!("Invalid string: {}", string)), }, "cup" | "cups" => Token::Unit(Cup), "pt" | "pint" | "pints" => Token::Unit(Pint), "qt" | "quart" | "quarts" => Token::Unit(Quart), "gal" | "gallon" | "gallons" => Token::Unit(Gallon), "bbl" => Token::Unit(OilBarrel), - "oil" => { - match read_word("", lexer).as_str() { - "barrel" | "barrels" => Token::Unit(OilBarrel), - string => return Err(format!("Invalid string: {}", string)), - } + "oil" => match read_word("", lexer).as_str() { + "barrel" | "barrels" => Token::Unit(OilBarrel), + string => return Err(format!("Invalid string: {}", string)), }, - "metric" => { - match read_word("", lexer).as_str() { - "ton" | "tons" | "tonne" | "tonnes" => Token::Unit(MetricTon), - "hp" | "hps" | "horsepower" | "horsepowers" => Token::Unit(MetricHorsepower), - string => return Err(format!("Invalid string: {}", string)), - } + "metric" => match read_word("", lexer).as_str() { + "ton" | "tons" | "tonne" | "tonnes" => Token::Unit(MetricTon), + "hp" | "hps" | "horsepower" | "horsepowers" => Token::Unit(MetricHorsepower), + string => return Err(format!("Invalid string: {}", string)), }, "mg" | "milligram" | "milligrams" => Token::Unit(Milligram), @@ -343,44 +372,36 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "t" | "tonne" | "tonnes" => Token::Unit(MetricTon), "oz" | "ounces" => Token::Unit(Ounce), "lb" | "lbs" => Token::Unit(Pound), - "pound" | "pounds" => { - match lexer.chars.next() { - Some("-") => { - match read_word_plain(&mut lexer.chars).as_str() { - "force" => Token::LexerKeyword(PoundForce), - other => { - lexer.tokens.push(Token::Unit(Pound)); - lexer.tokens.push(Token::Operator(Minus)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - } - } - }, - Some(c) => { - lexer.tokens.push(Token::Unit(Pound)); - parse_token(c, lexer)?; - return Ok(()); - }, - None => { + "pound" | "pounds" => match lexer.chars.next() { + Some("-") => match read_word_plain(&mut lexer.chars).as_str() { + "force" => Token::LexerKeyword(PoundForce), + other => { lexer.tokens.push(Token::Unit(Pound)); + lexer.tokens.push(Token::Operator(Minus)); + parse_word_if_non_empty(other, lexer)?; return Ok(()); - }, + } + }, + Some(c) => { + lexer.tokens.push(Token::Unit(Pound)); + parse_token(c, lexer)?; + return Ok(()); + } + None => { + lexer.tokens.push(Token::Unit(Pound)); + return Ok(()); } }, "stone" | "stones" => Token::Unit(Stone), "st" | "ton" | "tons" => Token::Unit(ShortTon), - "short" => { - match read_word("", lexer).as_str() { - "ton" | "tons" | "tonne" | "tonnes" => Token::Unit(ShortTon), - string => return Err(format!("Invalid string: {}", string)), - } + "short" => match read_word("", lexer).as_str() { + "ton" | "tons" | "tonne" | "tonnes" => Token::Unit(ShortTon), + string => return Err(format!("Invalid string: {}", string)), }, "lt" => Token::Unit(LongTon), - "long" => { - match read_word("", lexer).as_str() { - "ton" | "tons" | "tonne" | "tonnes" => Token::Unit(LongTon), - string => return Err(format!("Invalid string: {}", string)), - } + "long" => match read_word("", lexer).as_str() { + "ton" | "tons" | "tonne" | "tonnes" => Token::Unit(LongTon), + string => return Err(format!("Invalid string: {}", string)), }, "bit" | "bits" => Token::Unit(Bit), @@ -453,24 +474,18 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "qflops" | "quettaflops" => Token::Unit(QuettaFlopPerSecond), "millijoule" | "millijoules" => Token::Unit(Millijoule), - "j"| "joule" | "joules" => Token::Unit(Joule), + "j" | "joule" | "joules" => Token::Unit(Joule), "nm" => Token::Unit(NewtonMeter), - "newton" => { - match lexer.chars.next() { - Some("-") => { - match read_word_plain(&mut lexer.chars).as_str() { - "meter" | "meters" | "metre" | "metres" => Token::Unit(NewtonMeter), - string => return Err(format!("Invalid string: {}", string)), - } - }, - Some(c) => { - match read_word(c, lexer).as_str() { - "meter" | "meters" | "metre" | "metres" => Token::Unit(NewtonMeter), - string => return Err(format!("Invalid string: {}", string)), - } - }, - None => return Err(format!("Invalid string: {}", word)), - } + "newton" => match lexer.chars.next() { + Some("-") => match read_word_plain(&mut lexer.chars).as_str() { + "meter" | "meters" | "metre" | "metres" => Token::Unit(NewtonMeter), + string => return Err(format!("Invalid string: {}", string)), + }, + Some(c) => match read_word(c, lexer).as_str() { + "meter" | "meters" | "metre" | "metres" => Token::Unit(NewtonMeter), + string => return Err(format!("Invalid string: {}", string)), + }, + None => return Err(format!("Invalid string: {}", word)), }, "kj" | "kilojoule" | "kilojoules" => Token::Unit(Kilojoule), "mj" | "megajoule" | "megajoules" => Token::Unit(Megajoule), @@ -479,16 +494,12 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "cal" | "calorie" | "calories" => Token::Unit(Calorie), "kcal" | "kilocalorie" | "kilocalories" => Token::Unit(KiloCalorie), "btu" => Token::Unit(BritishThermalUnit), - "british" => { - match read_word("", lexer).as_str() { - "thermal" => { - match read_word("", lexer).as_str() { - "unit" | "units" => Token::Unit(BritishThermalUnit), - string => return Err(format!("Invalid string: {}", string)), - } - }, + "british" => match read_word("", lexer).as_str() { + "thermal" => match read_word("", lexer).as_str() { + "unit" | "units" => Token::Unit(BritishThermalUnit), string => return Err(format!("Invalid string: {}", string)), - } + }, + string => return Err(format!("Invalid string: {}", string)), }, "wh" => Token::Unit(WattHour), "kwh" => Token::Unit(KilowattHour), @@ -507,68 +518,58 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> { "hp" | "hps" | "horsepower" | "horsepowers" => Token::Unit(Horsepower), "mhp" | "hpm" => Token::Unit(MetricHorsepower), - "watt" => { - match read_word("", lexer).as_str() { - "hr" | "hrs" | "hour" | "hours" => Token::Unit(WattHour), - other => { - lexer.tokens.push(Token::Unit(Watt)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - }, + "watt" => match read_word("", lexer).as_str() { + "hr" | "hrs" | "hour" | "hours" => Token::Unit(WattHour), + other => { + lexer.tokens.push(Token::Unit(Watt)); + parse_word_if_non_empty(other, lexer)?; + return Ok(()); } - } - "kilowatt" => { - match read_word("", lexer).as_str() { - "hr" | "hrs" | "hour" | "hours" => Token::Unit(KilowattHour), - other => { - lexer.tokens.push(Token::Unit(Kilowatt)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - }, + }, + "kilowatt" => match read_word("", lexer).as_str() { + "hr" | "hrs" | "hour" | "hours" => Token::Unit(KilowattHour), + other => { + lexer.tokens.push(Token::Unit(Kilowatt)); + parse_word_if_non_empty(other, lexer)?; + return Ok(()); } - } - "megawatt" => { - match read_word("", lexer).as_str() { - "hr" | "hrs" | "hour" | "hours" => Token::Unit(MegawattHour), - other => { - lexer.tokens.push(Token::Unit(Megawatt)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - }, + }, + "megawatt" => match read_word("", lexer).as_str() { + "hr" | "hrs" | "hour" | "hours" => Token::Unit(MegawattHour), + other => { + lexer.tokens.push(Token::Unit(Megawatt)); + parse_word_if_non_empty(other, lexer)?; + return Ok(()); } - } - "gigawatt" => { - match read_word("", lexer).as_str() { - "hr" | "hrs" | "hour" | "hours" => Token::Unit(GigawattHour), - other => { - lexer.tokens.push(Token::Unit(Gigawatt)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - }, + }, + "gigawatt" => match read_word("", lexer).as_str() { + "hr" | "hrs" | "hour" | "hours" => Token::Unit(GigawattHour), + other => { + lexer.tokens.push(Token::Unit(Gigawatt)); + parse_word_if_non_empty(other, lexer)?; + return Ok(()); } - } - "terawatt" => { - match read_word("", lexer).as_str() { - "hr" | "hrs" | "hour" | "hours" => Token::Unit(TerawattHour), - other => { - lexer.tokens.push(Token::Unit(Terawatt)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - }, + }, + "terawatt" => match read_word("", lexer).as_str() { + "hr" | "hrs" | "hour" | "hours" => Token::Unit(TerawattHour), + other => { + lexer.tokens.push(Token::Unit(Terawatt)); + parse_word_if_non_empty(other, lexer)?; + return Ok(()); } - } - "petawatt" => { - match read_word("", lexer).as_str() { - "hr" | "hrs" | "hour" | "hours" => Token::Unit(PetawattHour), - other => { - lexer.tokens.push(Token::Unit(Petawatt)); - parse_word_if_non_empty(other, lexer)?; - return Ok(()); - }, + }, + "petawatt" => match read_word("", lexer).as_str() { + "hr" | "hrs" | "hour" | "hours" => Token::Unit(PetawattHour), + other => { + lexer.tokens.push(Token::Unit(Petawatt)); + parse_word_if_non_empty(other, lexer)?; + return Ok(()); } - } + }, - "ma" | "milliamp" | "milliamps" | "milliampere" | "milliamperes" => Token::Unit(Milliampere), + "ma" | "milliamp" | "milliamps" | "milliampere" | "milliamperes" => { + Token::Unit(Milliampere) + } "a" | "amp" | "amps" | "ampere" | "amperes" => Token::Unit(Ampere), "ka" | "kiloamp" | "kiloamps" | "kiloampere" | "kiloamperes" => Token::Unit(Kiloampere), "bi" | "biot" | "biots" | "aba" | "abampere" | "abamperes" => Token::Unit(Abampere), @@ -637,8 +638,8 @@ pub fn lex(input: &str, remove_trailing_operator: bool) -> Result, St match &input.chars().last().unwrap_or('x') { '+' | '-' | '*' | '/' | '^' | '(' => { input.pop(); - }, - _ => {}, + } + _ => {} } } @@ -679,38 +680,38 @@ pub fn lex(input: &str, remove_trailing_operator: bool) -> Result, St Some(Token::TextOperator(Of)) => { // "10% of 1km" should be percentage tokens[token_index] = Token::UnaryOperator(Percent); - }, + } Some(Token::Operator(operator)) => { match operator { LeftParen => { // "10%(2)" should be modulo tokens[token_index] = Token::Operator(Modulo); - }, + } _ => { // "10%*2" should be a percentage tokens[token_index] = Token::UnaryOperator(Percent); } } - }, + } Some(Token::UnaryOperator(_)) => { // "10%!" should be a percentage tokens[token_index] = Token::UnaryOperator(Percent); - }, + } Some(Token::LexerKeyword(PercentChar)) => { // "10%%" should be a percentage tokens[token_index] = Token::UnaryOperator(Percent); - }, + } None => { // percent if there's no element afterwards tokens[token_index] = Token::UnaryOperator(Percent); - }, + } _ => { // everything else should be modulo, for example if the % is // before a number, function or constants tokens[token_index] = Token::Operator(Modulo); - }, + } } - }, + } // decide if " is 'inch' or 'inch of mercury' Token::LexerKeyword(DoubleQuotes) => { match tokens.get(token_index + 1) { @@ -718,270 +719,282 @@ pub fn lex(input: &str, remove_trailing_operator: bool) -> Result, St // "hg should be inch of mercury tokens[token_index] = Token::Unit(InchOfMercury); tokens.remove(token_index + 1); - }, + } _ => { // otherwise, Inch tokens[token_index] = Token::Unit(Inch); - }, + } } - }, + } // if hg wasn't already turned into inch of mercury, it's hectogram Token::LexerKeyword(Hg) => { tokens[token_index] = Token::Unit(Hectogram); - }, + } // decide if "in" is Inch or To Token::LexerKeyword(In) => { match tokens.get(token_index + 1) { Some(Token::Unit(_)) => { // "in" should be To tokens[token_index] = Token::TextOperator(To); - }, + } _ => { // otherwise, Inch tokens[token_index] = Token::Unit(Inch); - }, + } } - }, - _ => {}, + } + _ => {} } // parse units like km/h, lbf per square inch if token_index >= 2 { - let token1 = &tokens[token_index-2]; - let token2 = match &tokens[token_index-1] { + let token1 = &tokens[token_index - 2]; + let token2 = match &tokens[token_index - 1] { // treat km/h the same as km per h Token::Operator(Divide) => &Token::LexerKeyword(Per), - _ => &tokens[token_index-1], + _ => &tokens[token_index - 1], }; let token3 = &tokens[token_index]; let mut replaced = true; match (token1, token2, token3) { // km/h (Token::Unit(Kilometer), Token::LexerKeyword(Per), Token::Unit(Hour)) => { - tokens[token_index-2] = Token::Unit(KilometersPerHour); - }, + tokens[token_index - 2] = Token::Unit(KilometersPerHour); + } // mi/h (Token::Unit(Mile), Token::LexerKeyword(Per), Token::Unit(Hour)) => { - tokens[token_index-2] = Token::Unit(MilesPerHour); - }, + tokens[token_index - 2] = Token::Unit(MilesPerHour); + } // m/s (Token::Unit(Meter), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(MetersPerSecond); - }, + tokens[token_index - 2] = Token::Unit(MetersPerSecond); + } // ft/s (Token::Unit(Foot), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(FeetPerSecond); - }, + tokens[token_index - 2] = Token::Unit(FeetPerSecond); + } // bits per second (Token::Unit(Bit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(BitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(BitsPerSecond); + } // kilobits per second (Token::Unit(Kilobit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(KilobitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(KilobitsPerSecond); + } // megabits per second (Token::Unit(Megabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(MegabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(MegabitsPerSecond); + } // gigabits per second (Token::Unit(Gigabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(GigabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(GigabitsPerSecond); + } // terabits per second (Token::Unit(Terabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(TerabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(TerabitsPerSecond); + } // petabits per second (Token::Unit(Petabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(PetabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(PetabitsPerSecond); + } // exabits per second (Token::Unit(Exabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ExabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ExabitsPerSecond); + } // zettabits per second (Token::Unit(Zettabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ZettabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ZettabitsPerSecond); + } // yottabits per second (Token::Unit(Yottabit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(YottabitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(YottabitsPerSecond); + } // kibibits per second (Token::Unit(Kibibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(KibibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(KibibitsPerSecond); + } // mebibits per second (Token::Unit(Mebibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(MebibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(MebibitsPerSecond); + } // gibibits per second (Token::Unit(Gibibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(GibibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(GibibitsPerSecond); + } // tebibits per second (Token::Unit(Tebibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(TebibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(TebibitsPerSecond); + } // pebibits per second (Token::Unit(Pebibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(PebibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(PebibitsPerSecond); + } // exbibits per second (Token::Unit(Exbibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ExbibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ExbibitsPerSecond); + } // zebibits per second (Token::Unit(Zebibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ZebibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ZebibitsPerSecond); + } // yobibits per second (Token::Unit(Yobibit), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(YobibitsPerSecond); - }, + tokens[token_index - 2] = Token::Unit(YobibitsPerSecond); + } // bytes per second (Token::Unit(Byte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(BytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(BytesPerSecond); + } // kilobytes per second (Token::Unit(Kilobyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(KilobytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(KilobytesPerSecond); + } // megabytes per second (Token::Unit(Megabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(MegabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(MegabytesPerSecond); + } // gigabytes per second (Token::Unit(Gigabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(GigabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(GigabytesPerSecond); + } // terabytes per second (Token::Unit(Terabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(TerabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(TerabytesPerSecond); + } // petabytes per second (Token::Unit(Petabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(PetabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(PetabytesPerSecond); + } // exabytes per second (Token::Unit(Exabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ExabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ExabytesPerSecond); + } // zettabytes per second (Token::Unit(Zettabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ZettabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ZettabytesPerSecond); + } // yottabytes per second (Token::Unit(Yottabyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(YottabytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(YottabytesPerSecond); + } // kibibytes per second (Token::Unit(Kibibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(KibibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(KibibytesPerSecond); + } // mebibytes per second (Token::Unit(Mebibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(MebibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(MebibytesPerSecond); + } // gibibytes per second (Token::Unit(Gibibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(GibibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(GibibytesPerSecond); + } // tebibytes per second (Token::Unit(Tebibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(TebibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(TebibytesPerSecond); + } // pebibytes per second (Token::Unit(Pebibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(PebibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(PebibytesPerSecond); + } // exbibytes per second (Token::Unit(Exbibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ExbibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ExbibytesPerSecond); + } // zebibytes per second (Token::Unit(Zebibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ZebibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ZebibytesPerSecond); + } // yobibytes per second (Token::Unit(Yobibyte), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(YobibytesPerSecond); - }, + tokens[token_index - 2] = Token::Unit(YobibytesPerSecond); + } // FLOP per second (Token::Unit(Flop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(FlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(FlopPerSecond); + } // kiloFLOP per second (Token::Unit(KiloFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(KiloFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(KiloFlopPerSecond); + } // megaFLOP per second (Token::Unit(MegaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(MegaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(MegaFlopPerSecond); + } // gigaFLOP per second (Token::Unit(GigaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(GigaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(GigaFlopPerSecond); + } // teraFLOP per second (Token::Unit(TeraFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(TeraFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(TeraFlopPerSecond); + } // petaFLOP per second (Token::Unit(PetaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(PetaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(PetaFlopPerSecond); + } // exaFLOP per second (Token::Unit(ExaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ExaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ExaFlopPerSecond); + } // zettaFLOP per second (Token::Unit(ZettaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(ZettaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(ZettaFlopPerSecond); + } // yottaFLOP per second (Token::Unit(YottaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(YottaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(YottaFlopPerSecond); + } // ronnaFLOP per second (Token::Unit(RonnaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(RonnaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(RonnaFlopPerSecond); + } // quettaFLOP per second (Token::Unit(QuettaFlop), Token::LexerKeyword(Per), Token::Unit(Second)) => { - tokens[token_index-2] = Token::Unit(QuettaFlopPerSecond); - }, + tokens[token_index - 2] = Token::Unit(QuettaFlopPerSecond); + } // btu/min - (Token::Unit(BritishThermalUnit), Token::LexerKeyword(Per), Token::Unit(Minute)) => { - tokens[token_index-2] = Token::Unit(BritishThermalUnitsPerMinute); - }, + ( + Token::Unit(BritishThermalUnit), + Token::LexerKeyword(Per), + Token::Unit(Minute), + ) => { + tokens[token_index - 2] = Token::Unit(BritishThermalUnitsPerMinute); + } // btu/h (Token::Unit(BritishThermalUnit), Token::LexerKeyword(Per), Token::Unit(Hour)) => { - tokens[token_index-2] = Token::Unit(BritishThermalUnitsPerHour); - }, + tokens[token_index - 2] = Token::Unit(BritishThermalUnitsPerHour); + } // lbs/sqin - (Token::LexerKeyword(PoundForce), Token::LexerKeyword(Per), Token::Unit(SquareInch)) => { - tokens[token_index-2] = Token::Unit(PoundsPerSquareInch); - }, + ( + Token::LexerKeyword(PoundForce), + Token::LexerKeyword(Per), + Token::Unit(SquareInch), + ) => { + tokens[token_index - 2] = Token::Unit(PoundsPerSquareInch); + } // inch of mercury (Token::Unit(Inch), Token::TextOperator(Of), Token::LexerKeyword(Mercury)) => { - tokens[token_index-2] = Token::Unit(InchOfMercury); - }, + tokens[token_index - 2] = Token::Unit(InchOfMercury); + } // revolutions per minute - (Token::LexerKeyword(Revolution), Token::LexerKeyword(Per), Token::Unit(Minute)) => { - tokens[token_index-2] = Token::Unit(RevolutionsPerMinute); - }, + ( + Token::LexerKeyword(Revolution), + Token::LexerKeyword(Per), + Token::Unit(Minute), + ) => { + tokens[token_index - 2] = Token::Unit(RevolutionsPerMinute); + } _ => { replaced = false; - }, + } } if replaced { tokens.remove(token_index); - tokens.remove(token_index-1); + tokens.remove(token_index - 1); token_index -= 2; } } - if token_index == tokens.len()-1 { + if token_index == tokens.len() - 1 { break; } else { token_index += 1; @@ -1010,7 +1023,10 @@ mod tests { panic!("lex error: {}\nrun_lex input: {}", e, input); } }; - let info_msg = format!("run_lex input: {}\nexpected: {:?}\nreceived: {:?}", input, expected_tokens, tokens); + let info_msg = format!( + "run_lex input: {}\nexpected: {:?}\nreceived: {:?}", + input, expected_tokens, tokens + ); assert!(tokens == expected_tokens, "{info_msg}"); // Prove we can handle multiple spaces wherever we handle a single space @@ -1024,9 +1040,14 @@ mod tests { assert!(tokens_stripped_spaces == expected_tokens, "{info_msg}"); // Prove we don't need a space after a digit - let input_afterdigit_stripped_spaces = strip_afterdigit_spacing.replace_all(input, "$1"); - let tokens_afterdigit_stripped_spaces = lex(&input_afterdigit_stripped_spaces, false).unwrap(); - assert!(tokens_afterdigit_stripped_spaces == expected_tokens, "{info_msg}"); + let input_afterdigit_stripped_spaces = + strip_afterdigit_spacing.replace_all(input, "$1"); + let tokens_afterdigit_stripped_spaces = + lex(&input_afterdigit_stripped_spaces, false).unwrap(); + assert!( + tokens_afterdigit_stripped_spaces == expected_tokens, + "{info_msg}" + ); }; let run_datarate_lex = |input: &str, expected_tokens: Vec| { @@ -1035,27 +1056,71 @@ mod tests { // Prove plural and non-plural data units behave identically let input_nonplural_units = nonplural_data_units.replace_all(input, "$1"); let tokens_nonplural_units = lex(&input_nonplural_units, false).unwrap(); - let info_msg = format!("run_datarate_lex input: {}\nexpected: {:?}\nreceived: {:?}", input, expected_tokens, tokens_nonplural_units); + let info_msg = format!( + "run_datarate_lex input: {}\nexpected: {:?}\nreceived: {:?}", + input, expected_tokens, tokens_nonplural_units + ); assert!(tokens_nonplural_units == expected_tokens, "{info_msg}"); }; - run_lex("88 kilometres * 2", vec![numtok!(88), Token::Unit(Kilometer), Token::Operator(Multiply), numtok!(2)]); + run_lex( + "88 kilometres * 2", + vec![ + numtok!(88), + Token::Unit(Kilometer), + Token::Operator(Multiply), + numtok!(2), + ], + ); run_lex("0.5 marathon", vec![numtok!(0.5), Token::Unit(Marathon)]); run_lex("100 nmi", vec![numtok!(100), Token::Unit(NauticalMile)]); - run_lex("101 nautical miles", vec![numtok!(101), Token::Unit(NauticalMile)]); + run_lex( + "101 nautical miles", + vec![numtok!(101), Token::Unit(NauticalMile)], + ); run_lex("2 lightyears", vec![numtok!(2), Token::Unit(LightYear)]); run_lex("1 light year", vec![numtok!(1), Token::Unit(LightYear)]); run_lex("10 lightsec", vec![numtok!(10), Token::Unit(LightSecond)]); run_lex("12 light secs", vec![numtok!(12), Token::Unit(LightSecond)]); - run_lex("33.3 square meters", vec![numtok!(33.3), Token::Unit(SquareMeter)]); + run_lex( + "33.3 square meters", + vec![numtok!(33.3), Token::Unit(SquareMeter)], + ); run_lex("54 m2", vec![numtok!(54), Token::Unit(SquareMeter)]); run_lex("87 sq miles", vec![numtok!(87), Token::Unit(SquareMile)]); run_lex("500 feet2", vec![numtok!(500), Token::Unit(SquareFoot)]); run_lex("500 feet²", vec![numtok!(500), Token::Unit(SquareFoot)]); run_lex("4 cubic metres", vec![numtok!(4), Token::Unit(CubicMeter)]); - run_lex("34 cubic feet + 23 cubic yards", vec![numtok!(34), Token::Unit(CubicFoot), Token::Operator(Plus), numtok!(23), Token::Unit(CubicYard)]); - run_lex("66 inches3 + 65 millimetre³", vec![numtok!(66), Token::Unit(CubicInch), Token::Operator(Plus), numtok!(65), Token::Unit(CubicMillimeter)]); - run_lex("66 inches³ + 65 millimetre3", vec![numtok!(66), Token::Unit(CubicInch), Token::Operator(Plus), numtok!(65), Token::Unit(CubicMillimeter)]); + run_lex( + "34 cubic feet + 23 cubic yards", + vec![ + numtok!(34), + Token::Unit(CubicFoot), + Token::Operator(Plus), + numtok!(23), + Token::Unit(CubicYard), + ], + ); + run_lex( + "66 inches3 + 65 millimetre³", + vec![ + numtok!(66), + Token::Unit(CubicInch), + Token::Operator(Plus), + numtok!(65), + Token::Unit(CubicMillimeter), + ], + ); + run_lex( + "66 inches³ + 65 millimetre3", + vec![ + numtok!(66), + Token::Unit(CubicInch), + Token::Operator(Plus), + numtok!(65), + Token::Unit(CubicMillimeter), + ], + ); run_lex("42 millilitres", vec![numtok!(42), Token::Unit(Milliliter)]); run_lex("3 tbs", vec![numtok!(3), Token::Unit(Tablespoon)]); run_lex("6 floz", vec![numtok!(6), Token::Unit(FluidOunce)]); @@ -1069,7 +1134,10 @@ mod tests { run_lex("210 lbs", vec![numtok!(210), Token::Unit(Pound)]); run_lex("210 pound", vec![numtok!(210), Token::Unit(Pound)]); run_lex("210 pounds", vec![numtok!(210), Token::Unit(Pound)]); - run_lex("210 pounds-force", vec![numtok!(210), Token::LexerKeyword(PoundForce)]); + run_lex( + "210 pounds-force", + vec![numtok!(210), Token::LexerKeyword(PoundForce)], + ); run_lex("3 ton", vec![numtok!(3), Token::Unit(ShortTon)]); run_lex("3 short tons", vec![numtok!(3), Token::Unit(ShortTon)]); run_lex("4 lt", vec![numtok!(4), Token::Unit(LongTon)]); @@ -1100,8 +1168,14 @@ mod tests { run_datarate_lex("2.1 terabytes", vec![numtok!(2.1), Token::Unit(Terabyte)]); run_datarate_lex("1.08 petabytes", vec![numtok!(1.08), Token::Unit(Petabyte)]); run_datarate_lex("0.73 exabytes", vec![numtok!(0.73), Token::Unit(Exabyte)]); - run_datarate_lex("0.49 zettabytes", vec![numtok!(0.49), Token::Unit(Zettabyte)]); - run_datarate_lex("0.23 yottabytes", vec![numtok!(0.23), Token::Unit(Yottabyte)]); + run_datarate_lex( + "0.49 zettabytes", + vec![numtok!(0.49), Token::Unit(Zettabyte)], + ); + run_datarate_lex( + "0.23 yottabytes", + vec![numtok!(0.23), Token::Unit(Yottabyte)], + ); run_datarate_lex("63 kibibytes", vec![numtok!(63), Token::Unit(Kibibyte)]); run_datarate_lex("32 mebibytes", vec![numtok!(32), Token::Unit(Mebibyte)]); run_datarate_lex("3.5 gibibytes", vec![numtok!(3.5), Token::Unit(Gibibyte)]); @@ -1113,46 +1187,166 @@ mod tests { run_lex("432 bps", vec![numtok!(432), Token::Unit(BitsPerSecond)]); run_lex("56 kbps", vec![numtok!(56), Token::Unit(KilobitsPerSecond)]); run_lex("12 mbps", vec![numtok!(12), Token::Unit(MegabitsPerSecond)]); - run_lex("4.2 gbps", vec![numtok!(4.2), Token::Unit(GigabitsPerSecond)]); - run_lex("2.2 tbps", vec![numtok!(2.2), Token::Unit(TerabitsPerSecond)]); - run_lex("1.7 pbps", vec![numtok!(1.7), Token::Unit(PetabitsPerSecond)]); - run_lex("0.99 ebps", vec![numtok!(0.99), Token::Unit(ExabitsPerSecond)]); - run_lex("0.64 zbps", vec![numtok!(0.64), Token::Unit(ZettabitsPerSecond)]); - run_lex("0.278 ybps", vec![numtok!(0.278), Token::Unit(YottabitsPerSecond)]); - run_datarate_lex("4 bits per second", vec![numtok!(4), Token::Unit(BitsPerSecond)]); - run_datarate_lex("5 kilobits per second", vec![numtok!(5), Token::Unit(KilobitsPerSecond)]); - run_datarate_lex("6 megabits per second", vec![numtok!(6), Token::Unit(MegabitsPerSecond)]); - run_datarate_lex("7 gigabits per second", vec![numtok!(7), Token::Unit(GigabitsPerSecond)]); - run_datarate_lex("8 terabits per second", vec![numtok!(8), Token::Unit(TerabitsPerSecond)]); - run_datarate_lex("9 petabits per second", vec![numtok!(9), Token::Unit(PetabitsPerSecond)]); - run_datarate_lex("10 exabits per second", vec![numtok!(10), Token::Unit(ExabitsPerSecond)]); - run_datarate_lex("11 zettabits per second", vec![numtok!(11), Token::Unit(ZettabitsPerSecond)]); - run_datarate_lex("12 yottabits per second", vec![numtok!(12), Token::Unit(YottabitsPerSecond)]); - run_datarate_lex("13 kibibits per second", vec![numtok!(13), Token::Unit(KibibitsPerSecond)]); - run_datarate_lex("14 mebibits per second", vec![numtok!(14), Token::Unit(MebibitsPerSecond)]); - run_datarate_lex("15 gibibits per second", vec![numtok!(15), Token::Unit(GibibitsPerSecond)]); - run_datarate_lex("16 tebibits per second", vec![numtok!(16), Token::Unit(TebibitsPerSecond)]); - run_datarate_lex("17 pebibits per second", vec![numtok!(17), Token::Unit(PebibitsPerSecond)]); - run_datarate_lex("18 exbibits per second", vec![numtok!(18), Token::Unit(ExbibitsPerSecond)]); - run_datarate_lex("19 zebibits per second", vec![numtok!(19), Token::Unit(ZebibitsPerSecond)]); - run_datarate_lex("20 yobibits per second", vec![numtok!(20), Token::Unit(YobibitsPerSecond)]); - run_datarate_lex("4 bytes per second", vec![numtok!(4), Token::Unit(BytesPerSecond)]); - run_datarate_lex("5 kilobytes per second", vec![numtok!(5), Token::Unit(KilobytesPerSecond)]); - run_datarate_lex("6 megabytes per second", vec![numtok!(6), Token::Unit(MegabytesPerSecond)]); - run_datarate_lex("7 gigabytes per second", vec![numtok!(7), Token::Unit(GigabytesPerSecond)]); - run_datarate_lex("8 terabytes per second", vec![numtok!(8), Token::Unit(TerabytesPerSecond)]); - run_datarate_lex("9 petabytes per second", vec![numtok!(9), Token::Unit(PetabytesPerSecond)]); - run_datarate_lex("10 exabytes per second", vec![numtok!(10), Token::Unit(ExabytesPerSecond)]); - run_datarate_lex("11 zettabytes per second", vec![numtok!(11), Token::Unit(ZettabytesPerSecond)]); - run_datarate_lex("12 yottabytes per second", vec![numtok!(12), Token::Unit(YottabytesPerSecond)]); - run_datarate_lex("13 kibibytes per second", vec![numtok!(13), Token::Unit(KibibytesPerSecond)]); - run_datarate_lex("14 mebibytes per second", vec![numtok!(14), Token::Unit(MebibytesPerSecond)]); - run_datarate_lex("15 gibibytes per second", vec![numtok!(15), Token::Unit(GibibytesPerSecond)]); - run_datarate_lex("16 tebibytes per second", vec![numtok!(16), Token::Unit(TebibytesPerSecond)]); - run_datarate_lex("17 pebibytes per second", vec![numtok!(17), Token::Unit(PebibytesPerSecond)]); - run_datarate_lex("18 exbibytes per second", vec![numtok!(18), Token::Unit(ExbibytesPerSecond)]); - run_datarate_lex("19 zebibytes per second", vec![numtok!(19), Token::Unit(ZebibytesPerSecond)]); - run_datarate_lex("20 yobibytes per second", vec![numtok!(20), Token::Unit(YobibytesPerSecond)]); + run_lex( + "4.2 gbps", + vec![numtok!(4.2), Token::Unit(GigabitsPerSecond)], + ); + run_lex( + "2.2 tbps", + vec![numtok!(2.2), Token::Unit(TerabitsPerSecond)], + ); + run_lex( + "1.7 pbps", + vec![numtok!(1.7), Token::Unit(PetabitsPerSecond)], + ); + run_lex( + "0.99 ebps", + vec![numtok!(0.99), Token::Unit(ExabitsPerSecond)], + ); + run_lex( + "0.64 zbps", + vec![numtok!(0.64), Token::Unit(ZettabitsPerSecond)], + ); + run_lex( + "0.278 ybps", + vec![numtok!(0.278), Token::Unit(YottabitsPerSecond)], + ); + run_datarate_lex( + "4 bits per second", + vec![numtok!(4), Token::Unit(BitsPerSecond)], + ); + run_datarate_lex( + "5 kilobits per second", + vec![numtok!(5), Token::Unit(KilobitsPerSecond)], + ); + run_datarate_lex( + "6 megabits per second", + vec![numtok!(6), Token::Unit(MegabitsPerSecond)], + ); + run_datarate_lex( + "7 gigabits per second", + vec![numtok!(7), Token::Unit(GigabitsPerSecond)], + ); + run_datarate_lex( + "8 terabits per second", + vec![numtok!(8), Token::Unit(TerabitsPerSecond)], + ); + run_datarate_lex( + "9 petabits per second", + vec![numtok!(9), Token::Unit(PetabitsPerSecond)], + ); + run_datarate_lex( + "10 exabits per second", + vec![numtok!(10), Token::Unit(ExabitsPerSecond)], + ); + run_datarate_lex( + "11 zettabits per second", + vec![numtok!(11), Token::Unit(ZettabitsPerSecond)], + ); + run_datarate_lex( + "12 yottabits per second", + vec![numtok!(12), Token::Unit(YottabitsPerSecond)], + ); + run_datarate_lex( + "13 kibibits per second", + vec![numtok!(13), Token::Unit(KibibitsPerSecond)], + ); + run_datarate_lex( + "14 mebibits per second", + vec![numtok!(14), Token::Unit(MebibitsPerSecond)], + ); + run_datarate_lex( + "15 gibibits per second", + vec![numtok!(15), Token::Unit(GibibitsPerSecond)], + ); + run_datarate_lex( + "16 tebibits per second", + vec![numtok!(16), Token::Unit(TebibitsPerSecond)], + ); + run_datarate_lex( + "17 pebibits per second", + vec![numtok!(17), Token::Unit(PebibitsPerSecond)], + ); + run_datarate_lex( + "18 exbibits per second", + vec![numtok!(18), Token::Unit(ExbibitsPerSecond)], + ); + run_datarate_lex( + "19 zebibits per second", + vec![numtok!(19), Token::Unit(ZebibitsPerSecond)], + ); + run_datarate_lex( + "20 yobibits per second", + vec![numtok!(20), Token::Unit(YobibitsPerSecond)], + ); + run_datarate_lex( + "4 bytes per second", + vec![numtok!(4), Token::Unit(BytesPerSecond)], + ); + run_datarate_lex( + "5 kilobytes per second", + vec![numtok!(5), Token::Unit(KilobytesPerSecond)], + ); + run_datarate_lex( + "6 megabytes per second", + vec![numtok!(6), Token::Unit(MegabytesPerSecond)], + ); + run_datarate_lex( + "7 gigabytes per second", + vec![numtok!(7), Token::Unit(GigabytesPerSecond)], + ); + run_datarate_lex( + "8 terabytes per second", + vec![numtok!(8), Token::Unit(TerabytesPerSecond)], + ); + run_datarate_lex( + "9 petabytes per second", + vec![numtok!(9), Token::Unit(PetabytesPerSecond)], + ); + run_datarate_lex( + "10 exabytes per second", + vec![numtok!(10), Token::Unit(ExabytesPerSecond)], + ); + run_datarate_lex( + "11 zettabytes per second", + vec![numtok!(11), Token::Unit(ZettabytesPerSecond)], + ); + run_datarate_lex( + "12 yottabytes per second", + vec![numtok!(12), Token::Unit(YottabytesPerSecond)], + ); + run_datarate_lex( + "13 kibibytes per second", + vec![numtok!(13), Token::Unit(KibibytesPerSecond)], + ); + run_datarate_lex( + "14 mebibytes per second", + vec![numtok!(14), Token::Unit(MebibytesPerSecond)], + ); + run_datarate_lex( + "15 gibibytes per second", + vec![numtok!(15), Token::Unit(GibibytesPerSecond)], + ); + run_datarate_lex( + "16 tebibytes per second", + vec![numtok!(16), Token::Unit(TebibytesPerSecond)], + ); + run_datarate_lex( + "17 pebibytes per second", + vec![numtok!(17), Token::Unit(PebibytesPerSecond)], + ); + run_datarate_lex( + "18 exbibytes per second", + vec![numtok!(18), Token::Unit(ExbibytesPerSecond)], + ); + run_datarate_lex( + "19 zebibytes per second", + vec![numtok!(19), Token::Unit(ZebibytesPerSecond)], + ); + run_datarate_lex( + "20 yobibytes per second", + vec![numtok!(20), Token::Unit(YobibytesPerSecond)], + ); run_lex("1 flop", vec![numtok!(1), Token::Unit(Flop)]); run_lex("2 kflop", vec![numtok!(2), Token::Unit(KiloFlop)]); run_lex("3 mflop", vec![numtok!(3), Token::Unit(MegaFlop)]); @@ -1165,27 +1359,87 @@ mod tests { run_lex("10 rflop", vec![numtok!(10), Token::Unit(RonnaFlop)]); run_lex("11 qflop", vec![numtok!(11), Token::Unit(QuettaFlop)]); run_lex("1 flop/s", vec![numtok!(1), Token::Unit(FlopPerSecond)]); - run_lex("2 kflop/s", vec![numtok!(2), Token::Unit(KiloFlopPerSecond)]); - run_lex("3 mflop/s", vec![numtok!(3), Token::Unit(MegaFlopPerSecond)]); - run_lex("4 gflop/s", vec![numtok!(4), Token::Unit(GigaFlopPerSecond)]); - run_lex("5 tflop/s", vec![numtok!(5), Token::Unit(TeraFlopPerSecond)]); - run_lex("6 pflop/s", vec![numtok!(6), Token::Unit(PetaFlopPerSecond)]); + run_lex( + "2 kflop/s", + vec![numtok!(2), Token::Unit(KiloFlopPerSecond)], + ); + run_lex( + "3 mflop/s", + vec![numtok!(3), Token::Unit(MegaFlopPerSecond)], + ); + run_lex( + "4 gflop/s", + vec![numtok!(4), Token::Unit(GigaFlopPerSecond)], + ); + run_lex( + "5 tflop/s", + vec![numtok!(5), Token::Unit(TeraFlopPerSecond)], + ); + run_lex( + "6 pflop/s", + vec![numtok!(6), Token::Unit(PetaFlopPerSecond)], + ); run_lex("7 eflop/s", vec![numtok!(7), Token::Unit(ExaFlopPerSecond)]); - run_lex("8 zflop/s", vec![numtok!(8), Token::Unit(ZettaFlopPerSecond)]); - run_lex("9 yflop/s", vec![numtok!(9), Token::Unit(YottaFlopPerSecond)]); - run_lex("10 rflop/s", vec![numtok!(10), Token::Unit(RonnaFlopPerSecond)]); - run_lex("11 qflop/s", vec![numtok!(11), Token::Unit(QuettaFlopPerSecond)]); - run_lex("1 flop per second", vec![numtok!(1), Token::Unit(FlopPerSecond)]); - run_lex("2 kflop per second", vec![numtok!(2), Token::Unit(KiloFlopPerSecond)]); - run_lex("3 mflop per second", vec![numtok!(3), Token::Unit(MegaFlopPerSecond)]); - run_lex("4 gflop per second", vec![numtok!(4), Token::Unit(GigaFlopPerSecond)]); - run_lex("5 tflop per second", vec![numtok!(5), Token::Unit(TeraFlopPerSecond)]); - run_lex("6 pflop per second", vec![numtok!(6), Token::Unit(PetaFlopPerSecond)]); - run_lex("7 eflop per second", vec![numtok!(7), Token::Unit(ExaFlopPerSecond)]); - run_lex("8 zflop per second", vec![numtok!(8), Token::Unit(ZettaFlopPerSecond)]); - run_lex("9 yflop per second", vec![numtok!(9), Token::Unit(YottaFlopPerSecond)]); - run_lex("10 rflop per second", vec![numtok!(10), Token::Unit(RonnaFlopPerSecond)]); - run_lex("11 qflop per second", vec![numtok!(11), Token::Unit(QuettaFlopPerSecond)]); + run_lex( + "8 zflop/s", + vec![numtok!(8), Token::Unit(ZettaFlopPerSecond)], + ); + run_lex( + "9 yflop/s", + vec![numtok!(9), Token::Unit(YottaFlopPerSecond)], + ); + run_lex( + "10 rflop/s", + vec![numtok!(10), Token::Unit(RonnaFlopPerSecond)], + ); + run_lex( + "11 qflop/s", + vec![numtok!(11), Token::Unit(QuettaFlopPerSecond)], + ); + run_lex( + "1 flop per second", + vec![numtok!(1), Token::Unit(FlopPerSecond)], + ); + run_lex( + "2 kflop per second", + vec![numtok!(2), Token::Unit(KiloFlopPerSecond)], + ); + run_lex( + "3 mflop per second", + vec![numtok!(3), Token::Unit(MegaFlopPerSecond)], + ); + run_lex( + "4 gflop per second", + vec![numtok!(4), Token::Unit(GigaFlopPerSecond)], + ); + run_lex( + "5 tflop per second", + vec![numtok!(5), Token::Unit(TeraFlopPerSecond)], + ); + run_lex( + "6 pflop per second", + vec![numtok!(6), Token::Unit(PetaFlopPerSecond)], + ); + run_lex( + "7 eflop per second", + vec![numtok!(7), Token::Unit(ExaFlopPerSecond)], + ); + run_lex( + "8 zflop per second", + vec![numtok!(8), Token::Unit(ZettaFlopPerSecond)], + ); + run_lex( + "9 yflop per second", + vec![numtok!(9), Token::Unit(YottaFlopPerSecond)], + ); + run_lex( + "10 rflop per second", + vec![numtok!(10), Token::Unit(RonnaFlopPerSecond)], + ); + run_lex( + "11 qflop per second", + vec![numtok!(11), Token::Unit(QuettaFlopPerSecond)], + ); run_lex("1 flops", vec![numtok!(1), Token::Unit(FlopPerSecond)]); run_lex("2 kflops", vec![numtok!(2), Token::Unit(KiloFlopPerSecond)]); run_lex("3 mflops", vec![numtok!(3), Token::Unit(MegaFlopPerSecond)]); @@ -1193,143 +1447,814 @@ mod tests { run_lex("5 tflops", vec![numtok!(5), Token::Unit(TeraFlopPerSecond)]); run_lex("6 pflops", vec![numtok!(6), Token::Unit(PetaFlopPerSecond)]); run_lex("7 eflops", vec![numtok!(7), Token::Unit(ExaFlopPerSecond)]); - run_lex("8 zflops", vec![numtok!(8), Token::Unit(ZettaFlopPerSecond)]); - run_lex("9 yflops", vec![numtok!(9), Token::Unit(YottaFlopPerSecond)]); - run_lex("10 rflops", vec![numtok!(10), Token::Unit(RonnaFlopPerSecond)]); - run_lex("11 qflops", vec![numtok!(11), Token::Unit(QuettaFlopPerSecond)]); + run_lex( + "8 zflops", + vec![numtok!(8), Token::Unit(ZettaFlopPerSecond)], + ); + run_lex( + "9 yflops", + vec![numtok!(9), Token::Unit(YottaFlopPerSecond)], + ); + run_lex( + "10 rflops", + vec![numtok!(10), Token::Unit(RonnaFlopPerSecond)], + ); + run_lex( + "11 qflops", + vec![numtok!(11), Token::Unit(QuettaFlopPerSecond)], + ); run_lex("234 wh", vec![numtok!(234), Token::Unit(WattHour)]); run_lex("1 w", vec![numtok!(1), Token::Unit(Watt)]); run_lex("1 watt", vec![numtok!(1), Token::Unit(Watt)]); run_lex("1 watts", vec![numtok!(1), Token::Unit(Watt)]); run_lex("1 watt hour", vec![numtok!(1), Token::Unit(WattHour)]); - run_lex("0 watt + 1 watts", vec![numtok!(0), Token::Unit(Watt), Token::Operator(Plus), numtok!(1), Token::Unit(Watt)]); - run_lex("0 watt * 1", vec![numtok!(0), Token::Unit(Watt), Token::Operator(Multiply), numtok!(1)]); - run_lex("2 watts + 3 watts", vec![numtok!(2), Token::Unit(Watt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 watts * 3", vec![numtok!(2), Token::Unit(Watt), Token::Operator(Multiply), numtok!(3)]); - run_lex("4 watt plus 5 watts", vec![numtok!(4), Token::Unit(Watt), Token::Operator(Plus), numtok!(5), Token::Unit(Watt)]); - run_lex("4 watt times 5", vec![numtok!(4), Token::Unit(Watt), Token::Operator(Multiply), numtok!(5)]); - run_lex("6 watts plus 7 watts", vec![numtok!(6), Token::Unit(Watt), Token::Operator(Plus), numtok!(7), Token::Unit(Watt)]); - run_lex("6 watts times 7", vec![numtok!(6), Token::Unit(Watt), Token::Operator(Multiply), numtok!(7)]); + run_lex( + "0 watt + 1 watts", + vec![ + numtok!(0), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(1), + Token::Unit(Watt), + ], + ); + run_lex( + "0 watt * 1", + vec![ + numtok!(0), + Token::Unit(Watt), + Token::Operator(Multiply), + numtok!(1), + ], + ); + run_lex( + "2 watts + 3 watts", + vec![ + numtok!(2), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 watts * 3", + vec![ + numtok!(2), + Token::Unit(Watt), + Token::Operator(Multiply), + numtok!(3), + ], + ); + run_lex( + "4 watt plus 5 watts", + vec![ + numtok!(4), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Watt), + ], + ); + run_lex( + "4 watt times 5", + vec![ + numtok!(4), + Token::Unit(Watt), + Token::Operator(Multiply), + numtok!(5), + ], + ); + run_lex( + "6 watts plus 7 watts", + vec![ + numtok!(6), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Watt), + ], + ); + run_lex( + "6 watts times 7", + vec![ + numtok!(6), + Token::Unit(Watt), + Token::Operator(Multiply), + numtok!(7), + ], + ); run_lex("2.3 kwh", vec![numtok!(2.3), Token::Unit(KilowattHour)]); run_lex("1 kw", vec![numtok!(1), Token::Unit(Kilowatt)]); run_lex("1 kilowatt", vec![numtok!(1), Token::Unit(Kilowatt)]); run_lex("1 kilowatts", vec![numtok!(1), Token::Unit(Kilowatt)]); - run_lex("1 kilowatt hour", vec![numtok!(1), Token::Unit(KilowattHour)]); - run_lex("2 kilowatt + 3 watt", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatt * 4", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Multiply), numtok!(4)]); - run_lex("2 kilowatt times 4", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Multiply), numtok!(4)]); - run_lex("2 kilowatt + 3 watts", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatts + 3 watt", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatts + 3 watts", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatt plus 3 watt", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatt plus 3 watts", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatts plus 3 watt", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 kilowatts plus 3 watts", vec![numtok!(2), Token::Unit(Kilowatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("6.6 watts + 4 kilowatts", vec![numtok!(6.6), Token::Unit(Watt), Token::Operator(Plus), numtok!(4), Token::Unit(Kilowatt)]); - run_lex("6.6 watts plus 4 kilowatts", vec![numtok!(6.6), Token::Unit(Watt), Token::Operator(Plus), numtok!(4), Token::Unit(Kilowatt)]); + run_lex( + "1 kilowatt hour", + vec![numtok!(1), Token::Unit(KilowattHour)], + ); + run_lex( + "2 kilowatt + 3 watt", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatt * 4", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Multiply), + numtok!(4), + ], + ); + run_lex( + "2 kilowatt times 4", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Multiply), + numtok!(4), + ], + ); + run_lex( + "2 kilowatt + 3 watts", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatts + 3 watt", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatts + 3 watts", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatt plus 3 watt", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatt plus 3 watts", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatts plus 3 watt", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 kilowatts plus 3 watts", + vec![ + numtok!(2), + Token::Unit(Kilowatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "6.6 watts + 4 kilowatts", + vec![ + numtok!(6.6), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(4), + Token::Unit(Kilowatt), + ], + ); + run_lex( + "6.6 watts plus 4 kilowatts", + vec![ + numtok!(6.6), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(4), + Token::Unit(Kilowatt), + ], + ); run_lex("2.3 mwh", vec![numtok!(2.3), Token::Unit(MegawattHour)]); run_lex("1 mw", vec![numtok!(1), Token::Unit(Megawatt)]); run_lex("1 megawatt", vec![numtok!(1), Token::Unit(Megawatt)]); - run_lex("1 megawatt hour", vec![numtok!(1), Token::Unit(MegawattHour)]); - run_lex("2 megawatt + 3 watt", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatt * 6", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Multiply), numtok!(6)]); - run_lex("2 megawatt times 6", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Multiply), numtok!(6)]); - run_lex("2 megawatt + 3 watts", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatts + 3 watt", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatts + 3 watts", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatt plus 3 watt", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatt plus 3 watts", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatts plus 3 watt", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("2 megawatts plus 3 watts", vec![numtok!(2), Token::Unit(Megawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Watt)]); - run_lex("6.6 watts + 4 megawatts", vec![numtok!(6.6), Token::Unit(Watt), Token::Operator(Plus), numtok!(4), Token::Unit(Megawatt)]); - run_lex("6.6 watts plus 4 megawatts", vec![numtok!(6.6), Token::Unit(Watt), Token::Operator(Plus), numtok!(4), Token::Unit(Megawatt)]); + run_lex( + "1 megawatt hour", + vec![numtok!(1), Token::Unit(MegawattHour)], + ); + run_lex( + "2 megawatt + 3 watt", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatt * 6", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Multiply), + numtok!(6), + ], + ); + run_lex( + "2 megawatt times 6", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Multiply), + numtok!(6), + ], + ); + run_lex( + "2 megawatt + 3 watts", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatts + 3 watt", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatts + 3 watts", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatt plus 3 watt", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatt plus 3 watts", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatts plus 3 watt", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "2 megawatts plus 3 watts", + vec![ + numtok!(2), + Token::Unit(Megawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Watt), + ], + ); + run_lex( + "6.6 watts + 4 megawatts", + vec![ + numtok!(6.6), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(4), + Token::Unit(Megawatt), + ], + ); + run_lex( + "6.6 watts plus 4 megawatts", + vec![ + numtok!(6.6), + Token::Unit(Watt), + Token::Operator(Plus), + numtok!(4), + Token::Unit(Megawatt), + ], + ); run_lex("234 gwh", vec![numtok!(234), Token::Unit(GigawattHour)]); run_lex("1 gw", vec![numtok!(1), Token::Unit(Gigawatt)]); run_lex("1 gigawatt", vec![numtok!(1), Token::Unit(Gigawatt)]); run_lex("1 gigawatts", vec![numtok!(1), Token::Unit(Gigawatt)]); - run_lex("1 gigawatt hour", vec![numtok!(1), Token::Unit(GigawattHour)]); - run_lex("0 gigawatt + 1 gigawatts", vec![numtok!(0), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(1), Token::Unit(Gigawatt)]); - run_lex("0 gigawatt * 1", vec![numtok!(0), Token::Unit(Gigawatt), Token::Operator(Multiply), numtok!(1)]); - run_lex("2 gigawatts + 3 gigawatts", vec![numtok!(2), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(3), Token::Unit(Gigawatt)]); - run_lex("2 gigawatts * 3", vec![numtok!(2), Token::Unit(Gigawatt), Token::Operator(Multiply), numtok!(3)]); - run_lex("4 gigawatt plus 5 watt", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(5), Token::Unit(Watt)]); - run_lex("4 gigawatt plus 5 megawatt", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(5), Token::Unit(Megawatt)]); - run_lex("4 gigawatt plus 5 gigawatt", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(5), Token::Unit(Gigawatt)]); - run_lex("4 gigawatt plus 5 watts", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(5), Token::Unit(Watt)]); - run_lex("4 gigawatt plus 5 megawatts", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(5), Token::Unit(Megawatt)]); - run_lex("4 gigawatt plus 5 gigawatts", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(5), Token::Unit(Gigawatt)]); - run_lex("4 gigawatt times 5", vec![numtok!(4), Token::Unit(Gigawatt), Token::Operator(Multiply), numtok!(5)]); - run_lex("6 gigawatts plus 7 watt", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(7), Token::Unit(Watt)]); - run_lex("6 gigawatts plus 7 megawatt", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(7), Token::Unit(Megawatt)]); - run_lex("6 gigawatts plus 7 gigawatt", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(7), Token::Unit(Gigawatt)]); - run_lex("6 gigawatts plus 7 watts", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(7), Token::Unit(Watt)]); - run_lex("6 gigawatts plus 7 megawatts", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(7), Token::Unit(Megawatt)]); - run_lex("6 gigawatts plus 7 gigawatts", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Plus), numtok!(7), Token::Unit(Gigawatt)]); - run_lex("6 gigawatts times 7", vec![numtok!(6), Token::Unit(Gigawatt), Token::Operator(Multiply), numtok!(7)]); - run_lex("88 mw * 3", vec![numtok!(88), Token::Unit(Megawatt), Token::Operator(Multiply), numtok!(3)]); - run_lex("88 mw times 3", vec![numtok!(88), Token::Unit(Megawatt), Token::Operator(Multiply), numtok!(3)]); + run_lex( + "1 gigawatt hour", + vec![numtok!(1), Token::Unit(GigawattHour)], + ); + run_lex( + "0 gigawatt + 1 gigawatts", + vec![ + numtok!(0), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(1), + Token::Unit(Gigawatt), + ], + ); + run_lex( + "0 gigawatt * 1", + vec![ + numtok!(0), + Token::Unit(Gigawatt), + Token::Operator(Multiply), + numtok!(1), + ], + ); + run_lex( + "2 gigawatts + 3 gigawatts", + vec![ + numtok!(2), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(3), + Token::Unit(Gigawatt), + ], + ); + run_lex( + "2 gigawatts * 3", + vec![ + numtok!(2), + Token::Unit(Gigawatt), + Token::Operator(Multiply), + numtok!(3), + ], + ); + run_lex( + "4 gigawatt plus 5 watt", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Watt), + ], + ); + run_lex( + "4 gigawatt plus 5 megawatt", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Megawatt), + ], + ); + run_lex( + "4 gigawatt plus 5 gigawatt", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Gigawatt), + ], + ); + run_lex( + "4 gigawatt plus 5 watts", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Watt), + ], + ); + run_lex( + "4 gigawatt plus 5 megawatts", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Megawatt), + ], + ); + run_lex( + "4 gigawatt plus 5 gigawatts", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(5), + Token::Unit(Gigawatt), + ], + ); + run_lex( + "4 gigawatt times 5", + vec![ + numtok!(4), + Token::Unit(Gigawatt), + Token::Operator(Multiply), + numtok!(5), + ], + ); + run_lex( + "6 gigawatts plus 7 watt", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Watt), + ], + ); + run_lex( + "6 gigawatts plus 7 megawatt", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Megawatt), + ], + ); + run_lex( + "6 gigawatts plus 7 gigawatt", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Gigawatt), + ], + ); + run_lex( + "6 gigawatts plus 7 watts", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Watt), + ], + ); + run_lex( + "6 gigawatts plus 7 megawatts", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Megawatt), + ], + ); + run_lex( + "6 gigawatts plus 7 gigawatts", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Plus), + numtok!(7), + Token::Unit(Gigawatt), + ], + ); + run_lex( + "6 gigawatts times 7", + vec![ + numtok!(6), + Token::Unit(Gigawatt), + Token::Operator(Multiply), + numtok!(7), + ], + ); + run_lex( + "88 mw * 3", + vec![ + numtok!(88), + Token::Unit(Megawatt), + Token::Operator(Multiply), + numtok!(3), + ], + ); + run_lex( + "88 mw times 3", + vec![ + numtok!(88), + Token::Unit(Megawatt), + Token::Operator(Multiply), + numtok!(3), + ], + ); run_lex("999 kb", vec![numtok!(999), Token::Unit(Kilobyte)]); - run_lex("200 gb - 100 mb", vec![numtok!(200), Token::Unit(Gigabyte), Token::Operator(Minus), numtok!(100), Token::Unit(Megabyte)]); + run_lex( + "200 gb - 100 mb", + vec![ + numtok!(200), + Token::Unit(Gigabyte), + Token::Operator(Minus), + numtok!(100), + Token::Unit(Megabyte), + ], + ); run_lex("999 kib", vec![numtok!(999), Token::Unit(Kibibyte)]); - run_lex("200 gib - 100 mib", vec![numtok!(200), Token::Unit(Gibibyte), Token::Operator(Minus), numtok!(100), Token::Unit(Mebibyte)]); + run_lex( + "200 gib - 100 mib", + vec![ + numtok!(200), + Token::Unit(Gibibyte), + Token::Operator(Minus), + numtok!(100), + Token::Unit(Mebibyte), + ], + ); run_lex("45 btu", vec![numtok!(45), Token::Unit(BritishThermalUnit)]); - run_lex("45.5 british thermal unit", vec![numtok!(45.5), Token::Unit(BritishThermalUnit)]); - run_lex("46 british thermal units", vec![numtok!(46), Token::Unit(BritishThermalUnit)]); - run_lex("5432 newton metres", vec![numtok!(5432), Token::Unit(NewtonMeter)]); - run_lex("2345 newton-meters", vec![numtok!(2345), Token::Unit(NewtonMeter)]); + run_lex( + "45.5 british thermal unit", + vec![numtok!(45.5), Token::Unit(BritishThermalUnit)], + ); + run_lex( + "46 british thermal units", + vec![numtok!(46), Token::Unit(BritishThermalUnit)], + ); + run_lex( + "5432 newton metres", + vec![numtok!(5432), Token::Unit(NewtonMeter)], + ); + run_lex( + "2345 newton-meters", + vec![numtok!(2345), Token::Unit(NewtonMeter)], + ); run_lex("20 lbf", vec![numtok!(20), Token::LexerKeyword(PoundForce)]); run_lex("60 hz", vec![numtok!(60), Token::Unit(Hertz)]); - run_lex("1100 rpm", vec![numtok!(1100), Token::Unit(RevolutionsPerMinute)]); - run_lex("1150 revolutions per minute", vec![numtok!(1150), Token::Unit(RevolutionsPerMinute)]); - run_lex("1 revolution per min", vec![numtok!(1), Token::Unit(RevolutionsPerMinute)]); - run_lex("4 revolution / mins", vec![numtok!(4), Token::Unit(RevolutionsPerMinute)]); - run_lex("1250 r / min", vec![numtok!(1250), Token::Unit(RevolutionsPerMinute)]); - run_lex("1300 rev / min", vec![numtok!(1300), Token::Unit(RevolutionsPerMinute)]); - run_lex("1350 rev / minute", vec![numtok!(1350), Token::Unit(RevolutionsPerMinute)]); - run_lex("1250 r per min", vec![numtok!(1250), Token::Unit(RevolutionsPerMinute)]); - run_lex("1300 rev per min", vec![numtok!(1300), Token::Unit(RevolutionsPerMinute)]); - run_lex("1350 rev per minute", vec![numtok!(1350), Token::Unit(RevolutionsPerMinute)]); - run_lex("100 kph", vec![numtok!(100), Token::Unit(KilometersPerHour)]); - run_lex("100 kmh", vec![numtok!(100), Token::Unit(KilometersPerHour)]); - run_lex("100 kilometers per hour", vec![numtok!(100), Token::Unit(KilometersPerHour)]); - run_lex("100 kilometre / hrs", vec![numtok!(100), Token::Unit(KilometersPerHour)]); + run_lex( + "1100 rpm", + vec![numtok!(1100), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1150 revolutions per minute", + vec![numtok!(1150), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1 revolution per min", + vec![numtok!(1), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "4 revolution / mins", + vec![numtok!(4), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1250 r / min", + vec![numtok!(1250), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1300 rev / min", + vec![numtok!(1300), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1350 rev / minute", + vec![numtok!(1350), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1250 r per min", + vec![numtok!(1250), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1300 rev per min", + vec![numtok!(1300), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "1350 rev per minute", + vec![numtok!(1350), Token::Unit(RevolutionsPerMinute)], + ); + run_lex( + "100 kph", + vec![numtok!(100), Token::Unit(KilometersPerHour)], + ); + run_lex( + "100 kmh", + vec![numtok!(100), Token::Unit(KilometersPerHour)], + ); + run_lex( + "100 kilometers per hour", + vec![numtok!(100), Token::Unit(KilometersPerHour)], + ); + run_lex( + "100 kilometre / hrs", + vec![numtok!(100), Token::Unit(KilometersPerHour)], + ); run_lex("3.6 mps", vec![numtok!(3.6), Token::Unit(MetersPerSecond)]); - run_lex("3.6 meters per second", vec![numtok!(3.6), Token::Unit(MetersPerSecond)]); - run_lex("3.6 metre / secs", vec![numtok!(3.6), Token::Unit(MetersPerSecond)]); + run_lex( + "3.6 meters per second", + vec![numtok!(3.6), Token::Unit(MetersPerSecond)], + ); + run_lex( + "3.6 metre / secs", + vec![numtok!(3.6), Token::Unit(MetersPerSecond)], + ); run_lex("60 mph", vec![numtok!(60), Token::Unit(MilesPerHour)]); - run_lex("60 miles per hour", vec![numtok!(60), Token::Unit(MilesPerHour)]); + run_lex( + "60 miles per hour", + vec![numtok!(60), Token::Unit(MilesPerHour)], + ); run_lex("60 mile / hr", vec![numtok!(60), Token::Unit(MilesPerHour)]); run_lex("35 fps", vec![numtok!(35), Token::Unit(FeetPerSecond)]); run_lex("35 ft / sec", vec![numtok!(35), Token::Unit(FeetPerSecond)]); - run_lex("35 ft per seconds", vec![numtok!(35), Token::Unit(FeetPerSecond)]); - run_lex("35 foot / secs", vec![numtok!(35), Token::Unit(FeetPerSecond)]); - run_lex("35 foot per seconds", vec![numtok!(35), Token::Unit(FeetPerSecond)]); - run_lex("35 feet / sec", vec![numtok!(35), Token::Unit(FeetPerSecond)]); - run_lex("35 feet per second", vec![numtok!(35), Token::Unit(FeetPerSecond)]); + run_lex( + "35 ft per seconds", + vec![numtok!(35), Token::Unit(FeetPerSecond)], + ); + run_lex( + "35 foot / secs", + vec![numtok!(35), Token::Unit(FeetPerSecond)], + ); + run_lex( + "35 foot per seconds", + vec![numtok!(35), Token::Unit(FeetPerSecond)], + ); + run_lex( + "35 feet / sec", + vec![numtok!(35), Token::Unit(FeetPerSecond)], + ); + run_lex( + "35 feet per second", + vec![numtok!(35), Token::Unit(FeetPerSecond)], + ); run_lex("30 pa", vec![numtok!(30), Token::Unit(Pascal)]); - run_lex("23 celsius + 4 celsius", vec![numtok!(23), Token::Unit(Celsius), Token::Operator(Plus), numtok!(4), Token::Unit(Celsius)]); - run_lex("54 f - 1.5 fahrenheit", vec![numtok!(54), Token::Unit(Fahrenheit), Token::Operator(Minus), numtok!(1.5), Token::Unit(Fahrenheit)]); - run_lex("50 metric tonnes", vec![numtok!(50), Token::Unit(MetricTon)]); - run_lex("77 metric hps", vec![numtok!(77), Token::Unit(MetricHorsepower)]); + run_lex( + "23 celsius + 4 celsius", + vec![ + numtok!(23), + Token::Unit(Celsius), + Token::Operator(Plus), + numtok!(4), + Token::Unit(Celsius), + ], + ); + run_lex( + "54 f - 1.5 fahrenheit", + vec![ + numtok!(54), + Token::Unit(Fahrenheit), + Token::Operator(Minus), + numtok!(1.5), + Token::Unit(Fahrenheit), + ], + ); + run_lex( + "50 metric tonnes", + vec![numtok!(50), Token::Unit(MetricTon)], + ); + run_lex( + "77 metric hps", + vec![numtok!(77), Token::Unit(MetricHorsepower)], + ); - run_lex("100 + 99", vec![numtok!(100), Token::Operator(Plus), numtok!(99)]); - run_lex("100 plus 99", vec![numtok!(100), Token::Operator(Plus), numtok!(99)]); - run_lex("12 - 4", vec![numtok!(12), Token::Operator(Minus), numtok!(4)]); - run_lex("12 minus 4", vec![numtok!(12), Token::Operator(Minus), numtok!(4)]); - run_lex("50.5 * 2", vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)]); - run_lex("50.5 times 2", vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)]); - run_lex("50.5 multiplied by 2", vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)]); - run_lex("6 / 3", vec![numtok!(6), Token::Operator(Divide), numtok!(3)]); - run_lex("50 / 10", vec![numtok!(50), Token::Operator(Divide), numtok!(10)]); - run_lex("52 ÷ 12", vec![numtok!(52), Token::Operator(Divide), numtok!(12)]); - run_lex("6 divided by 3", vec![numtok!(6), Token::Operator(Divide), numtok!(3)]); - run_lex("7 mod 5", vec![numtok!(7), Token::Operator(Modulo), numtok!(5)]); + run_lex( + "100 + 99", + vec![numtok!(100), Token::Operator(Plus), numtok!(99)], + ); + run_lex( + "100 plus 99", + vec![numtok!(100), Token::Operator(Plus), numtok!(99)], + ); + run_lex( + "12 - 4", + vec![numtok!(12), Token::Operator(Minus), numtok!(4)], + ); + run_lex( + "12 minus 4", + vec![numtok!(12), Token::Operator(Minus), numtok!(4)], + ); + run_lex( + "50.5 * 2", + vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)], + ); + run_lex( + "50.5 times 2", + vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)], + ); + run_lex( + "50.5 multiplied by 2", + vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)], + ); + run_lex( + "6 / 3", + vec![numtok!(6), Token::Operator(Divide), numtok!(3)], + ); + run_lex( + "50 / 10", + vec![numtok!(50), Token::Operator(Divide), numtok!(10)], + ); + run_lex( + "52 ÷ 12", + vec![numtok!(52), Token::Operator(Divide), numtok!(12)], + ); + run_lex( + "6 divided by 3", + vec![numtok!(6), Token::Operator(Divide), numtok!(3)], + ); + run_lex( + "7 mod 5", + vec![numtok!(7), Token::Operator(Modulo), numtok!(5)], + ); - run_lex("(2 + 3) * 4", vec![Token::Operator(LeftParen), numtok!(2), Token::Operator(Plus), numtok!(3), Token::Operator(RightParen), Token::Operator(Multiply), numtok!(4)]); - run_lex("52 weeks * (12 hrs + 12 hours)", vec![numtok!(52), Token::Unit(Week), Token::Operator(Multiply), Token::Operator(LeftParen), numtok!(12), Token::Unit(Hour), Token::Operator(Plus), numtok!(12), Token::Unit(Hour), Token::Operator(RightParen)]); - run_lex("12 pound+", vec![numtok!(12), Token::Unit(Pound), Token::Operator(Plus)]); + run_lex( + "(2 + 3) * 4", + vec![ + Token::Operator(LeftParen), + numtok!(2), + Token::Operator(Plus), + numtok!(3), + Token::Operator(RightParen), + Token::Operator(Multiply), + numtok!(4), + ], + ); + run_lex( + "52 weeks * (12 hrs + 12 hours)", + vec![ + numtok!(52), + Token::Unit(Week), + Token::Operator(Multiply), + Token::Operator(LeftParen), + numtok!(12), + Token::Unit(Hour), + Token::Operator(Plus), + numtok!(12), + Token::Unit(Hour), + Token::Operator(RightParen), + ], + ); + run_lex( + "12 pound+", + vec![numtok!(12), Token::Unit(Pound), Token::Operator(Plus)], + ); - run_lex("5 π m", vec![numtok!(5), Token::Constant(Pi), Token::Unit(Meter)]); - run_lex("5 Ω + 2 mΩ", vec![numtok!(5), Token::Unit(Ohm), Token::Operator(Plus), numtok!(2), Token::Unit(Milliohm)]); + run_lex( + "5 π m", + vec![numtok!(5), Token::Constant(Pi), Token::Unit(Meter)], + ); + run_lex( + "5 Ω + 2 mΩ", + vec![ + numtok!(5), + Token::Unit(Ohm), + Token::Operator(Plus), + numtok!(2), + Token::Unit(Milliohm), + ], + ); } } diff --git a/src/lib.rs b/src/lib.rs index 82f5143..3533940 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,8 @@ //! } //! ``` -use crate::units::Unit; -use fastnum::{dec128 as d, D128}; +use crate::units::{Unit, UnitType, primitive_unit}; +use fastnum::{D128, dec128 as d}; use std::fmt::{self, Display}; use web_time::Instant; @@ -55,28 +55,78 @@ pub mod units; /// }; /// ``` pub struct Number { - /// The number part of a [`Number`] struct + /// The value of the number pub value: D128, - /// The unit of a [`Number`] struct. This can be [`NoType`](units::UnitType::NoType) - pub unit: Unit, + /// The unit and exponent + pub unit: Vec<(Unit, isize)>, } impl Number { - pub const fn new(value: D128, unit: Unit) -> Number { + pub fn new_unitless(value: D128) -> Number { + Number { + value, + unit: vec![], + } + } + pub fn with_basic_unit(value: D128, unit: Unit) -> Number { + Number { + value, + unit: vec![(unit, 1)], + } + } + pub fn with_unit(value: D128, unit: Vec<(Unit, isize)>) -> Number { Number { value, unit } } + pub fn has_unit(&self) -> bool { + !self.unit.is_empty() + } + pub fn is_unitless(&self) -> bool { + self.unit.is_empty() + } pub fn get_simplified_value(&self) -> D128 { self.value.reduce() } + pub fn primitive_unit(&self) -> Vec<(Unit, isize)> { + primitive_unit(&self.unit) + } + pub fn contains_primitive(&self, unit: UnitType) -> bool { + self.unit.iter().any(|(u, _)| u.category() == unit) + } + pub fn singular(&self) -> String { + let mut s = String::new(); + for (i, unit) in self.unit.iter().enumerate() { + if i != 0 { + match unit.1.is_positive() { + true => s.push_str(" * "), + false => s.push_str(" / "), + } + } + s.push_str(unit.0.plural()); + } + s + } + pub fn plural(&self) -> String { + let mut s = String::new(); + for (i, unit) in self.unit.iter().enumerate() { + if i != 0 { + match unit.1.is_positive() { + true => s.push_str(" * "), + false => s.push_str(" / "), + } + } + s.push_str(unit.0.plural()); + } + s + } } impl Display for Number { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let value = self.get_simplified_value(); let word = match self.value == d!(1) { - true => self.unit.singular(), - false => self.unit.plural(), + true => self.singular(), + false => self.plural(), }; - let output = match word { + let output = match word.as_str() { "" => format!("{value}"), _ => format!("{value} {word}"), }; @@ -187,7 +237,7 @@ pub enum LexerKeyword { Revolution, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] /// A token like a [`Number`](Token::Number), [`Operator`](Token::Operator), [`Unit`](Token::Unit) etc. /// /// Strings can be divided up into these tokens by the [`lexer`], and then put into the [`parser`]. @@ -209,6 +259,24 @@ pub enum Token { Negative, Unit(units::Unit), } +impl fmt::Debug for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Token::Operator(op) => write!(f, "Operator({:?})", op), + Token::UnaryOperator(op) => write!(f, "UnaryOperator({:?})", op), + Token::Number(num) => write!(f, "Number({num})"), + Token::FunctionIdentifier(id) => write!(f, "FunctionIdentifier({:?})", id), + Token::Constant(c) => write!(f, "Constant({:?})", c), + Token::Paren => write!(f, "Paren"), + Token::Per => write!(f, "Per"), + Token::LexerKeyword(op) => write!(f, "LexerKeyword({:?})", op), + Token::TextOperator(op) => write!(f, "TextOperator({:?})", op), + Token::NamedNumber(num) => write!(f, "NamedNumber({:?})", num), + Token::Negative => write!(f, "Negative"), + Token::Unit(u) => write!(f, "Unit({:?})", u), + } + } +} #[macro_export] macro_rules! numtok { @@ -234,11 +302,7 @@ macro_rules! numtok { /// } /// } /// ``` -pub fn eval( - input: &str, - allow_trailing_operators: bool, - verbose: bool, -) -> Result { +pub fn eval(input: &str, allow_trailing_operators: bool, verbose: bool) -> Result { let lex_start = Instant::now(); match lexer::lex(input, allow_trailing_operators) { @@ -318,7 +382,10 @@ mod tests { assert_eq!(default_eval("-2(-3)"), Number::new(d!(6), Unit::NoUnit)); assert_eq!(default_eval("-2(3)"), Number::new(d!(-6), Unit::NoUnit)); assert_eq!(default_eval("(3)-2"), Number::new(d!(1), Unit::NoUnit)); - assert_eq!(default_eval("-1km to m"), Number::new(d!(-1000), Unit::Meter)); + assert_eq!( + default_eval("-1km to m"), + Number::new(d!(-1000), Unit::Meter) + ); assert_eq!(default_eval("2*-3*0.5"), Number::new(d!(-3), Unit::NoUnit)); assert_eq!(default_eval("-3^2"), Number::new(d!(-9), Unit::NoUnit)); assert_eq!(default_eval("-1+2"), Number::new(d!(1), Unit::NoUnit)); diff --git a/src/units.rs b/src/units.rs index ef7e043..b096931 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,12 +1,9 @@ -use fastnum::{dec128 as d, D128}; use crate::Number; +use fastnum::{D128, dec128 as d}; #[derive(Clone, Copy, PartialEq, Debug)] /// An enum of all possible unit types, like [`Length`], [`DigitalStorage`] etc. -/// There is also a [`NoType`] unit type for normal numbers. pub enum UnitType { - /// A normal number, for example `5` - NoType, /// A unit of time, for example [`Hour`] Time, /// A unit of length, for example [`Mile`] @@ -44,8 +41,75 @@ pub enum UnitType { /// A unit of temperature, for example [`Kelvin`] Temperature, } +impl UnitType { + fn primitive(&self) -> Vec<(Unit, isize)> { + match self { + // Time => vec![(Time, 1)], + // Length => vec![(Length, 1)], + // Area => vec![(Length, 2)], + // Volume => vec![(Length, 3)], + // Mass => vec![(Mass, 1)], + // DigitalStorage => vec![(DigitalStorage, 1)], + // DataTransferRate => vec![(DigitalStorage, 1), (Time, -1)], + // FlopCount => vec![(FlopCount, 1)], + // FlopRate => vec![(FlopCount, 1), (Time, -1)], + // Energy => vec![(Mass, 1), (Length, 2), (Time, -2)], + // Power => vec![(Mass, 1), (Length, 2), (Time, -3)], + // ElectricCurrent => vec![(ElectricCurrent, 1)], + // Resistance => vec![(Mass, 1), (Length, 2), (Time, -3), (ElectricCurrent, -2)], + // Voltage => vec![(Mass, 1), (Length, 2), (Time, -3), (ElectricCurrent, -1)], + // Pressure => vec![(Mass, 1), (Length, -1), (Time, -2)], + // Frequency => vec![(Time, -1)], + // Speed => vec![(Length, 1), (Time, -1)], + // Temperature => vec![(Temperature, 1)], + Time => vec![(Nanosecond, 1)], + Length => vec![(Millimeter, 1)], + Area => vec![(SquareMillimeter, 2)], + Volume => vec![(CubicMillimeter, 3)], + Mass => vec![(Gram, 1)], + DigitalStorage => vec![(Bit, 1)], + DataTransferRate => vec![(Bit, 1), (Nanosecond, -1)], + FlopCount => vec![(Flop, 1)], + FlopRate => vec![(Flop, 1), (Nanosecond, -1)], + Energy => vec![(Gram, 1), (SquareMillimeter, 2), (Nanosecond, -2)], + Power => vec![(Gram, 1), (SquareMillimeter, 2), (Nanosecond, -3)], + ElectricCurrent => vec![(Ampere, 1)], + Resistance => vec![ + (Gram, 1), + (SquareMillimeter, 2), + (Nanosecond, -3), + (Ampere, -2), + ], + Voltage => vec![ + (Gram, 1), + (SquareMillimeter, 2), + (Nanosecond, -3), + (Ampere, -1), + ], + Pressure => vec![(Gram, 1), (Millimeter, -1), (Nanosecond, -2)], + Frequency => vec![(Nanosecond, -1)], + Speed => vec![(Millimeter, 1), (Nanosecond, -1)], + Temperature => vec![(Kelvin, 1)], + } + } +} use UnitType::*; +pub fn primitive_unit(unit: &Vec<(Unit, isize)>) -> Vec<(Unit, isize)> { + let mut primitives: Vec<(Unit, isize)> = Vec::new(); + for (unit, exponent) in unit { + for (primitive, primitive_exponent) in unit.category().primitive() { + let existing = primitives.iter_mut().find(|(u, _)| u == &primitive); + match existing { + Some(existing) => existing.1 += primitive_exponent * exponent, + None => primitives.push((primitive, primitive_exponent * exponent)), + } + } + } + primitives.retain(|(_, exponent)| exponent != &0); + primitives +} + // Macro for creating units. Not possible to extend/change the default units // with this because the default units are imported into the lexer, parser // and evaluator @@ -92,8 +156,6 @@ macro_rules! create_units { } create_units!( - NoUnit: (NoType, d!(1), "", ""), - Nanosecond: (Time, d!(1), "nanosecond", "nanoseconds"), Microsecond: (Time, d!(1000), "microsecond", "microseconds"), Millisecond: (Time, d!(1000000), "millisecond", "milliseconds"), @@ -342,65 +404,96 @@ create_units!( ); /// Returns the conversion factor between two units. -/// +/// /// The conversion factor is what you need to multiply `unit` with to get /// `to_unit`. For example, the conversion factor from 1 minute to 1 second /// is 60. -/// +/// /// This is not sufficient for [`Temperature`] units. pub fn get_conversion_factor(unit: Unit, to_unit: Unit) -> D128 { unit.weight() / to_unit.weight() } +fn combined_weight(unit: &[(Unit, isize)]) -> D128 { + unit.iter().fold(D128::from(1), |acc, (u, exp)| { + acc * integer_power(u.weight(), *exp) + }) +} + +fn integer_power(base: D128, exp: isize) -> D128 { + let positive = (0..exp.unsigned_abs()).fold(D128::from(1), |acc, _| acc * base); + if exp >= 0 { + positive + } else { + D128::from(1) / positive + } +} + /// Convert a [`Number`] to a specified [`Unit`]. -pub fn convert(number: Number, to_unit: Unit) -> Result { - if number.unit.category() != to_unit.category() { - return Err(format!("Cannot convert from {:?} to {:?}", number.unit, to_unit)); +pub fn convert(number: Number, to_unit: Vec<(Unit, isize)>) -> Result { + if number.primitive_unit() != primitive_unit(&to_unit) { + return Err(format!( + "Cannot convert from {:?} to {:?}", + number.unit, to_unit + )); } let value = number.value; - let ok = |new_value| { - Ok(Number::new(new_value, to_unit)) - }; - if number.unit.category() == UnitType::Temperature { - match (number.unit, to_unit) { - (Kelvin, Kelvin) => ok(value), - (Kelvin, Celsius) => ok(value-d!(273.15)), - (Kelvin, Fahrenheit) => ok(value*d!(1.8)-d!(459.67)), - (Celsius, Celsius) => ok(value), - (Celsius, Kelvin) => ok(value+d!(273.15)), - (Celsius, Fahrenheit) => ok(value*d!(1.8)+d!(32)), - (Fahrenheit, Fahrenheit) => ok(value), - (Fahrenheit, Kelvin) => ok((value+d!(459.67))*d!(5)/d!(9)), - (Fahrenheit, Celsius) => ok((value-d!(32))/d!(1.8)), - _ => Err(format!("Error converting temperature {:?} to {:?}", number.unit, to_unit)), - } + // let ok = |new_value| Ok(Number::with_unit(new_value, to_unit)); + let primitive_u = number.primitive_unit(); + if number.primitive_unit() == vec![(Kelvin, 1)] { + todo!(); + // match (number.unit.0, to_unit) { + // (Kelvin, Kelvin) => ok(value), + // (Kelvin, Celsius) => ok(value - d!(273.15)), + // (Kelvin, Fahrenheit) => ok(value * d!(1.8) - d!(459.67)), + // (Celsius, Celsius) => ok(value), + // (Celsius, Kelvin) => ok(value + d!(273.15)), + // (Celsius, Fahrenheit) => ok(value * d!(1.8) + d!(32)), + // (Fahrenheit, Fahrenheit) => ok(value), + // (Fahrenheit, Kelvin) => ok((value + d!(459.67)) * d!(5) / d!(9)), + // (Fahrenheit, Celsius) => ok((value - d!(32)) / d!(1.8)), + // _ => Err(format!( + // "Error converting temperature {:?} to {:?}", + // number.unit, to_unit + // )), + // } } else { - let conversion_factor = get_conversion_factor(number.unit, to_unit); - ok(number.value * conversion_factor) + // let conversion_factor = get_conversion_factor(number.unit, to_unit); + + let source_weight = combined_weight(&number.unit); + let target_weight = combined_weight(&to_unit); + + Ok(Number { + value: number.value * source_weight / target_weight, + unit: to_unit.to_vec(), + }) } } /// If one of two provided [`Number`]s has a larger [`Unit`] than the other, convert /// the large one to the unit of the small one. pub fn convert_to_lowest(left: Number, right: Number) -> Result<(Number, Number), String> { - if left.unit.weight() == right.unit.weight() { - Ok((left, right)) - } else if left.unit.weight() > right.unit.weight() { - let left_converted = convert(left, right.unit)?; - Ok((left_converted, right)) - } else { - let right_converted = convert(right, left.unit)?; - Ok((left, right_converted)) - } + todo!(); + // if left.unit.weight() == right.unit.weight() { + // Ok((left, right)) + // } else if left.unit.weight() > right.unit.weight() { + // let left_converted = convert(left, right.unit)?; + // Ok((left_converted, right)) + // } else { + // let right_converted = convert(right, left.unit)?; + // Ok((left, right_converted)) + // } } /// Return the sum of two [`Number`]s pub fn add(left: Number, right: Number) -> Result { if left.unit == right.unit { - Ok(Number::new(left.value + right.value, left.unit)) - } else if left.unit.category() == right.unit.category() && left.unit.category() != Temperature { + Ok(Number::with_unit(left.value + right.value, left.unit)) + } else if left.primitive_unit() == right.primitive_unit() + && !left.contains_primitive(Temperature) + { let (left, right) = convert_to_lowest(left, right)?; - Ok(Number::new(left.value + right.value, left.unit)) + Ok(Number::with_unit(left.value + right.value, left.unit)) } else { Err(format!("Cannot add {:?} and {:?}", left.unit, right.unit)) } @@ -409,165 +502,203 @@ pub fn add(left: Number, right: Number) -> Result { /// Subtract a [`Number`] from another [`Number`] pub fn subtract(left: Number, right: Number) -> Result { if left.unit == right.unit { - Ok(Number::new(left.value - right.value, left.unit)) - } else if left.unit.category() == right.unit.category() && left.unit.category() != Temperature { + Ok(Number::with_unit(left.value - right.value, left.unit)) + } else if left.primitive_unit() == right.primitive_unit() + && !left.contains_primitive(Temperature) + { let (left, right) = convert_to_lowest(left, right)?; - Ok(Number::new(left.value - right.value, left.unit)) + Ok(Number::with_unit(left.value - right.value, left.unit)) } else { - Err(format!("Cannot subtract {:?} by {:?}", left.unit, right.unit)) + Err(format!( + "Cannot subtract {:?} by {:?}", + left.unit, right.unit + )) } } /// Convert a [`Number`] to an ideal unit. -/// +/// /// If you have 1,000,000 millimeters, this will return 1 kilometer. -/// +/// /// This only affects units of `Length`, `Time`, `Area`, `Volume`, /// `Energy`, `Power`, `ElectricCurrent`, `Resistance`, and `Voltage`. /// Other units are passed through. pub fn to_ideal_unit(number: Number) -> Number { - let value = number.value * number.unit.weight(); - if number.unit.category() == Length { - if value >= d!(1000000000000000000) { // ≈ 0.1 light years - return Number::new(value/LightYear.weight(), LightYear) - } else if value >= d!(1000000) { // 1 km - return Number::new(value/Kilometer.weight(), Kilometer) - } else if value >= d!(1000) { // 1 m - return Number::new(value/Meter.weight(), Meter) - } else if value >= d!(10) { // 1 cm - return Number::new(value/Centimeter.weight(), Centimeter) - } else { - return Number::new(value, Millimeter) - } - } else if number.unit.category() == Time { - if value >= d!(31556952000000000) { - return Number::new(value/Year.weight(), Year); - } else if value >= d!(86400000000000) { - return Number::new(value/Day.weight(), Day); - } else if value >= d!(3600000000000) { - return Number::new(value/Hour.weight(), Hour); - } else if value >= d!(60000000000) { - return Number::new(value/Minute.weight(), Minute); - } else if value >= d!(1000000000) { - return Number::new(value/Second.weight(), Second); - } else if value >= d!(1000000) { - return Number::new(value/Millisecond.weight(), Millisecond); - } else if value >= d!(1000) { - return Number::new(value/Microsecond.weight(), Microsecond); - } else { - return Number::new(value, Nanosecond); - } - } else if number.unit.category() == Area { - if value >= d!(1000000000000) { // 1 km2 - return Number::new(value/SquareKilometer.weight(), SquareKilometer) - } else if value >= d!(10000000000) { // 1 hectare - return Number::new(value/Hectare.weight(), Hectare) - } else if value >= d!(1000000) { // 1 m2 - return Number::new(value/SquareMeter.weight(), SquareMeter) - } else if value >= d!(100) { // 1 cm2 - return Number::new(value/SquareCentimeter.weight(), SquareCentimeter) - } else { - return Number::new(value, SquareMillimeter) - } - } else if number.unit.category() == Volume { - if value >= d!(1000000000000000000) { // 1 km3 - return Number::new(value/CubicKilometer.weight(), CubicKilometer) - } else if value >= d!(1000000000) { // 1 m3 - return Number::new(value/CubicMeter.weight(), CubicMeter) - } else if value >= d!(1000000) { // 1 l - return Number::new(value/Liter.weight(), Liter) - } else if value >= d!(1000) { // 1 ml - return Number::new(value/Milliliter.weight(), Milliliter) - } else { - return Number::new(value, CubicMillimeter) - } - } else if number.unit.category() == Energy { - if value >= d!(3600000000000000000) { // 1 petawatthour - return Number::new(value/PetawattHour.weight(), PetawattHour) - } else if value >= d!(3600000000000000) { // 1 terawatthour - return Number::new(value/TerawattHour.weight(), TerawattHour) - } else if value >= d!(3600000000000) { // 1 gigawatthour - return Number::new(value/GigawattHour.weight(), GigawattHour) - } else if value >= d!(3600000000) { // 1 megawatthour - return Number::new(value/MegawattHour.weight(), MegawattHour) - } else if value >= d!(3600000) { // 1 kilowatthour - return Number::new(value/KilowattHour.weight(), KilowattHour) - } else if value >= d!(3600) { // 1 watthour - return Number::new(value/WattHour.weight(), WattHour) - } else if value >= d!(1) { // 1 joule - return Number::new(value, Joule) - } else { - return Number::new(value/Millijoule.weight(), Millijoule) - } - } else if number.unit.category() == Power { - if value >= d!(1000000000000000) { // 1 petawatt - return Number::new(value/Petawatt.weight(), Petawatt) - } else if value >= d!(1000000000000) { // 1 terawatt - return Number::new(value/Terawatt.weight(), Terawatt) - } else if value >= d!(1000000000) { // 1 gigawatt - return Number::new(value/Gigawatt.weight(), Gigawatt) - } else if value >= d!(1000000) { // megawatt - return Number::new(value/Megawatt.weight(), Megawatt) - } else if value >= d!(1000) { // 1 kilowatt - return Number::new(value/Kilowatt.weight(), Kilowatt) - } else if value >= d!(1) { // 1 watt - return Number::new(value, Watt) - } else { - return Number::new(value/Milliwatt.weight(), Milliwatt) - } - } else if number.unit.category() == ElectricCurrent { - if value >= d!(1000) { // 1 kiloampere - return Number::new(value/Kiloampere.weight(), Kiloampere) - } else if value >= d!(1) { // 1 ampere - return Number::new(value, Ampere) - } else { - return Number::new(value/Milliampere.weight(), Milliampere) - } - } else if number.unit.category() == Resistance { - if value >= d!(1000) { // 1 kiloohm - return Number::new(value/Kiloohm.weight(), Kiloohm) - } else if value >= d!(1) { // 1 ohm - return Number::new(value, Ohm) - } else { - return Number::new(value/Milliohm.weight(), Milliohm) - } - } else if number.unit.category() == Voltage { - if value >= d!(1000) { // 1 kilovolt - return Number::new(value/Kilovolt.weight(), Kilovolt) - } else if value >= d!(1) { // 1 volt - return Number::new(value, Volt) - } else { - return Number::new(value/Millivolt.weight(), Millivolt) - } - } - number + todo!(); + // let value = number.value * number.unit.weight(); + // if number.unit.category() == Length { + // if value >= d!(1000000000000000000) { + // // ≈ 0.1 light years + // return Number::new(value / LightYear.weight(), LightYear); + // } else if value >= d!(1000000) { + // // 1 km + // return Number::new(value / Kilometer.weight(), Kilometer); + // } else if value >= d!(1000) { + // // 1 m + // return Number::new(value / Meter.weight(), Meter); + // } else if value >= d!(10) { + // // 1 cm + // return Number::new(value / Centimeter.weight(), Centimeter); + // } else { + // return Number::new(value, Millimeter); + // } + // } else if number.unit.category() == Time { + // if value >= d!(31556952000000000) { + // return Number::new(value / Year.weight(), Year); + // } else if value >= d!(86400000000000) { + // return Number::new(value / Day.weight(), Day); + // } else if value >= d!(3600000000000) { + // return Number::new(value / Hour.weight(), Hour); + // } else if value >= d!(60000000000) { + // return Number::new(value / Minute.weight(), Minute); + // } else if value >= d!(1000000000) { + // return Number::new(value / Second.weight(), Second); + // } else if value >= d!(1000000) { + // return Number::new(value / Millisecond.weight(), Millisecond); + // } else if value >= d!(1000) { + // return Number::new(value / Microsecond.weight(), Microsecond); + // } else { + // return Number::new(value, Nanosecond); + // } + // } else if number.unit.category() == Area { + // if value >= d!(1000000000000) { + // // 1 km2 + // return Number::new(value / SquareKilometer.weight(), SquareKilometer); + // } else if value >= d!(10000000000) { + // // 1 hectare + // return Number::new(value / Hectare.weight(), Hectare); + // } else if value >= d!(1000000) { + // // 1 m2 + // return Number::new(value / SquareMeter.weight(), SquareMeter); + // } else if value >= d!(100) { + // // 1 cm2 + // return Number::new(value / SquareCentimeter.weight(), SquareCentimeter); + // } else { + // return Number::new(value, SquareMillimeter); + // } + // } else if number.unit.category() == Volume { + // if value >= d!(1000000000000000000) { + // // 1 km3 + // return Number::new(value / CubicKilometer.weight(), CubicKilometer); + // } else if value >= d!(1000000000) { + // // 1 m3 + // return Number::new(value / CubicMeter.weight(), CubicMeter); + // } else if value >= d!(1000000) { + // // 1 l + // return Number::new(value / Liter.weight(), Liter); + // } else if value >= d!(1000) { + // // 1 ml + // return Number::new(value / Milliliter.weight(), Milliliter); + // } else { + // return Number::new(value, CubicMillimeter); + // } + // } else if number.unit.category() == Energy { + // if value >= d!(3600000000000000000) { + // // 1 petawatthour + // return Number::new(value / PetawattHour.weight(), PetawattHour); + // } else if value >= d!(3600000000000000) { + // // 1 terawatthour + // return Number::new(value / TerawattHour.weight(), TerawattHour); + // } else if value >= d!(3600000000000) { + // // 1 gigawatthour + // return Number::new(value / GigawattHour.weight(), GigawattHour); + // } else if value >= d!(3600000000) { + // // 1 megawatthour + // return Number::new(value / MegawattHour.weight(), MegawattHour); + // } else if value >= d!(3600000) { + // // 1 kilowatthour + // return Number::new(value / KilowattHour.weight(), KilowattHour); + // } else if value >= d!(3600) { + // // 1 watthour + // return Number::new(value / WattHour.weight(), WattHour); + // } else if value >= d!(1) { + // // 1 joule + // return Number::new(value, Joule); + // } else { + // return Number::new(value / Millijoule.weight(), Millijoule); + // } + // } else if number.unit.category() == Power { + // if value >= d!(1000000000000000) { + // // 1 petawatt + // return Number::new(value / Petawatt.weight(), Petawatt); + // } else if value >= d!(1000000000000) { + // // 1 terawatt + // return Number::new(value / Terawatt.weight(), Terawatt); + // } else if value >= d!(1000000000) { + // // 1 gigawatt + // return Number::new(value / Gigawatt.weight(), Gigawatt); + // } else if value >= d!(1000000) { + // // megawatt + // return Number::new(value / Megawatt.weight(), Megawatt); + // } else if value >= d!(1000) { + // // 1 kilowatt + // return Number::new(value / Kilowatt.weight(), Kilowatt); + // } else if value >= d!(1) { + // // 1 watt + // return Number::new(value, Watt); + // } else { + // return Number::new(value / Milliwatt.weight(), Milliwatt); + // } + // } else if number.unit.category() == ElectricCurrent { + // if value >= d!(1000) { + // // 1 kiloampere + // return Number::new(value / Kiloampere.weight(), Kiloampere); + // } else if value >= d!(1) { + // // 1 ampere + // return Number::new(value, Ampere); + // } else { + // return Number::new(value / Milliampere.weight(), Milliampere); + // } + // } else if number.unit.category() == Resistance { + // if value >= d!(1000) { + // // 1 kiloohm + // return Number::new(value / Kiloohm.weight(), Kiloohm); + // } else if value >= d!(1) { + // // 1 ohm + // return Number::new(value, Ohm); + // } else { + // return Number::new(value / Milliohm.weight(), Milliohm); + // } + // } else if number.unit.category() == Voltage { + // if value >= d!(1000) { + // // 1 kilovolt + // return Number::new(value / Kilovolt.weight(), Kilovolt); + // } else if value >= d!(1) { + // // 1 volt + // return Number::new(value, Volt); + // } else { + // return Number::new(value / Millivolt.weight(), Millivolt); + // } + // } + // number } /// Convert a [`Number`] to an ideal [`Joule`] unit, if the number is a unit of [`Energy`]. pub fn to_ideal_joule_unit(number: Number) -> Number { - let value = number.value * number.unit.weight(); - if number.unit.category() == Energy { - if value >= d!(1000000000000) { // 1 terajoule - return Number::new(value/Terajoule.weight(), Terajoule) - } else if value >= d!(1000000000) { // 1 gigajoule - return Number::new(value/Gigajoule.weight(), Gigajoule) - } else if value >= d!(1000000) { // 1 megajoule - return Number::new(value/Megajoule.weight(), Megajoule) - } else if value >= d!(1000) { // 1 kilojoule - return Number::new(value/Kilojoule.weight(), Kilojoule) - } else if value >= d!(1) { // 1 joule - return Number::new(value/Joule.weight(), Joule) - } else { - return Number::new(value/Millijoule.weight(), Millijoule) - } - } - number + todo!(); + // let value = number.value * number.unit.weight(); + // if number.unit.category() == Energy { + // if value >= d!(1000000000000) { // 1 terajoule + // return Number::new(value/Terajoule.weight(), Terajoule) + // } else if value >= d!(1000000000) { // 1 gigajoule + // return Number::new(value/Gigajoule.weight(), Gigajoule) + // } else if value >= d!(1000000) { // 1 megajoule + // return Number::new(value/Megajoule.weight(), Megajoule) + // } else if value >= d!(1000) { // 1 kilojoule + // return Number::new(value/Kilojoule.weight(), Kilojoule) + // } else if value >= d!(1) { // 1 joule + // return Number::new(value/Joule.weight(), Joule) + // } else { + // return Number::new(value/Millijoule.weight(), Millijoule) + // } + // } + // number } /// Multiply two [`Number`]s -/// +/// /// - Temperatures don't work -/// - If you multiply [`NoType`] with any other unit, the result gets that other unit +/// - If you multiply a unit with a unitless number, the result gets that other unit /// - If you multiply [`Length`] with [`Length`], the result has a unit of [`Area`], etc. /// - If you multiply [`Speed`] with [`Time`], the result has a unit of [`Length`] /// - If you multiply [`Voltage`] with [`ElectricCurrent`], the result has a unit of [`Power`] @@ -578,131 +709,132 @@ pub fn multiply(left: Number, right: Number) -> Result { } fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result { - let lcat = left.unit.category(); - let rcat = right.unit.category(); - if left.unit == NoUnit && right.unit == NoUnit { - // 3 * 2 - Ok(Number::new(left.value * right.value, left.unit)) - } else if left.unit.category() == Temperature || right.unit.category() == Temperature { - // if temperature - Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit)) - } else if left.unit == NoUnit && right.unit != NoUnit { - // 3 * 2 anyunit - Ok(Number::new(left.value * right.value, right.unit)) - } else if lcat == Length && rcat == Length { - // length * length - let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) - } else if lcat == Length && rcat == Area { - // length * area - let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, CubicMillimeter))) - } else if lcat == Speed && rcat == Time { - // 1 km/h * 1h - let kph_value = left.value * left.unit.weight(); - let hours = convert(right, Hour)?; - let result = kph_value * hours.value; - let final_unit = match left.unit { - KilometersPerHour => Kilometer, - MetersPerSecond => Meter, - MilesPerHour => Mile, - FeetPerSecond => Foot, - Knot => NauticalMile, - _ => Meter, - }; - let kilometers = Number::new(result, Kilometer); - Ok(convert(kilometers, final_unit)?) - } else if lcat == DataTransferRate && rcat == Time { - // 8 megabytes per second * 1 minute - let data_rate_value = left.value * left.unit.weight(); - let seconds = convert(right, Second)?; - let result = data_rate_value * seconds.value; - let final_unit = match left.unit { - BitsPerSecond => Bit, - KilobitsPerSecond => Kilobit, - MegabitsPerSecond => Megabit, - GigabitsPerSecond => Gigabit, - TerabitsPerSecond => Terabit, - PetabitsPerSecond => Petabit, - ExabitsPerSecond => Exabit, - ZettabitsPerSecond => Zettabit, - YottabitsPerSecond => Yottabit, - KibibitsPerSecond => Kibibit, - MebibitsPerSecond => Mebibit, - GibibitsPerSecond => Gibibit, - TebibitsPerSecond => Tebibit, - PebibitsPerSecond => Pebibit, - ExbibitsPerSecond => Exbibit, - ZebibitsPerSecond => Zebibit, - YobibitsPerSecond => Yobibit, - BytesPerSecond => Byte, - KilobytesPerSecond => Kilobyte, - MegabytesPerSecond => Megabyte, - GigabytesPerSecond => Gigabyte, - TerabytesPerSecond => Terabyte, - PetabytesPerSecond => Petabyte, - ExabytesPerSecond => Exabyte, - ZettabytesPerSecond => Zettabyte, - YottabytesPerSecond => Yottabyte, - KibibytesPerSecond => Kibibyte, - MebibytesPerSecond => Mebibyte, - GibibytesPerSecond => Gibibyte, - TebibytesPerSecond => Tebibyte, - PebibytesPerSecond => Pebibyte, - ExbibytesPerSecond => Exbibyte, - ZebibytesPerSecond => Zebibyte, - YobibytesPerSecond => Yobibyte, - _ => Bit, - }; - let data_storage = Number::new(result, Bit); - Ok(convert(data_storage, final_unit)?) - } else if lcat == FlopRate && rcat == Time { - // 8 megaFLOP per second * 1 minute - let compute_perf_value = left.value * left.unit.weight(); - let seconds = convert(right, Second)?; - let result = compute_perf_value * seconds.value; - let final_unit = match left.unit { - FlopPerSecond => Flop, - KiloFlopPerSecond => KiloFlop, - MegaFlopPerSecond => MegaFlop, - GigaFlopPerSecond => GigaFlop, - TeraFlopPerSecond => TeraFlop, - PetaFlopPerSecond => PetaFlop, - ExaFlopPerSecond => ExaFlop, - ZettaFlopPerSecond => ZettaFlop, - YottaFlopPerSecond => YottaFlop, - RonnaFlopPerSecond => RonnaFlop, - QuettaFlopPerSecond => QuettaFlop, - _ => Flop, - }; - let compute_work = Number::new(result, Flop); - Ok(convert(compute_work, final_unit)?) - } else if lcat == Voltage && rcat == ElectricCurrent { - // 1 volt * 1 ampere = 1 watt - let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Watt))) - } else if lcat == ElectricCurrent && rcat == Resistance { - // 1 amp * 1 ohm = 1 volt - let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Watt))) - } else if lcat == Power && rcat == Time { - // 1 watt * 1 second = 1 joule - let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight() / Unit::Second.weight()); - match right.unit { - Second => Ok(to_ideal_joule_unit(Number::new(result, Joule))), - _ => Ok(to_ideal_unit(Number::new(result, Joule))), - } - } else if swapped { - Err(format!("Cannot multiply {:?} and {:?}", right.unit, left.unit)) - } else { - actual_multiply(right, left, true) - } + todo!(); + // let lcat = left.unit.category(); + // let rcat = right.unit.category(); + // if left.unit == NoUnit && right.unit == NoUnit { + // // 3 * 2 + // Ok(Number::new(left.value * right.value, left.unit)) + // } else if left.unit.category() == Temperature || right.unit.category() == Temperature { + // // if temperature + // Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit)) + // } else if left.unit == NoUnit && right.unit != NoUnit { + // // 3 * 2 anyunit + // Ok(Number::new(left.value * right.value, right.unit)) + // } else if lcat == Length && rcat == Length { + // // length * length + // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) + // } else if lcat == Length && rcat == Area { + // // length * area + // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, CubicMillimeter))) + // } else if lcat == Speed && rcat == Time { + // // 1 km/h * 1h + // let kph_value = left.value * left.unit.weight(); + // let hours = convert(right, Hour)?; + // let result = kph_value * hours.value; + // let final_unit = match left.unit { + // KilometersPerHour => Kilometer, + // MetersPerSecond => Meter, + // MilesPerHour => Mile, + // FeetPerSecond => Foot, + // Knot => NauticalMile, + // _ => Meter, + // }; + // let kilometers = Number::new(result, Kilometer); + // Ok(convert(kilometers, final_unit)?) + // } else if lcat == DataTransferRate && rcat == Time { + // // 8 megabytes per second * 1 minute + // let data_rate_value = left.value * left.unit.weight(); + // let seconds = convert(right, Second)?; + // let result = data_rate_value * seconds.value; + // let final_unit = match left.unit { + // BitsPerSecond => Bit, + // KilobitsPerSecond => Kilobit, + // MegabitsPerSecond => Megabit, + // GigabitsPerSecond => Gigabit, + // TerabitsPerSecond => Terabit, + // PetabitsPerSecond => Petabit, + // ExabitsPerSecond => Exabit, + // ZettabitsPerSecond => Zettabit, + // YottabitsPerSecond => Yottabit, + // KibibitsPerSecond => Kibibit, + // MebibitsPerSecond => Mebibit, + // GibibitsPerSecond => Gibibit, + // TebibitsPerSecond => Tebibit, + // PebibitsPerSecond => Pebibit, + // ExbibitsPerSecond => Exbibit, + // ZebibitsPerSecond => Zebibit, + // YobibitsPerSecond => Yobibit, + // BytesPerSecond => Byte, + // KilobytesPerSecond => Kilobyte, + // MegabytesPerSecond => Megabyte, + // GigabytesPerSecond => Gigabyte, + // TerabytesPerSecond => Terabyte, + // PetabytesPerSecond => Petabyte, + // ExabytesPerSecond => Exabyte, + // ZettabytesPerSecond => Zettabyte, + // YottabytesPerSecond => Yottabyte, + // KibibytesPerSecond => Kibibyte, + // MebibytesPerSecond => Mebibyte, + // GibibytesPerSecond => Gibibyte, + // TebibytesPerSecond => Tebibyte, + // PebibytesPerSecond => Pebibyte, + // ExbibytesPerSecond => Exbibyte, + // ZebibytesPerSecond => Zebibyte, + // YobibytesPerSecond => Yobibyte, + // _ => Bit, + // }; + // let data_storage = Number::new(result, Bit); + // Ok(convert(data_storage, final_unit)?) + // } else if lcat == FlopRate && rcat == Time { + // // 8 megaFLOP per second * 1 minute + // let compute_perf_value = left.value * left.unit.weight(); + // let seconds = convert(right, Second)?; + // let result = compute_perf_value * seconds.value; + // let final_unit = match left.unit { + // FlopPerSecond => Flop, + // KiloFlopPerSecond => KiloFlop, + // MegaFlopPerSecond => MegaFlop, + // GigaFlopPerSecond => GigaFlop, + // TeraFlopPerSecond => TeraFlop, + // PetaFlopPerSecond => PetaFlop, + // ExaFlopPerSecond => ExaFlop, + // ZettaFlopPerSecond => ZettaFlop, + // YottaFlopPerSecond => YottaFlop, + // RonnaFlopPerSecond => RonnaFlop, + // QuettaFlopPerSecond => QuettaFlop, + // _ => Flop, + // }; + // let compute_work = Number::new(result, Flop); + // Ok(convert(compute_work, final_unit)?) + // } else if lcat == Voltage && rcat == ElectricCurrent { + // // 1 volt * 1 ampere = 1 watt + // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Watt))) + // } else if lcat == ElectricCurrent && rcat == Resistance { + // // 1 amp * 1 ohm = 1 volt + // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Watt))) + // } else if lcat == Power && rcat == Time { + // // 1 watt * 1 second = 1 joule + // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight() / Unit::Second.weight()); + // match right.unit { + // Second => Ok(to_ideal_joule_unit(Number::new(result, Joule))), + // _ => Ok(to_ideal_unit(Number::new(result, Joule))), + // } + // } else if swapped { + // Err(format!("Cannot multiply {:?} and {:?}", right.unit, left.unit)) + // } else { + // actual_multiply(right, left, true) + // } } /// Divide a [`Number`] by another [`Number`] -/// +/// /// - Temperatures don't work -/// - If you divide a unit by that same unit, the result has a unit of [`NoType`] +/// - If you divide a unit by that same unit, the result has no unit /// - If you divide [`Volume`] by [`Length`], the result has a unit of [`Area`], etc. /// - If you divide [`Length`] by [`Time`], the result has a unit of [`Speed`] /// - If you divide [`Length`] by [`Speed`], the result has a unit of [`Time`] @@ -712,141 +844,145 @@ fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result Result { - let lcat = left.unit.category(); - let rcat = right.unit.category(); - if left.unit == NoUnit && right.unit == NoUnit { - // 3 / 2 - Ok(Number::new(left.value / right.value, left.unit)) - } else if lcat == Temperature || rcat == Temperature { - // if temperature - Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) - } else if left.unit != NoUnit && right.unit == NoUnit { - // 1 km / 2 - Ok(Number::new(left.value / right.value, left.unit)) - } else if lcat == rcat { - // 4 km / 2 km - let (left, right) = convert_to_lowest(left, right)?; - Ok(Number::new(left.value / right.value, NoUnit)) - } else if (lcat == Area && rcat == Length) || (lcat == Volume && rcat == Area) { - // 1 km2 / 1 km, 1 km3 / 1 km2 - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Millimeter))) - } else if lcat == Volume && rcat == Length { - // 1 km3 / 1 km - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) - } else if lcat == Length && rcat == Time { - // 1 km / 2s - let final_unit = match (left.unit, right.unit) { - (Kilometer, Hour) => KilometersPerHour, - (Meter, Second) => MetersPerSecond, - (Mile, Hour) => MilesPerHour, - (Foot, Second) => FeetPerSecond, - (NauticalMile, Hour) => Knot, - _ => KilometersPerHour, - }; - let kilometers = convert(left, Kilometer)?; - let hours = convert(right, Hour)?; - let kph = Number::new(kilometers.value / hours.value, KilometersPerHour); - Ok(convert(kph, final_unit)?) - } else if lcat == Length && rcat == Speed { - // 12 km / 100 kph - let kilometers = convert(left, Kilometer)?; - let kilometers_per_hour = convert(right, KilometersPerHour)?; - let hour = Number::new(kilometers.value / kilometers_per_hour.value, Hour); - Ok(to_ideal_unit(hour)) - } else if lcat == DigitalStorage && rcat == DataTransferRate { - // 1 kilobit / 1 bit per second - let bits = convert(left, Bit)?; - let bits_per_second = convert(right, BitsPerSecond)?; - let seconds = Number::new(bits.value / bits_per_second.value, Second); - Ok(to_ideal_unit(seconds)) - } else if lcat == FlopCount && rcat == FlopRate { - // 1 kiloFLOP / 1 FLOP per second - let flop = convert(left, Flop)?; - let flop_per_second = convert(right, FlopPerSecond)?; - let seconds = Number::new(flop.value / flop_per_second.value, Second); - Ok(to_ideal_unit(seconds)) - } else if lcat == Power && rcat == ElectricCurrent { - // 1 watt / 1 ampere = 1 volt - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Volt))) - } else if lcat == Voltage && rcat == ElectricCurrent { - // 1 volt / 1 ampere = 1 ohm - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Ohm))) - } else if lcat == Voltage && rcat == Resistance { - // 1 volt / 1 ohm = 1 amp - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Ampere))) - } else if lcat == Power && rcat == Voltage { - // 1 watt / 1 volt = 1 amp - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - Ok(to_ideal_unit(Number::new(result, Ampere))) - } else if lcat == Energy && rcat == Time { - // 1 joule / 1 second = 1 watt - let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight() / Unit::Second.weight()); - Ok(to_ideal_unit(Number::new(result, Watt))) - } else { - Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) - } + todo!(); + // let lcat = left.unit.category(); + // let rcat = right.unit.category(); + // if left.unit == NoUnit && right.unit == NoUnit { + // // 3 / 2 + // Ok(Number::new(left.value / right.value, left.unit)) + // } else if lcat == Temperature || rcat == Temperature { + // // if temperature + // Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) + // } else if left.has_unit() && right.unit == NoUnit { + // // 1 km / 2 + // Ok(Number::new(left.value / right.value, left.unit)) + // } else if lcat == rcat { + // // 4 km / 2 km + // let (left, right) = convert_to_lowest(left, right)?; + // Ok(Number::new(left.value / right.value, NoUnit)) + // } else if (lcat == Area && rcat == Length) || (lcat == Volume && rcat == Area) { + // // 1 km2 / 1 km, 1 km3 / 1 km2 + // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Millimeter))) + // } else if lcat == Volume && rcat == Length { + // // 1 km3 / 1 km + // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) + // } else if lcat == Length && rcat == Time { + // // 1 km / 2s + // let final_unit = match (left.unit, right.unit) { + // (Kilometer, Hour) => KilometersPerHour, + // (Meter, Second) => MetersPerSecond, + // (Mile, Hour) => MilesPerHour, + // (Foot, Second) => FeetPerSecond, + // (NauticalMile, Hour) => Knot, + // _ => KilometersPerHour, + // }; + // let kilometers = convert(left, Kilometer)?; + // let hours = convert(right, Hour)?; + // let kph = Number::new(kilometers.value / hours.value, KilometersPerHour); + // Ok(convert(kph, final_unit)?) + // } else if lcat == Length && rcat == Speed { + // // 12 km / 100 kph + // let kilometers = convert(left, Kilometer)?; + // let kilometers_per_hour = convert(right, KilometersPerHour)?; + // let hour = Number::new(kilometers.value / kilometers_per_hour.value, Hour); + // Ok(to_ideal_unit(hour)) + // } else if lcat == DigitalStorage && rcat == DataTransferRate { + // // 1 kilobit / 1 bit per second + // let bits = convert(left, Bit)?; + // let bits_per_second = convert(right, BitsPerSecond)?; + // let seconds = Number::new(bits.value / bits_per_second.value, Second); + // Ok(to_ideal_unit(seconds)) + // } else if lcat == FlopCount && rcat == FlopRate { + // // 1 kiloFLOP / 1 FLOP per second + // let flop = convert(left, Flop)?; + // let flop_per_second = convert(right, FlopPerSecond)?; + // let seconds = Number::new(flop.value / flop_per_second.value, Second); + // Ok(to_ideal_unit(seconds)) + // } else if lcat == Power && rcat == ElectricCurrent { + // // 1 watt / 1 ampere = 1 volt + // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Volt))) + // } else if lcat == Voltage && rcat == ElectricCurrent { + // // 1 volt / 1 ampere = 1 ohm + // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Ohm))) + // } else if lcat == Voltage && rcat == Resistance { + // // 1 volt / 1 ohm = 1 amp + // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Ampere))) + // } else if lcat == Power && rcat == Voltage { + // // 1 watt / 1 volt = 1 amp + // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); + // Ok(to_ideal_unit(Number::new(result, Ampere))) + // } else if lcat == Energy && rcat == Time { + // // 1 joule / 1 second = 1 watt + // let result = (left.value * left.unit.weight()) + // / (right.value * right.unit.weight() / Unit::Second.weight()); + // Ok(to_ideal_unit(Number::new(result, Watt))) + // } else { + // Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) + // } } /// Modulo a [`Number`] by another [`Number`]. -/// +/// /// `left` and `right` need to have the same [`UnitType`], and the result will have that same [`UnitType`]. /// /// Temperatures don't work. pub fn modulo(left: Number, right: Number) -> Result { - if left.unit.category() == Temperature || right.unit.category() == Temperature { - // if temperature - Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) - } else if left.unit.category() == right.unit.category() { - // 5 km % 3 m - let (left, right) = convert_to_lowest(left, right)?; - Ok(Number::new(left.value % right.value, left.unit)) - } else { - Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) - } + todo!(); + // if left.unit.category() == Temperature || right.unit.category() == Temperature { + // // if temperature + // Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) + // } else if left.unit.category() == right.unit.category() { + // // 5 km % 3 m + // let (left, right) = convert_to_lowest(left, right)?; + // Ok(Number::new(left.value % right.value, left.unit)) + // } else { + // Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) + // } } /// Returns a [`Number`] to the power of another [`Number`] -/// +/// /// - If you take [`Length`] to the power of [`NoType`], the result has a unit of [`Area`]. /// - If you take [`Length`] to the power of [`Length`], the result has a unit of [`Area`] /// - If you take [`Length`] to the power of [`Area`], the result has a unit of [`Volume`] /// - etc. pub fn pow(left: Number, right: Number) -> Result { - // I tried converting `right` to use powi, but somehow that was slower - let lcat = left.unit.category(); - let rcat = left.unit.category(); - if left.unit == NoUnit && right.unit == NoUnit { - // 3 ^ 2 - Ok(Number::new(left.value.pow(right.value), left.unit)) - } else if right.value == d!(1) && right.unit == NoUnit { - Ok(left) - } else if lcat == Length && right.unit == NoUnit && right.value == d!(2) { - // x km ^ 2 - let result = (left.value * left.unit.weight()).pow(right.value); - Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) - } else if lcat == Length && right.unit == NoUnit && right.value == d!(3) { - // x km ^ 3 - let result = (left.value * left.unit.weight()).pow(right.value); - Ok(to_ideal_unit(Number::new(result, CubicMillimeter))) - } else { - Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit)) - } + todo!(); + // // I tried converting `right` to use powi, but somehow that was slower + // let lcat = left.unit.category(); + // let rcat = left.unit.category(); + // if left.unit == NoUnit && right.unit == NoUnit { + // // 3 ^ 2 + // Ok(Number::new(left.value.pow(right.value), left.unit)) + // } else if right.value == d!(1) && right.unit == NoUnit { + // Ok(left) + // } else if lcat == Length && right.unit == NoUnit && right.value == d!(2) { + // // x km ^ 2 + // let result = (left.value * left.unit.weight()).pow(right.value); + // Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) + // } else if lcat == Length && right.unit == NoUnit && right.value == d!(3) { + // // x km ^ 3 + // let result = (left.value * left.unit.weight()).pow(right.value); + // Ok(to_ideal_unit(Number::new(result, CubicMillimeter))) + // } else { + // Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit)) + // } } #[cfg(test)] mod tests { use fastnum::decimal::Context; -use super::*; + use super::*; macro_rules! assert_float_eq { ( $actual:expr, $expected:literal ) => { assert!(($actual - $expected).abs() < f64::EPSILON); - } + }; } #[test] @@ -970,37 +1106,124 @@ use super::*; assert_float_eq!(convert_test(1024.0, Zebibyte, Yobibyte), 1.0); assert_float_eq!(convert_test(1000.0, BitsPerSecond, KilobitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, KilobitsPerSecond, MegabitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, MegabitsPerSecond, GigabitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, GigabitsPerSecond, TerabitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, TerabitsPerSecond, PetabitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, PetabitsPerSecond, ExabitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, ExabitsPerSecond, ZettabitsPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, ZettabitsPerSecond, YottabitsPerSecond), 1.0); + assert_float_eq!( + convert_test(1000.0, KilobitsPerSecond, MegabitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, MegabitsPerSecond, GigabitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, GigabitsPerSecond, TerabitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, TerabitsPerSecond, PetabitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, PetabitsPerSecond, ExabitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, ExabitsPerSecond, ZettabitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, ZettabitsPerSecond, YottabitsPerSecond), + 1.0 + ); assert_float_eq!(convert_test(1024.0, BitsPerSecond, KibibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, KibibitsPerSecond, MebibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, MebibitsPerSecond, GibibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, GibibitsPerSecond, TebibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, TebibitsPerSecond, PebibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, PebibitsPerSecond, ExbibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, ExbibitsPerSecond, ZebibitsPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, ZebibitsPerSecond, YobibitsPerSecond), 1.0); + assert_float_eq!( + convert_test(1024.0, KibibitsPerSecond, MebibitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, MebibitsPerSecond, GibibitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, GibibitsPerSecond, TebibitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, TebibitsPerSecond, PebibitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, PebibitsPerSecond, ExbibitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, ExbibitsPerSecond, ZebibitsPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, ZebibitsPerSecond, YobibitsPerSecond), + 1.0 + ); assert_float_eq!(convert_test(8.0, BitsPerSecond, BytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, BytesPerSecond, KilobytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, KilobytesPerSecond, MegabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, MegabytesPerSecond, GigabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, GigabytesPerSecond, TerabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, TerabytesPerSecond, PetabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, PetabytesPerSecond, ExabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, ExabytesPerSecond, ZettabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, ZettabytesPerSecond, YottabytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, KibibytesPerSecond, MebibytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, MebibytesPerSecond, GibibytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, GibibytesPerSecond, TebibytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, TebibytesPerSecond, PebibytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, PebibytesPerSecond, ExbibytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, ExbibytesPerSecond, ZebibytesPerSecond), 1.0); - assert_float_eq!(convert_test(1024.0, ZebibytesPerSecond, YobibytesPerSecond), 1.0); + assert_float_eq!( + convert_test(1000.0, BytesPerSecond, KilobytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, KilobytesPerSecond, MegabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, MegabytesPerSecond, GigabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, GigabytesPerSecond, TerabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, TerabytesPerSecond, PetabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, PetabytesPerSecond, ExabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, ExabytesPerSecond, ZettabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, ZettabytesPerSecond, YottabytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, KibibytesPerSecond, MebibytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, MebibytesPerSecond, GibibytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, GibibytesPerSecond, TebibytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, TebibytesPerSecond, PebibytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, PebibytesPerSecond, ExbibytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, ExbibytesPerSecond, ZebibytesPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1024.0, ZebibytesPerSecond, YobibytesPerSecond), + 1.0 + ); assert_float_eq!(convert_test(1000.0, Flop, KiloFlop), 1.0); assert_float_eq!(convert_test(1000.0, KiloFlop, MegaFlop), 1.0); @@ -1014,15 +1237,42 @@ use super::*; assert_float_eq!(convert_test(1000.0, RonnaFlop, QuettaFlop), 1.0); assert_float_eq!(convert_test(1000.0, FlopPerSecond, KiloFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, KiloFlopPerSecond, MegaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, MegaFlopPerSecond, GigaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, GigaFlopPerSecond, TeraFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, TeraFlopPerSecond, PetaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, PetaFlopPerSecond, ExaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, ExaFlopPerSecond, ZettaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, ZettaFlopPerSecond, YottaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, YottaFlopPerSecond, RonnaFlopPerSecond), 1.0); - assert_float_eq!(convert_test(1000.0, RonnaFlopPerSecond, QuettaFlopPerSecond), 1.0); + assert_float_eq!( + convert_test(1000.0, KiloFlopPerSecond, MegaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, MegaFlopPerSecond, GigaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, GigaFlopPerSecond, TeraFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, TeraFlopPerSecond, PetaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, PetaFlopPerSecond, ExaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, ExaFlopPerSecond, ZettaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, ZettaFlopPerSecond, YottaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, YottaFlopPerSecond, RonnaFlopPerSecond), + 1.0 + ); + assert_float_eq!( + convert_test(1000.0, RonnaFlopPerSecond, QuettaFlopPerSecond), + 1.0 + ); assert_float_eq!(convert_test(1000.0, Millijoule, Joule), 1.0); assert_float_eq!(convert_test(1000.0, Joule, Kilojoule), 1.0); @@ -1046,8 +1296,18 @@ use super::*; assert_float_eq!(convert_test(1000.0, Megawatt, Gigawatt), 1.0); assert_float_eq!(convert_test(1000.0, Gigawatt, Terawatt), 1.0); assert_float_eq!(convert_test(1000.0, Terawatt, Petawatt), 1.0); - assert_float_eq!(convert_test(17.584264210333, Watt, BritishThermalUnitsPerMinute), 1.0); - assert_float_eq!(convert_test(59.999999999999317571673374406441452784, BritishThermalUnitsPerHour, BritishThermalUnitsPerMinute), 1.0); + assert_float_eq!( + convert_test(17.584264210333, Watt, BritishThermalUnitsPerMinute), + 1.0 + ); + assert_float_eq!( + convert_test( + 59.999999999999317571673374406441452784, + BritishThermalUnitsPerHour, + BritishThermalUnitsPerMinute + ), + 1.0 + ); assert_float_eq!(convert_test(745.6998715822702, Watt, Horsepower), 1.0); assert_float_eq!(convert_test(735.49875, Watt, MetricHorsepower), 1.0); @@ -1066,7 +1326,10 @@ use super::*; assert_float_eq!(convert_test(100.0, Pascal, Millibar), 1.0); assert_float_eq!(convert_test(1000.0, Millibar, Bar), 1.0); assert_float_eq!(convert_test(3386.389, Pascal, InchOfMercury), 1.0); - assert_float_eq!(convert_test(6894.757293168361, Pascal, PoundsPerSquareInch), 1.0); + assert_float_eq!( + convert_test(6894.757293168361, Pascal, PoundsPerSquareInch), + 1.0 + ); assert_float_eq!(convert_test(133.3223684210526, Pascal, Torr), 1.0); assert_float_eq!(convert_test(1000.0, Hertz, Kilohertz), 1.0); -- 2.51.2 From 267a2031c92643d356255b4d5f243e389d463f58 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 00:27:03 +0200 Subject: [PATCH 02/15] Add conversion --- src/units.rs | 57 ++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/units.rs b/src/units.rs index b096931..9610f65 100644 --- a/src/units.rs +++ b/src/units.rs @@ -403,17 +403,6 @@ create_units!( Fahrenheit: (Temperature, d!(0), "fahrenheit", "fahrenheit"), ); -/// Returns the conversion factor between two units. -/// -/// The conversion factor is what you need to multiply `unit` with to get -/// `to_unit`. For example, the conversion factor from 1 minute to 1 second -/// is 60. -/// -/// This is not sufficient for [`Temperature`] units. -pub fn get_conversion_factor(unit: Unit, to_unit: Unit) -> D128 { - unit.weight() / to_unit.weight() -} - fn combined_weight(unit: &[(Unit, isize)]) -> D128 { unit.iter().fold(D128::from(1), |acc, (u, exp)| { acc * integer_power(u.weight(), *exp) @@ -438,28 +427,34 @@ pub fn convert(number: Number, to_unit: Vec<(Unit, isize)>) -> Result ok(value), - // (Kelvin, Celsius) => ok(value - d!(273.15)), - // (Kelvin, Fahrenheit) => ok(value * d!(1.8) - d!(459.67)), - // (Celsius, Celsius) => ok(value), - // (Celsius, Kelvin) => ok(value + d!(273.15)), - // (Celsius, Fahrenheit) => ok(value * d!(1.8) + d!(32)), - // (Fahrenheit, Fahrenheit) => ok(value), - // (Fahrenheit, Kelvin) => ok((value + d!(459.67)) * d!(5) / d!(9)), - // (Fahrenheit, Celsius) => ok((value - d!(32)) / d!(1.8)), - // _ => Err(format!( - // "Error converting temperature {:?} to {:?}", - // number.unit, to_unit - // )), - // } + if number.unit.len() != 1 + || to_unit.len() != 1 + || number.unit[0].1 != 1 + || to_unit[0].1 != 1 + { + return Err(format!( + "Cannot convert from {:?} to {:?}", + number.unit, to_unit + )); + } + let ok = |new_value| Ok(Number::with_unit(new_value, to_unit.clone())); + match (number.unit[0].0, to_unit[0].0) { + (Kelvin, Kelvin) => ok(value), + (Kelvin, Celsius) => ok(value - d!(273.15)), + (Kelvin, Fahrenheit) => ok(value * d!(1.8) - d!(459.67)), + (Celsius, Celsius) => ok(value), + (Celsius, Kelvin) => ok(value + d!(273.15)), + (Celsius, Fahrenheit) => ok(value * d!(1.8) + d!(32)), + (Fahrenheit, Fahrenheit) => ok(value), + (Fahrenheit, Kelvin) => ok((value + d!(459.67)) * d!(5) / d!(9)), + (Fahrenheit, Celsius) => ok((value - d!(32)) / d!(1.8)), + _ => Err(format!( + "Error converting temperature {:?} to {:?}", + number.unit, to_unit + )), + } } else { - // let conversion_factor = get_conversion_factor(number.unit, to_unit); - let source_weight = combined_weight(&number.unit); let target_weight = combined_weight(&to_unit); -- 2.51.2 From 449339b0e260ced8d76b1f2bde1f4f67b714e38d Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 00:32:18 +0200 Subject: [PATCH 03/15] Add convert_to_lowest() --- src/units.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/units.rs b/src/units.rs index 9610f65..d7c9484 100644 --- a/src/units.rs +++ b/src/units.rs @@ -468,16 +468,16 @@ pub fn convert(number: Number, to_unit: Vec<(Unit, isize)>) -> Result Result<(Number, Number), String> { - todo!(); - // if left.unit.weight() == right.unit.weight() { - // Ok((left, right)) - // } else if left.unit.weight() > right.unit.weight() { - // let left_converted = convert(left, right.unit)?; - // Ok((left_converted, right)) - // } else { - // let right_converted = convert(right, left.unit)?; - // Ok((left, right_converted)) - // } + assert!(left.primitive_unit() == right.primitive_unit()); + if combined_weight(&left.unit) == combined_weight(&right.unit) { + Ok((left, right)) + } else if combined_weight(&left.unit) > combined_weight(&right.unit) { + let left_converted = convert(left, right.unit.clone())?; + Ok((left_converted, right)) + } else { + let right_converted = convert(right, left.unit.clone())?; + Ok((left, right_converted)) + } } /// Return the sum of two [`Number`]s -- 2.51.2 From 1964fa88da36d5b3b0f67f6e542a403c4c9a31c7 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 01:00:34 +0200 Subject: [PATCH 04/15] Fix unit printing --- src/lib.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3533940..ed2a694 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,31 +92,30 @@ impl Number { pub fn contains_primitive(&self, unit: UnitType) -> bool { self.unit.iter().any(|(u, _)| u.category() == unit) } - pub fn singular(&self) -> String { + fn get_unit_string(&self, plural: bool) -> String { let mut s = String::new(); - for (i, unit) in self.unit.iter().enumerate() { - if i != 0 { - match unit.1.is_positive() { - true => s.push_str(" * "), - false => s.push_str(" / "), - } + for unit in &self.unit { + match unit.1.is_positive() { + true if s.len() == 0 => (), + true => s.push_str(" * "), + false => s.push_str(" / "), + } + match plural { + false => s.push_str(unit.0.singular()), + true => s.push_str(unit.0.plural()), + }; + if unit.1.abs() >= 2 { + s.push_str("^"); + s.push_str(&unit.1.to_string()); } - s.push_str(unit.0.plural()); } s } + pub fn singular(&self) -> String { + self.get_unit_string(false) + } pub fn plural(&self) -> String { - let mut s = String::new(); - for (i, unit) in self.unit.iter().enumerate() { - if i != 0 { - match unit.1.is_positive() { - true => s.push_str(" * "), - false => s.push_str(" / "), - } - } - s.push_str(unit.0.plural()); - } - s + self.get_unit_string(true) } } impl Display for Number { -- 2.51.2 From 43c734be7a8e8a5e4d62cda091b2c90998fd514a Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 01:36:35 +0200 Subject: [PATCH 05/15] Add multiplication --- src/units.rs | 374 +++++++++++++++++++++++---------------------------- 1 file changed, 172 insertions(+), 202 deletions(-) diff --git a/src/units.rs b/src/units.rs index d7c9484..2e88334 100644 --- a/src/units.rs +++ b/src/units.rs @@ -64,8 +64,8 @@ impl UnitType { // Temperature => vec![(Temperature, 1)], Time => vec![(Nanosecond, 1)], Length => vec![(Millimeter, 1)], - Area => vec![(SquareMillimeter, 2)], - Volume => vec![(CubicMillimeter, 3)], + Area => vec![(Millimeter, 2)], + Volume => vec![(Millimeter, 3)], Mass => vec![(Gram, 1)], DigitalStorage => vec![(Bit, 1)], DataTransferRate => vec![(Bit, 1), (Nanosecond, -1)], @@ -519,153 +519,153 @@ pub fn subtract(left: Number, right: Number) -> Result { /// `Energy`, `Power`, `ElectricCurrent`, `Resistance`, and `Voltage`. /// Other units are passed through. pub fn to_ideal_unit(number: Number) -> Number { - todo!(); - // let value = number.value * number.unit.weight(); - // if number.unit.category() == Length { - // if value >= d!(1000000000000000000) { - // // ≈ 0.1 light years - // return Number::new(value / LightYear.weight(), LightYear); - // } else if value >= d!(1000000) { - // // 1 km - // return Number::new(value / Kilometer.weight(), Kilometer); - // } else if value >= d!(1000) { - // // 1 m - // return Number::new(value / Meter.weight(), Meter); - // } else if value >= d!(10) { - // // 1 cm - // return Number::new(value / Centimeter.weight(), Centimeter); - // } else { - // return Number::new(value, Millimeter); - // } - // } else if number.unit.category() == Time { - // if value >= d!(31556952000000000) { - // return Number::new(value / Year.weight(), Year); - // } else if value >= d!(86400000000000) { - // return Number::new(value / Day.weight(), Day); - // } else if value >= d!(3600000000000) { - // return Number::new(value / Hour.weight(), Hour); - // } else if value >= d!(60000000000) { - // return Number::new(value / Minute.weight(), Minute); - // } else if value >= d!(1000000000) { - // return Number::new(value / Second.weight(), Second); - // } else if value >= d!(1000000) { - // return Number::new(value / Millisecond.weight(), Millisecond); - // } else if value >= d!(1000) { - // return Number::new(value / Microsecond.weight(), Microsecond); - // } else { - // return Number::new(value, Nanosecond); - // } - // } else if number.unit.category() == Area { - // if value >= d!(1000000000000) { - // // 1 km2 - // return Number::new(value / SquareKilometer.weight(), SquareKilometer); - // } else if value >= d!(10000000000) { - // // 1 hectare - // return Number::new(value / Hectare.weight(), Hectare); - // } else if value >= d!(1000000) { - // // 1 m2 - // return Number::new(value / SquareMeter.weight(), SquareMeter); - // } else if value >= d!(100) { - // // 1 cm2 - // return Number::new(value / SquareCentimeter.weight(), SquareCentimeter); - // } else { - // return Number::new(value, SquareMillimeter); - // } - // } else if number.unit.category() == Volume { - // if value >= d!(1000000000000000000) { - // // 1 km3 - // return Number::new(value / CubicKilometer.weight(), CubicKilometer); - // } else if value >= d!(1000000000) { - // // 1 m3 - // return Number::new(value / CubicMeter.weight(), CubicMeter); - // } else if value >= d!(1000000) { - // // 1 l - // return Number::new(value / Liter.weight(), Liter); - // } else if value >= d!(1000) { - // // 1 ml - // return Number::new(value / Milliliter.weight(), Milliliter); - // } else { - // return Number::new(value, CubicMillimeter); - // } - // } else if number.unit.category() == Energy { - // if value >= d!(3600000000000000000) { - // // 1 petawatthour - // return Number::new(value / PetawattHour.weight(), PetawattHour); - // } else if value >= d!(3600000000000000) { - // // 1 terawatthour - // return Number::new(value / TerawattHour.weight(), TerawattHour); - // } else if value >= d!(3600000000000) { - // // 1 gigawatthour - // return Number::new(value / GigawattHour.weight(), GigawattHour); - // } else if value >= d!(3600000000) { - // // 1 megawatthour - // return Number::new(value / MegawattHour.weight(), MegawattHour); - // } else if value >= d!(3600000) { - // // 1 kilowatthour - // return Number::new(value / KilowattHour.weight(), KilowattHour); - // } else if value >= d!(3600) { - // // 1 watthour - // return Number::new(value / WattHour.weight(), WattHour); - // } else if value >= d!(1) { - // // 1 joule - // return Number::new(value, Joule); - // } else { - // return Number::new(value / Millijoule.weight(), Millijoule); - // } - // } else if number.unit.category() == Power { - // if value >= d!(1000000000000000) { - // // 1 petawatt - // return Number::new(value / Petawatt.weight(), Petawatt); - // } else if value >= d!(1000000000000) { - // // 1 terawatt - // return Number::new(value / Terawatt.weight(), Terawatt); - // } else if value >= d!(1000000000) { - // // 1 gigawatt - // return Number::new(value / Gigawatt.weight(), Gigawatt); - // } else if value >= d!(1000000) { - // // megawatt - // return Number::new(value / Megawatt.weight(), Megawatt); - // } else if value >= d!(1000) { - // // 1 kilowatt - // return Number::new(value / Kilowatt.weight(), Kilowatt); - // } else if value >= d!(1) { - // // 1 watt - // return Number::new(value, Watt); - // } else { - // return Number::new(value / Milliwatt.weight(), Milliwatt); - // } - // } else if number.unit.category() == ElectricCurrent { - // if value >= d!(1000) { - // // 1 kiloampere - // return Number::new(value / Kiloampere.weight(), Kiloampere); - // } else if value >= d!(1) { - // // 1 ampere - // return Number::new(value, Ampere); - // } else { - // return Number::new(value / Milliampere.weight(), Milliampere); - // } - // } else if number.unit.category() == Resistance { - // if value >= d!(1000) { - // // 1 kiloohm - // return Number::new(value / Kiloohm.weight(), Kiloohm); - // } else if value >= d!(1) { - // // 1 ohm - // return Number::new(value, Ohm); - // } else { - // return Number::new(value / Milliohm.weight(), Milliohm); - // } - // } else if number.unit.category() == Voltage { - // if value >= d!(1000) { - // // 1 kilovolt - // return Number::new(value / Kilovolt.weight(), Kilovolt); - // } else if value >= d!(1) { - // // 1 volt - // return Number::new(value, Volt); - // } else { - // return Number::new(value / Millivolt.weight(), Millivolt); - // } - // } - // number + let value = number.value * combined_weight(&number.unit); + let primitive = number.primitive_unit(); + if primitive == Length.primitive() { + if value >= d!(1000000000000000000) { + // ≈ 0.1 light years + return Number::with_basic_unit(value / LightYear.weight(), LightYear); + } else if value >= d!(1000000) { + // 1 km + return Number::with_basic_unit(value / Kilometer.weight(), Kilometer); + } else if value >= d!(1000) { + // 1 m + return Number::with_basic_unit(value / Meter.weight(), Meter); + } else if value >= d!(10) { + // 1 cm + return Number::with_basic_unit(value / Centimeter.weight(), Centimeter); + } else { + return Number::with_basic_unit(value, Millimeter); + } + } else if primitive == Time.primitive() { + if value >= d!(31556952000000000) { + return Number::with_basic_unit(value / Year.weight(), Year); + } else if value >= d!(86400000000000) { + return Number::with_basic_unit(value / Day.weight(), Day); + } else if value >= d!(3600000000000) { + return Number::with_basic_unit(value / Hour.weight(), Hour); + } else if value >= d!(60000000000) { + return Number::with_basic_unit(value / Minute.weight(), Minute); + } else if value >= d!(1000000000) { + return Number::with_basic_unit(value / Second.weight(), Second); + } else if value >= d!(1000000) { + return Number::with_basic_unit(value / Millisecond.weight(), Millisecond); + } else if value >= d!(1000) { + return Number::with_basic_unit(value / Microsecond.weight(), Microsecond); + } else { + return Number::with_basic_unit(value, Nanosecond); + } + } else if primitive == Area.primitive() { + if value >= d!(1000000000000) { + // 1 km2 + return Number::with_basic_unit(value / SquareKilometer.weight(), SquareKilometer); + } else if value >= d!(10000000000) { + // 1 hectare + return Number::with_basic_unit(value / Hectare.weight(), Hectare); + } else if value >= d!(1000000) { + // 1 m2 + return Number::with_basic_unit(value / SquareMeter.weight(), SquareMeter); + } else if value >= d!(100) { + // 1 cm2 + return Number::with_basic_unit(value / SquareCentimeter.weight(), SquareCentimeter); + } else { + return Number::with_basic_unit(value, SquareMillimeter); + } + } else if primitive == Volume.primitive() { + if value >= d!(1000000000000000000) { + // 1 km3 + return Number::with_basic_unit(value / CubicKilometer.weight(), CubicKilometer); + } else if value >= d!(1000000000) { + // 1 m3 + return Number::with_basic_unit(value / CubicMeter.weight(), CubicMeter); + } else if value >= d!(1000000) { + // 1 l + return Number::with_basic_unit(value / Liter.weight(), Liter); + } else if value >= d!(1000) { + // 1 ml + return Number::with_basic_unit(value / Milliliter.weight(), Milliliter); + } else { + return Number::with_basic_unit(value, CubicMillimeter); + } + } else if primitive == Energy.primitive() { + if value >= d!(3600000000000000000) { + // 1 petawatthour + return Number::with_basic_unit(value / PetawattHour.weight(), PetawattHour); + } else if value >= d!(3600000000000000) { + // 1 terawatthour + return Number::with_basic_unit(value / TerawattHour.weight(), TerawattHour); + } else if value >= d!(3600000000000) { + // 1 gigawatthour + return Number::with_basic_unit(value / GigawattHour.weight(), GigawattHour); + } else if value >= d!(3600000000) { + // 1 megawatthour + return Number::with_basic_unit(value / MegawattHour.weight(), MegawattHour); + } else if value >= d!(3600000) { + // 1 kilowatthour + return Number::with_basic_unit(value / KilowattHour.weight(), KilowattHour); + } else if value >= d!(3600) { + // 1 watthour + return Number::with_basic_unit(value / WattHour.weight(), WattHour); + } else if value >= d!(1) { + // 1 joule + return Number::with_basic_unit(value, Joule); + } else { + return Number::with_basic_unit(value / Millijoule.weight(), Millijoule); + } + } else if primitive == Power.primitive() { + if value >= d!(1000000000000000) { + // 1 petawatt + return Number::with_basic_unit(value / Petawatt.weight(), Petawatt); + } else if value >= d!(1000000000000) { + // 1 terawatt + return Number::with_basic_unit(value / Terawatt.weight(), Terawatt); + } else if value >= d!(1000000000) { + // 1 gigawatt + return Number::with_basic_unit(value / Gigawatt.weight(), Gigawatt); + } else if value >= d!(1000000) { + // megawatt + return Number::with_basic_unit(value / Megawatt.weight(), Megawatt); + } else if value >= d!(1000) { + // 1 kilowatt + return Number::with_basic_unit(value / Kilowatt.weight(), Kilowatt); + } else if value >= d!(1) { + // 1 watt + return Number::with_basic_unit(value, Watt); + } else { + return Number::with_basic_unit(value / Milliwatt.weight(), Milliwatt); + } + } else if primitive == ElectricCurrent.primitive() { + if value >= d!(1000) { + // 1 kiloampere + return Number::with_basic_unit(value / Kiloampere.weight(), Kiloampere); + } else if value >= d!(1) { + // 1 ampere + return Number::with_basic_unit(value, Ampere); + } else { + return Number::with_basic_unit(value / Milliampere.weight(), Milliampere); + } + } else if primitive == Resistance.primitive() { + if value >= d!(1000) { + // 1 kiloohm + return Number::with_basic_unit(value / Kiloohm.weight(), Kiloohm); + } else if value >= d!(1) { + // 1 ohm + return Number::with_basic_unit(value, Ohm); + } else { + return Number::with_basic_unit(value / Milliohm.weight(), Milliohm); + } + } else if primitive == Voltage.primitive() { + if value >= d!(1000) { + // 1 kilovolt + return Number::with_basic_unit(value / Kilovolt.weight(), Kilovolt); + } else if value >= d!(1) { + // 1 volt + return Number::with_basic_unit(value, Volt); + } else { + return Number::with_basic_unit(value / Millivolt.weight(), Millivolt); + } + } + number } /// Convert a [`Number`] to an ideal [`Joule`] unit, if the number is a unit of [`Energy`]. @@ -700,45 +700,30 @@ pub fn to_ideal_joule_unit(number: Number) -> Number { /// - If you multiply [`ElectricCurrent`] with [`Resistance`], the result has a unit of [`Voltage`] /// - If you multiply [`Power`] with [`Time`], the result has a unit of [`Energy`] pub fn multiply(left: Number, right: Number) -> Result { - actual_multiply(left, right, false) -} + if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { + println!("TEMP?"); + // if temperature + Err(format!( + "Cannot multiply {:?} and {:?}", + left.unit, right.unit + )) + } else { + let mut new_number = left; + new_number.value *= right.value; + for (r_unit, r_exp) in right.unit { + let existing = new_number.unit.iter_mut().find(|(u, _)| u == &r_unit); + match existing { + Some(existing) => existing.1 += r_exp, + None => new_number.unit.push((r_unit, r_exp)), + } + } + let new_number = to_ideal_unit(new_number); + Ok(new_number) + } + // todo: 8 megabytes per second * 1 minute + // todo: 8 megaFLOP per second * 1 minute + // todo: 1 amp * 1 ohm = 1 volt -fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result { - todo!(); - // let lcat = left.unit.category(); - // let rcat = right.unit.category(); - // if left.unit == NoUnit && right.unit == NoUnit { - // // 3 * 2 - // Ok(Number::new(left.value * right.value, left.unit)) - // } else if left.unit.category() == Temperature || right.unit.category() == Temperature { - // // if temperature - // Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit)) - // } else if left.unit == NoUnit && right.unit != NoUnit { - // // 3 * 2 anyunit - // Ok(Number::new(left.value * right.value, right.unit)) - // } else if lcat == Length && rcat == Length { - // // length * length - // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) - // } else if lcat == Length && rcat == Area { - // // length * area - // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, CubicMillimeter))) - // } else if lcat == Speed && rcat == Time { - // // 1 km/h * 1h - // let kph_value = left.value * left.unit.weight(); - // let hours = convert(right, Hour)?; - // let result = kph_value * hours.value; - // let final_unit = match left.unit { - // KilometersPerHour => Kilometer, - // MetersPerSecond => Meter, - // MilesPerHour => Mile, - // FeetPerSecond => Foot, - // Knot => NauticalMile, - // _ => Meter, - // }; - // let kilometers = Number::new(result, Kilometer); - // Ok(convert(kilometers, final_unit)?) // } else if lcat == DataTransferRate && rcat == Time { // // 8 megabytes per second * 1 minute // let data_rate_value = left.value * left.unit.weight(); @@ -804,25 +789,10 @@ fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result Ok(to_ideal_joule_unit(Number::new(result, Joule))), - // _ => Ok(to_ideal_unit(Number::new(result, Joule))), - // } - // } else if swapped { - // Err(format!("Cannot multiply {:?} and {:?}", right.unit, left.unit)) - // } else { - // actual_multiply(right, left, true) // } } -- 2.51.2 From 80b2a28122d5364df0ccd1d0d754ec5546eeae41 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 03:09:54 +0200 Subject: [PATCH 06/15] Fix weights --- src/units.rs | 391 ++++++++++++++++++++++----------------------------- 1 file changed, 165 insertions(+), 226 deletions(-) diff --git a/src/units.rs b/src/units.rs index 2e88334..3c00d79 100644 --- a/src/units.rs +++ b/src/units.rs @@ -44,51 +44,23 @@ pub enum UnitType { impl UnitType { fn primitive(&self) -> Vec<(Unit, isize)> { match self { - // Time => vec![(Time, 1)], - // Length => vec![(Length, 1)], - // Area => vec![(Length, 2)], - // Volume => vec![(Length, 3)], - // Mass => vec![(Mass, 1)], - // DigitalStorage => vec![(DigitalStorage, 1)], - // DataTransferRate => vec![(DigitalStorage, 1), (Time, -1)], - // FlopCount => vec![(FlopCount, 1)], - // FlopRate => vec![(FlopCount, 1), (Time, -1)], - // Energy => vec![(Mass, 1), (Length, 2), (Time, -2)], - // Power => vec![(Mass, 1), (Length, 2), (Time, -3)], - // ElectricCurrent => vec![(ElectricCurrent, 1)], - // Resistance => vec![(Mass, 1), (Length, 2), (Time, -3), (ElectricCurrent, -2)], - // Voltage => vec![(Mass, 1), (Length, 2), (Time, -3), (ElectricCurrent, -1)], - // Pressure => vec![(Mass, 1), (Length, -1), (Time, -2)], - // Frequency => vec![(Time, -1)], - // Speed => vec![(Length, 1), (Time, -1)], - // Temperature => vec![(Temperature, 1)], - Time => vec![(Nanosecond, 1)], - Length => vec![(Millimeter, 1)], - Area => vec![(Millimeter, 2)], - Volume => vec![(Millimeter, 3)], - Mass => vec![(Gram, 1)], + Time => vec![(Second, 1)], + Length => vec![(Meter, 1)], + Area => vec![(Meter, 2)], + Volume => vec![(Meter, 3)], + Mass => vec![(Kilogram, 1)], DigitalStorage => vec![(Bit, 1)], - DataTransferRate => vec![(Bit, 1), (Nanosecond, -1)], + DataTransferRate => vec![(Bit, 1), (Second, -1)], FlopCount => vec![(Flop, 1)], - FlopRate => vec![(Flop, 1), (Nanosecond, -1)], - Energy => vec![(Gram, 1), (SquareMillimeter, 2), (Nanosecond, -2)], - Power => vec![(Gram, 1), (SquareMillimeter, 2), (Nanosecond, -3)], + FlopRate => vec![(Flop, 1), (Second, -1)], + Energy => vec![(Kilogram, 1), (Meter, 2), (Second, -2)], + Power => vec![(Kilogram, 1), (Meter, 2), (Second, -3)], ElectricCurrent => vec![(Ampere, 1)], - Resistance => vec![ - (Gram, 1), - (SquareMillimeter, 2), - (Nanosecond, -3), - (Ampere, -2), - ], - Voltage => vec![ - (Gram, 1), - (SquareMillimeter, 2), - (Nanosecond, -3), - (Ampere, -1), - ], - Pressure => vec![(Gram, 1), (Millimeter, -1), (Nanosecond, -2)], - Frequency => vec![(Nanosecond, -1)], - Speed => vec![(Millimeter, 1), (Nanosecond, -1)], + Resistance => vec![(Kilogram, 1), (Meter, 2), (Second, -3), (Ampere, -2)], + Voltage => vec![(Kilogram, 1), (Meter, 2), (Second, -3), (Ampere, -1)], + Pressure => vec![(Kilogram, 1), (Meter, -1), (Second, -2)], + Frequency => vec![(Second, -1)], + Speed => vec![(Meter, 1), (Second, -1)], Temperature => vec![(Kelvin, 1)], } } @@ -156,84 +128,84 @@ macro_rules! create_units { } create_units!( - Nanosecond: (Time, d!(1), "nanosecond", "nanoseconds"), - Microsecond: (Time, d!(1000), "microsecond", "microseconds"), - Millisecond: (Time, d!(1000000), "millisecond", "milliseconds"), - Second: (Time, d!(1000000000), "second", "seconds"), - Minute: (Time, d!(60000000000), "minute", "minutes"), - Hour: (Time, d!(3600000000000), "hour", "hours"), - Day: (Time, d!(86400000000000), "day", "days"), - Week: (Time, d!(604800000000000), "week", "weeks"), - Month: (Time, d!(2629746000000000), "month", "months"), - Quarter: (Time, d!(7889238000000000), "quarter", "quarters"), - Year: (Time, d!(31556952000000000), "year", "years"), - Decade: (Time, d!(315569520000000000), "decade", "decades"), - Century: (Time, d!(3155695200000000000), "century", "centuries"), - Millennium: (Time, d!(31556952000000000000), "millennium", "millennia"), + Nanosecond: (Time, d!(0.000000001), "nanosecond", "nanoseconds"), + Microsecond: (Time, d!(0.000001), "microsecond", "microseconds"), + Millisecond: (Time, d!(0.001), "millisecond", "milliseconds"), + Second: (Time, d!(1), "second", "seconds"), + Minute: (Time, d!(60), "minute", "minutes"), + Hour: (Time, d!(3600), "hour", "hours"), + Day: (Time, d!(86400), "day", "days"), + Week: (Time, d!(604800), "week", "weeks"), + Month: (Time, d!(2629746), "month", "months"), + Quarter: (Time, d!(7889238), "quarter", "quarters"), + Year: (Time, d!(31556952), "year", "years"), + Decade: (Time, d!(315569520), "decade", "decades"), + Century: (Time, d!(3155695200), "century", "centuries"), + Millennium: (Time, d!(31556952000), "millennium", "millennia"), - Millimeter: (Length, d!(1), "millimeter", "millimeters"), - Centimeter: (Length, d!(10), "centimeter", "centimeters"), - Decimeter: (Length, d!(100), "decimeter", "decimeters"), - Meter: (Length, d!(1000), "meter", "meters"), - Kilometer: (Length, d!(1000000), "kilometer", "kilometers"), - Inch: (Length, d!(25.4), "inch", "inches"), - Foot: (Length, d!(304.8), "foot", "feet"), - Yard: (Length, d!(914.4), "yard", "yards"), - Mile: (Length, d!(1609344), "mile", "miles"), + Millimeter: (Length, d!(0.001), "millimeter", "millimeters"), + Centimeter: (Length, d!(0.01), "centimeter", "centimeters"), + Decimeter: (Length, d!(0.1), "decimeter", "decimeters"), + Meter: (Length, d!(1), "meter", "meters"), + Kilometer: (Length, d!(1000), "kilometer", "kilometers"), + Inch: (Length, d!(0.0254), "inch", "inches"), + Foot: (Length, d!(0.3048), "foot", "feet"), + Yard: (Length, d!(0.9144), "yard", "yards"), + Mile: (Length, d!(1609.344), "mile", "miles"), // 1-dimensional only: - Marathon: (Length, d!(42195000), "marathon", "marathons"), - NauticalMile: (Length, d!(1852000), "nautical mile", "nautical miles"), - LightYear: (Length, d!(9460730472580800000), "light year", "light years"), - LightSecond: (Length, d!(299792458000), "light second", "light seconds"), + Marathon: (Length, d!(42195), "marathon", "marathons"), + NauticalMile: (Length, d!(1852), "nautical mile", "nautical miles"), + LightYear: (Length, d!(9460730472580800), "light year", "light years"), + LightSecond: (Length, d!(299792458), "light second", "light seconds"), - SquareMillimeter: (Area, d!(1), "square millimeter", "square millimeters"), - SquareCentimeter: (Area, d!(100), "square centimeter", "square centimeters"), - SquareDecimeter: (Area, d!(10000), "square decimeter", "square decimeters"), - SquareMeter: (Area, d!(1000000), "square meter", "square meters"), - SquareKilometer: (Area, d!(1000000000000), "square kilometer", "square kilometers"), - SquareInch: (Area, d!(645.16), "square inch", "square inches"), - SquareFoot: (Area, d!(92903.04), "square foot", "square feet"), - SquareYard: (Area, d!(836127.36), "square yard", "square yards"), - SquareMile: (Area, d!(2589988110336.00), "square mile", "square miles"), + SquareMillimeter: (Area, d!(0.000001), "square millimeter", "square millimeters"), + SquareCentimeter: (Area, d!(0.0001), "square centimeter", "square centimeters"), + SquareDecimeter: (Area, d!(0.01), "square decimeter", "square decimeters"), + SquareMeter: (Area, d!(1), "square meter", "square meters"), + SquareKilometer: (Area, d!(1000000), "square kilometer", "square kilometers"), + SquareInch: (Area, d!(0.00064516), "square inch", "square inches"), + SquareFoot: (Area, d!(0.09290304), "square foot", "square feet"), + SquareYard: (Area, d!(0.83612736), "square yard", "square yards"), + SquareMile: (Area, d!(2589988.110336), "square mile", "square miles"), // 2-dimensional only - Are: (Area, d!(100000000), "are", "ares"), - Decare: (Area, d!(1000000000), "decare", "decares"), - Hectare: (Area, d!(10000000000), "hectare", "hectares"), - Acre: (Area, d!(4046856422.40), "acre", "acres"), + Are: (Area, d!(100), "are", "ares"), + Decare: (Area, d!(1000), "decare", "decares"), + Hectare: (Area, d!(10000), "hectare", "hectares"), + Acre: (Area, d!(4046.8564224), "acre", "acres"), - CubicMillimeter: (Volume, d!(1), "cubic millimeter", "cubic millimeters"), - CubicCentimeter: (Volume, d!(1000), "cubic centimeter", "cubic centimeters"), - CubicDecimeter: (Volume, d!(1000000), "cubic decimeter", "cubic decimeters"), - CubicMeter: (Volume, d!(1000000000), "cubic meter", "cubic meters"), - CubicKilometer: (Volume, d!(1000000000000000000), "cubic kilometer", "cubic kilometers"), - CubicInch: (Volume, d!(16387.064), "cubic inch", "cubic inches"), - CubicFoot: (Volume, d!(28316846.592), "cubic foot", "cubic feet"), - CubicYard: (Volume, d!(764554857.984), "cubic yard", "cubic yards"), - CubicMile: (Volume, d!(4168181825440579584), "cubic mile", "cubic miles"), + CubicMillimeter: (Volume, d!(0.000000001), "cubic millimeter", "cubic millimeters"), + CubicCentimeter: (Volume, d!(0.000001), "cubic centimeter", "cubic centimeters"), + CubicDecimeter: (Volume, d!(0.001), "cubic decimeter", "cubic decimeters"), + CubicMeter: (Volume, d!(1), "cubic meter", "cubic meters"), + CubicKilometer: (Volume, d!(1000000000), "cubic kilometer", "cubic kilometers"), + CubicInch: (Volume, d!(0.000016387064), "cubic inch", "cubic inches"), + CubicFoot: (Volume, d!(0.028316846592), "cubic foot", "cubic feet"), + CubicYard: (Volume, d!(0.764554857984), "cubic yard", "cubic yards"), + CubicMile: (Volume, d!(4168181825.440579584), "cubic mile", "cubic miles"), // 3-dimensional only - Milliliter: (Volume, d!(1000), "milliliter", "milliliters"), - Centiliter: (Volume, d!(10000), "centiliter", "centiliters"), - Deciliter: (Volume, d!(100000), "deciliter", "deciliters"), - Liter: (Volume, d!(1000000), "liter", "liters"), - Teaspoon: (Volume, d!(4928.92159375), "teaspoon", "teaspoons"), - Tablespoon: (Volume, d!(14786.76478125), "tablespoon", "tablespoons"), - FluidOunce: (Volume, d!(29573.5295625), "fluid ounce", "fluid ounces"), - Cup: (Volume, d!(236588.2365), "cup", "cups"), - Pint: (Volume, d!(473176.473), "pint", "pints"), - Quart: (Volume, d!(946352.946), "quart", "quarts"), - Gallon: (Volume, d!(3785411.784), "gallon", "gallons"), - OilBarrel: (Volume, d!(158987294.928), "oil barrel", "oil barrels"), + Milliliter: (Volume, d!(0.000001), "milliliter", "milliliters"), + Centiliter: (Volume, d!(0.00001), "centiliter", "centiliters"), + Deciliter: (Volume, d!(0.0001), "deciliter", "deciliters"), + Liter: (Volume, d!(0.001), "liter", "liters"), + Teaspoon: (Volume, d!(0.00000492892159375), "teaspoon", "teaspoons"), + Tablespoon: (Volume, d!(0.00001478676478125), "tablespoon", "tablespoons"), + FluidOunce: (Volume, d!(0.0000295735295625), "fluid ounce", "fluid ounces"), + Cup: (Volume, d!(0.0002365882365), "cup", "cups"), + Pint: (Volume, d!(0.000473176473), "pint", "pints"), + Quart: (Volume, d!(0.000946352946), "quart", "quarts"), + Gallon: (Volume, d!(0.003785411784), "gallon", "gallons"), + OilBarrel: (Volume, d!(0.158987294928), "oil barrel", "oil barrels"), - Milligram: (Mass, d!(0.001), "milligram", "milligrams"), - Gram: (Mass, d!(1), "gram", "grams"), - Hectogram: (Mass, d!(100), "hectogram", "hectograms"), - Kilogram: (Mass, d!(1000), "kilogram", "kilograms"), - MetricTon: (Mass, d!(1000000), "metric ton", "metric tons"), - Ounce: (Mass, d!(28.349523125), "ounce", "ounces"), - Pound: (Mass, d!(453.59237), "pound", "pounds"), - Stone: (Mass, d!(6350.29318), "stone", "stones"), - ShortTon: (Mass, d!(907184.74), "short ton", "short tons"), - LongTon: (Mass, d!(1016046.9088), "long ton", "long tons"), + Milligram: (Mass, d!(0.000001), "milligram", "milligrams"), + Gram: (Mass, d!(0.001), "gram", "grams"), + Hectogram: (Mass, d!(0.1), "hectogram", "hectograms"), + Kilogram: (Mass, d!(1), "kilogram", "kilograms"), + MetricTon: (Mass, d!(1000), "metric ton", "metric tons"), + Ounce: (Mass, d!(0.028349523125), "ounce", "ounces"), + Pound: (Mass, d!(0.45359237), "pound", "pounds"), + Stone: (Mass, d!(6.35029318), "stone", "stones"), + ShortTon: (Mass, d!(907.18474), "short ton", "short tons"), + LongTon: (Mass, d!(1016.0469088), "long ton", "long tons"), Bit: (DigitalStorage, d!(1), "bit", "bits"), Kilobit: (DigitalStorage, d!(1000), "kilobit", "kilobits"), @@ -356,7 +328,8 @@ create_units!( // inexact: BritishThermalUnitsPerMinute: (Power, d!(17.584264210333), "british thermal unit per minute", "british thermal units per minute"), // inexact: - BritishThermalUnitsPerHour: (Power, d!(0.29307107017222), "british thermal unit per hour", "british thermal units per hour"), // exact according to wikipedia: + BritishThermalUnitsPerHour: (Power, d!(0.29307107017222), "british thermal unit per hour", "british thermal units per hour"), + // exact according to wikipedia: Horsepower: (Power, d!(745.69987158227022), "horsepower", "horsepower"), MetricHorsepower: (Power, d!(735.49875), "metric horsepower", "metric horsepower"), @@ -392,11 +365,13 @@ create_units!( Petahertz: (Frequency, d!(1000000000000000), "petahertz", "petahertz"), RevolutionsPerMinute: (Frequency, d!(60), "revolution per minute", "revolutions per minute"), - KilometersPerHour: (Speed, d!(1), "kilometer per hour", "kilometers per hour"), - MetersPerSecond: (Speed, d!(3.6), "meter per second", "meters per second"), - MilesPerHour: (Speed, d!(1.609344), "mile per hour", "miles per hour"), - FeetPerSecond: (Speed, d!(1.09728), "foot per second", "feet per second"), - Knot: (Speed, d!(1.852), "knot", "knots"), + // inexact: + KilometersPerHour: (Speed, d!(0.2777777778), "kilometer per hour", "kilometers per hour"), + MetersPerSecond: (Speed, d!(1), "meter per second", "meters per second"), + MilesPerHour: (Speed, d!(0.44704), "mile per hour", "miles per hour"), + FeetPerSecond: (Speed, d!(0.3048), "foot per second", "feet per second"), + // inexact: + Knot: (Speed, d!(0.5144444444), "knot", "knots"), Kelvin: (Temperature, d!(0), "kelvin", "kelvin"), Celsius: (Temperature, d!(0), "celsius", "celsius"), @@ -522,145 +497,131 @@ pub fn to_ideal_unit(number: Number) -> Number { let value = number.value * combined_weight(&number.unit); let primitive = number.primitive_unit(); if primitive == Length.primitive() { - if value >= d!(1000000000000000000) { - // ≈ 0.1 light years + if value >= 0.1 * LightYear.weight() { return Number::with_basic_unit(value / LightYear.weight(), LightYear); - } else if value >= d!(1000000) { - // 1 km + } else if value >= Kilometer.weight() { return Number::with_basic_unit(value / Kilometer.weight(), Kilometer); - } else if value >= d!(1000) { - // 1 m + } else if value >= Meter.weight() { return Number::with_basic_unit(value / Meter.weight(), Meter); - } else if value >= d!(10) { - // 1 cm + } else if value >= Centimeter.weight() { return Number::with_basic_unit(value / Centimeter.weight(), Centimeter); } else { - return Number::with_basic_unit(value, Millimeter); + return Number::with_basic_unit(value / Millimeter.weight(), Millimeter); } } else if primitive == Time.primitive() { - if value >= d!(31556952000000000) { + if value >= Year.weight() { return Number::with_basic_unit(value / Year.weight(), Year); - } else if value >= d!(86400000000000) { + } else if value >= Day.weight() { return Number::with_basic_unit(value / Day.weight(), Day); - } else if value >= d!(3600000000000) { + } else if value >= Hour.weight() { return Number::with_basic_unit(value / Hour.weight(), Hour); - } else if value >= d!(60000000000) { + } else if value >= Minute.weight() { return Number::with_basic_unit(value / Minute.weight(), Minute); - } else if value >= d!(1000000000) { + } else if value >= Second.weight() { return Number::with_basic_unit(value / Second.weight(), Second); - } else if value >= d!(1000000) { + } else if value >= Millisecond.weight() { return Number::with_basic_unit(value / Millisecond.weight(), Millisecond); - } else if value >= d!(1000) { + } else if value >= Microsecond.weight() { return Number::with_basic_unit(value / Microsecond.weight(), Microsecond); } else { - return Number::with_basic_unit(value, Nanosecond); + return Number::with_basic_unit(value / Nanosecond.weight(), Nanosecond); } } else if primitive == Area.primitive() { - if value >= d!(1000000000000) { - // 1 km2 + if value >= SquareKilometer.weight() { return Number::with_basic_unit(value / SquareKilometer.weight(), SquareKilometer); - } else if value >= d!(10000000000) { - // 1 hectare + } else if value >= Hectare.weight() { return Number::with_basic_unit(value / Hectare.weight(), Hectare); - } else if value >= d!(1000000) { - // 1 m2 + } else if value >= SquareMeter.weight() { return Number::with_basic_unit(value / SquareMeter.weight(), SquareMeter); - } else if value >= d!(100) { - // 1 cm2 + } else if value >= SquareCentimeter.weight() { return Number::with_basic_unit(value / SquareCentimeter.weight(), SquareCentimeter); } else { - return Number::with_basic_unit(value, SquareMillimeter); + return Number::with_basic_unit(value / SquareMillimeter.weight(), SquareMillimeter); } } else if primitive == Volume.primitive() { - if value >= d!(1000000000000000000) { - // 1 km3 + if value >= CubicKilometer.weight() { return Number::with_basic_unit(value / CubicKilometer.weight(), CubicKilometer); - } else if value >= d!(1000000000) { - // 1 m3 + } else if value >= CubicMeter.weight() { return Number::with_basic_unit(value / CubicMeter.weight(), CubicMeter); - } else if value >= d!(1000000) { - // 1 l + } else if value >= Liter.weight() { return Number::with_basic_unit(value / Liter.weight(), Liter); - } else if value >= d!(1000) { - // 1 ml + } else if value >= Milliliter.weight() { return Number::with_basic_unit(value / Milliliter.weight(), Milliliter); } else { - return Number::with_basic_unit(value, CubicMillimeter); + return Number::with_basic_unit(value / CubicMillimeter.weight(), CubicMillimeter); } } else if primitive == Energy.primitive() { - if value >= d!(3600000000000000000) { - // 1 petawatthour - return Number::with_basic_unit(value / PetawattHour.weight(), PetawattHour); - } else if value >= d!(3600000000000000) { - // 1 terawatthour - return Number::with_basic_unit(value / TerawattHour.weight(), TerawattHour); - } else if value >= d!(3600000000000) { - // 1 gigawatthour - return Number::with_basic_unit(value / GigawattHour.weight(), GigawattHour); - } else if value >= d!(3600000000) { - // 1 megawatthour - return Number::with_basic_unit(value / MegawattHour.weight(), MegawattHour); - } else if value >= d!(3600000) { - // 1 kilowatthour - return Number::with_basic_unit(value / KilowattHour.weight(), KilowattHour); - } else if value >= d!(3600) { - // 1 watthour - return Number::with_basic_unit(value / WattHour.weight(), WattHour); - } else if value >= d!(1) { - // 1 joule - return Number::with_basic_unit(value, Joule); + let has_second = number.unit.iter().find(|unit| unit.0 == Second).is_some(); + if has_second { + if value >= Terajoule.weight() { + return Number::with_basic_unit(value / Terajoule.weight(), Terajoule); + } else if value >= Gigajoule.weight() { + return Number::with_basic_unit(value / Gigajoule.weight(), Gigajoule); + } else if value >= Megajoule.weight() { + return Number::with_basic_unit(value / Megajoule.weight(), Megajoule); + } else if value >= Kilojoule.weight() { + return Number::with_basic_unit(value / Kilojoule.weight(), Kilojoule); + } else if value >= Joule.weight() { + return Number::with_basic_unit(value / Joule.weight(), Joule); + } else { + return Number::with_basic_unit(value / Millijoule.weight(), Millijoule); + } } else { - return Number::with_basic_unit(value / Millijoule.weight(), Millijoule); + if value >= PetawattHour.weight() { + return Number::with_basic_unit(value / PetawattHour.weight(), PetawattHour); + } else if value >= TerawattHour.weight() { + return Number::with_basic_unit(value / TerawattHour.weight(), TerawattHour); + } else if value >= GigawattHour.weight() { + return Number::with_basic_unit(value / GigawattHour.weight(), GigawattHour); + } else if value >= MegawattHour.weight() { + return Number::with_basic_unit(value / MegawattHour.weight(), MegawattHour); + } else if value >= KilowattHour.weight() { + return Number::with_basic_unit(value / KilowattHour.weight(), KilowattHour); + } else if value >= WattHour.weight() { + return Number::with_basic_unit(value / WattHour.weight(), WattHour); + } else if value >= Joule.weight() { + return Number::with_basic_unit(value / Joule.weight(), Joule); + } else { + return Number::with_basic_unit(value / Millijoule.weight(), Millijoule); + } } } else if primitive == Power.primitive() { - if value >= d!(1000000000000000) { - // 1 petawatt + if value >= Petawatt.weight() { return Number::with_basic_unit(value / Petawatt.weight(), Petawatt); - } else if value >= d!(1000000000000) { - // 1 terawatt + } else if value >= Terawatt.weight() { return Number::with_basic_unit(value / Terawatt.weight(), Terawatt); - } else if value >= d!(1000000000) { - // 1 gigawatt + } else if value >= Gigawatt.weight() { return Number::with_basic_unit(value / Gigawatt.weight(), Gigawatt); - } else if value >= d!(1000000) { - // megawatt + } else if value >= Megawatt.weight() { return Number::with_basic_unit(value / Megawatt.weight(), Megawatt); - } else if value >= d!(1000) { - // 1 kilowatt + } else if value >= Kilowatt.weight() { return Number::with_basic_unit(value / Kilowatt.weight(), Kilowatt); - } else if value >= d!(1) { - // 1 watt - return Number::with_basic_unit(value, Watt); + } else if value >= Watt.weight() { + return Number::with_basic_unit(value / Watt.weight(), Watt); } else { return Number::with_basic_unit(value / Milliwatt.weight(), Milliwatt); } } else if primitive == ElectricCurrent.primitive() { - if value >= d!(1000) { - // 1 kiloampere + if value >= Kiloampere.weight() { return Number::with_basic_unit(value / Kiloampere.weight(), Kiloampere); - } else if value >= d!(1) { - // 1 ampere - return Number::with_basic_unit(value, Ampere); + } else if value >= Ampere.weight() { + return Number::with_basic_unit(value / Ampere.weight(), Ampere); } else { return Number::with_basic_unit(value / Milliampere.weight(), Milliampere); } } else if primitive == Resistance.primitive() { - if value >= d!(1000) { - // 1 kiloohm + if value >= Kiloohm.weight() { return Number::with_basic_unit(value / Kiloohm.weight(), Kiloohm); - } else if value >= d!(1) { - // 1 ohm - return Number::with_basic_unit(value, Ohm); + } else if value >= Ohm.weight() { + return Number::with_basic_unit(value / Ohm.weight(), Ohm); } else { return Number::with_basic_unit(value / Milliohm.weight(), Milliohm); } } else if primitive == Voltage.primitive() { - if value >= d!(1000) { - // 1 kilovolt + if value >= Kilovolt.weight() { return Number::with_basic_unit(value / Kilovolt.weight(), Kilovolt); - } else if value >= d!(1) { - // 1 volt - return Number::with_basic_unit(value, Volt); + } else if value >= Volt.weight() { + return Number::with_basic_unit(value / Volt.weight(), Volt); } else { return Number::with_basic_unit(value / Millivolt.weight(), Millivolt); } @@ -668,28 +629,6 @@ pub fn to_ideal_unit(number: Number) -> Number { number } -/// Convert a [`Number`] to an ideal [`Joule`] unit, if the number is a unit of [`Energy`]. -pub fn to_ideal_joule_unit(number: Number) -> Number { - todo!(); - // let value = number.value * number.unit.weight(); - // if number.unit.category() == Energy { - // if value >= d!(1000000000000) { // 1 terajoule - // return Number::new(value/Terajoule.weight(), Terajoule) - // } else if value >= d!(1000000000) { // 1 gigajoule - // return Number::new(value/Gigajoule.weight(), Gigajoule) - // } else if value >= d!(1000000) { // 1 megajoule - // return Number::new(value/Megajoule.weight(), Megajoule) - // } else if value >= d!(1000) { // 1 kilojoule - // return Number::new(value/Kilojoule.weight(), Kilojoule) - // } else if value >= d!(1) { // 1 joule - // return Number::new(value/Joule.weight(), Joule) - // } else { - // return Number::new(value/Millijoule.weight(), Millijoule) - // } - // } - // number -} - /// Multiply two [`Number`]s /// /// - Temperatures don't work -- 2.51.2 From 192cc2937668f2fec5f933819ecc7a329f17d3c0 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 08:02:42 +0200 Subject: [PATCH 07/15] Add division --- src/units.rs | 125 ++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 101 deletions(-) diff --git a/src/units.rs b/src/units.rs index 3c00d79..363885f 100644 --- a/src/units.rs +++ b/src/units.rs @@ -629,19 +629,13 @@ pub fn to_ideal_unit(number: Number) -> Number { number } -/// Multiply two [`Number`]s +/// Multiply two [`Number`]s. /// -/// - Temperatures don't work -/// - If you multiply a unit with a unitless number, the result gets that other unit -/// - If you multiply [`Length`] with [`Length`], the result has a unit of [`Area`], etc. -/// - If you multiply [`Speed`] with [`Time`], the result has a unit of [`Length`] -/// - If you multiply [`Voltage`] with [`ElectricCurrent`], the result has a unit of [`Power`] -/// - If you multiply [`ElectricCurrent`] with [`Resistance`], the result has a unit of [`Voltage`] -/// - If you multiply [`Power`] with [`Time`], the result has a unit of [`Energy`] +/// Units are converted accordingly. +/// +/// Temperatures don't work pub fn multiply(left: Number, right: Number) -> Result { if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { - println!("TEMP?"); - // if temperature Err(format!( "Cannot multiply {:?} and {:?}", left.unit, right.unit @@ -735,100 +729,29 @@ pub fn multiply(left: Number, right: Number) -> Result { // } } -/// Divide a [`Number`] by another [`Number`] +/// Divide a [`Number`] by another [`Number`]. +/// +/// Units are converted accordingly. /// -/// - Temperatures don't work -/// - If you divide a unit by that same unit, the result has no unit -/// - If you divide [`Volume`] by [`Length`], the result has a unit of [`Area`], etc. -/// - If you divide [`Length`] by [`Time`], the result has a unit of [`Speed`] -/// - If you divide [`Length`] by [`Speed`], the result has a unit of [`Time`] -/// - If you divide [`Power`] by [`ElectricCurrent`], the result has a unit of [`Volt`] -/// - If you divide [`Voltage`] by [`ElectricCurrent`], the result has a unit of [`Ohm`] -/// - If you divide [`Voltage`] by [`Resistance`], the result has a unit of [`Ampere`] -/// - If you divide [`Power`] by [`Voltage`], the result has a unit of [`Ampere`] -/// - If you divide [`Energy`] by [`Time`], the result has a unit of [`Power`] +/// Temperatures don't work. pub fn divide(left: Number, right: Number) -> Result { - todo!(); - // let lcat = left.unit.category(); - // let rcat = right.unit.category(); - // if left.unit == NoUnit && right.unit == NoUnit { - // // 3 / 2 - // Ok(Number::new(left.value / right.value, left.unit)) - // } else if lcat == Temperature || rcat == Temperature { - // // if temperature - // Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) - // } else if left.has_unit() && right.unit == NoUnit { - // // 1 km / 2 - // Ok(Number::new(left.value / right.value, left.unit)) - // } else if lcat == rcat { - // // 4 km / 2 km - // let (left, right) = convert_to_lowest(left, right)?; - // Ok(Number::new(left.value / right.value, NoUnit)) - // } else if (lcat == Area && rcat == Length) || (lcat == Volume && rcat == Area) { - // // 1 km2 / 1 km, 1 km3 / 1 km2 - // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, Millimeter))) - // } else if lcat == Volume && rcat == Length { - // // 1 km3 / 1 km - // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) - // } else if lcat == Length && rcat == Time { - // // 1 km / 2s - // let final_unit = match (left.unit, right.unit) { - // (Kilometer, Hour) => KilometersPerHour, - // (Meter, Second) => MetersPerSecond, - // (Mile, Hour) => MilesPerHour, - // (Foot, Second) => FeetPerSecond, - // (NauticalMile, Hour) => Knot, - // _ => KilometersPerHour, - // }; - // let kilometers = convert(left, Kilometer)?; - // let hours = convert(right, Hour)?; - // let kph = Number::new(kilometers.value / hours.value, KilometersPerHour); - // Ok(convert(kph, final_unit)?) - // } else if lcat == Length && rcat == Speed { - // // 12 km / 100 kph - // let kilometers = convert(left, Kilometer)?; - // let kilometers_per_hour = convert(right, KilometersPerHour)?; - // let hour = Number::new(kilometers.value / kilometers_per_hour.value, Hour); - // Ok(to_ideal_unit(hour)) - // } else if lcat == DigitalStorage && rcat == DataTransferRate { - // // 1 kilobit / 1 bit per second - // let bits = convert(left, Bit)?; - // let bits_per_second = convert(right, BitsPerSecond)?; - // let seconds = Number::new(bits.value / bits_per_second.value, Second); - // Ok(to_ideal_unit(seconds)) - // } else if lcat == FlopCount && rcat == FlopRate { - // // 1 kiloFLOP / 1 FLOP per second - // let flop = convert(left, Flop)?; - // let flop_per_second = convert(right, FlopPerSecond)?; - // let seconds = Number::new(flop.value / flop_per_second.value, Second); - // Ok(to_ideal_unit(seconds)) - // } else if lcat == Power && rcat == ElectricCurrent { - // // 1 watt / 1 ampere = 1 volt - // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, Volt))) - // } else if lcat == Voltage && rcat == ElectricCurrent { - // // 1 volt / 1 ampere = 1 ohm - // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, Ohm))) - // } else if lcat == Voltage && rcat == Resistance { - // // 1 volt / 1 ohm = 1 amp - // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, Ampere))) - // } else if lcat == Power && rcat == Voltage { - // // 1 watt / 1 volt = 1 amp - // let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, Ampere))) - // } else if lcat == Energy && rcat == Time { - // // 1 joule / 1 second = 1 watt - // let result = (left.value * left.unit.weight()) - // / (right.value * right.unit.weight() / Unit::Second.weight()); - // Ok(to_ideal_unit(Number::new(result, Watt))) - // } else { - // Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) - // } + if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { + Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) + } else { + let mut new_number = left; + new_number.value /= right.value; + for (r_unit, r_exp) in right.unit { + let existing = new_number.unit.iter_mut().find(|(u, _)| u == &r_unit); + match existing { + Some(existing) => existing.1 -= r_exp, + None => new_number.unit.push((r_unit, -r_exp)), + } + } + let new_number = to_ideal_unit(new_number); + Ok(new_number) + } } + /// Modulo a [`Number`] by another [`Number`]. /// /// `left` and `right` need to have the same [`UnitType`], and the result will have that same [`UnitType`]. -- 2.51.2 From 9fe708e1f2c56db02293ff38789d7d923d8dcfdf Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 08:02:49 +0200 Subject: [PATCH 08/15] Fix unit printing --- src/lib.rs | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ed2a694..c8304e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,10 @@ use crate::units::{Unit, UnitType, primitive_unit}; use fastnum::{D128, dec128 as d}; -use std::fmt::{self, Display}; +use std::{ + cmp::Reverse, + fmt::{self, Display}, +}; use web_time::Instant; /// Turns an [`AstNode`](parser::AstNode) into a [`Number`] @@ -60,7 +63,6 @@ pub struct Number { /// The unit and exponent pub unit: Vec<(Unit, isize)>, } - impl Number { pub fn new_unitless(value: D128) -> Number { Number { @@ -94,13 +96,24 @@ impl Number { } fn get_unit_string(&self, plural: bool) -> String { let mut s = String::new(); - for unit in &self.unit { - match unit.1.is_positive() { - true if s.len() == 0 => (), - true => s.push_str(" * "), - false => s.push_str(" / "), + let mut units = self.unit.clone(); + units.sort_by_key(|u| { + ( + u.1 < 0, // multiplications first + Reverse(u.1.abs()), // largest first, like "sqm seconds" + ) + }); + let mut positives = units.iter().filter(|u| u.1 > 0).peekable(); + while let Some(unit) = positives.next() { + if unit.1 <= 0 { + continue; + } + if s.len() != 0 { + s.push_str(" * "); } - match plural { + // only the last multiplication should be plural: `100 minute meters / volt hour` + let is_last = positives.peek().is_none(); + match is_last && plural { false => s.push_str(unit.0.singular()), true => s.push_str(unit.0.plural()), }; @@ -109,6 +122,17 @@ impl Number { s.push_str(&unit.1.to_string()); } } + for unit in units { + if unit.1 >= 0 { + continue; + } + s.push_str(" / "); + s.push_str(unit.0.singular()); + if unit.1.abs() >= 2 { + s.push_str("^"); + s.push_str(&unit.1.to_string()); + } + } s } pub fn singular(&self) -> String { -- 2.51.2 From a27ba4ede94611395545f29b1f5ed9995e34fc2d Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 08:30:24 +0200 Subject: [PATCH 09/15] Add modulo --- src/units.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/units.rs b/src/units.rs index 363885f..9a0e9a1 100644 --- a/src/units.rs +++ b/src/units.rs @@ -758,17 +758,15 @@ pub fn divide(left: Number, right: Number) -> Result { /// /// Temperatures don't work. pub fn modulo(left: Number, right: Number) -> Result { - todo!(); - // if left.unit.category() == Temperature || right.unit.category() == Temperature { - // // if temperature - // Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) - // } else if left.unit.category() == right.unit.category() { - // // 5 km % 3 m - // let (left, right) = convert_to_lowest(left, right)?; - // Ok(Number::new(left.value % right.value, left.unit)) - // } else { - // Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) - // } + if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { + Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) + } else if left.primitive_unit() == right.primitive_unit() { + // 5 km % 3 m + let (left, right) = convert_to_lowest(left, right)?; + Ok(Number::with_unit(left.value % right.value, left.unit)) + } else { + Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) + } } /// Returns a [`Number`] to the power of another [`Number`] -- 2.51.2 From e19f297276ad98e1cd3d5ed55c769d741471b1f4 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 09:29:46 +0200 Subject: [PATCH 10/15] Add pow --- src/units.rs | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/units.rs b/src/units.rs index 9a0e9a1..d7c8f32 100644 --- a/src/units.rs +++ b/src/units.rs @@ -776,26 +776,34 @@ pub fn modulo(left: Number, right: Number) -> Result { /// - If you take [`Length`] to the power of [`Area`], the result has a unit of [`Volume`] /// - etc. pub fn pow(left: Number, right: Number) -> Result { - todo!(); - // // I tried converting `right` to use powi, but somehow that was slower - // let lcat = left.unit.category(); - // let rcat = left.unit.category(); - // if left.unit == NoUnit && right.unit == NoUnit { - // // 3 ^ 2 - // Ok(Number::new(left.value.pow(right.value), left.unit)) - // } else if right.value == d!(1) && right.unit == NoUnit { - // Ok(left) - // } else if lcat == Length && right.unit == NoUnit && right.value == d!(2) { - // // x km ^ 2 - // let result = (left.value * left.unit.weight()).pow(right.value); - // Ok(to_ideal_unit(Number::new(result, SquareMillimeter))) - // } else if lcat == Length && right.unit == NoUnit && right.value == d!(3) { - // // x km ^ 3 - // let result = (left.value * left.unit.weight()).pow(right.value); - // Ok(to_ideal_unit(Number::new(result, CubicMillimeter))) - // } else { - // Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit)) - // } + // I tried converting `right` to use powi, but somehow that was slower + if left.contains_primitive(Temperature) || right.has_unit() { + Err(format!( + "Cannot raise {:?} to the power of {:?}", + left.unit, right.unit + )) + } else if left.is_unitless() { + let result = left.value.pow(right.value); + let new_number = Number::new_unitless(result); + Ok(new_number) + } else { + let exp: isize = match (right.value.try_into(), right.value.is_integral()) { + (Ok(exp), true) => exp, + _ => { + return Err(format!( + "Cannot raise {} to the power of {}. Numbers with units can only be raised to integer powers", + left, right + )); + } + }; + let result = left.value.pow(right.value); + let mut new_number = Number::with_unit(result, left.unit); + for (_, unit_exp) in new_number.unit.iter_mut() { + *unit_exp *= exp; + } + let new_number = to_ideal_unit(new_number); + Ok(new_number) + } } #[cfg(test)] -- 2.51.2 From 27c219abd3ffeaf6e79d8e867076d2d4e25d21be Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 09:40:07 +0200 Subject: [PATCH 11/15] Improve error messages --- src/evaluator.rs | 10 +++++----- src/units.rs | 40 +++++++++++++++++----------------------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/evaluator.rs b/src/evaluator.rs index f7cab1a..b9bc804 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -75,7 +75,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { let result = cbrt(child_answer.value); Ok(Number::with_unit(result, child_answer.unit)) } else { - Err("log() only accepts UnitType::NoType".to_string()) + Err("cbrt() only accepts unitless numbers".to_string()) } } Sqrt => { @@ -83,7 +83,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { let result = sqrt(child_answer.value); Ok(Number::with_unit(result, child_answer.unit)) } else { - Err("log() only accepts UnitType::NoType".to_string()) + Err("sqrt() only accepts unitless numbers".to_string()) } } Log => { @@ -91,7 +91,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { let result = child_answer.value.log10(); Ok(Number::with_unit(result, child_answer.unit)) } else { - Err("log() only accepts UnitType::NoType".to_string()) + Err("log() only accepts unitless numbers".to_string()) } } Ln => { @@ -99,7 +99,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { let result = child_answer.value.ln(); Ok(Number::with_unit(result, child_answer.unit)) } else { - Err("ln() only accepts UnitType::NoType".to_string()) + Err("ln() only accepts unitless numbers".to_string()) } } Exp => { @@ -107,7 +107,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { let result = child_answer.value.exp(); Ok(Number::with_unit(result, child_answer.unit)) } else { - Err("exp() only accepts UnitType::NoType".to_string()) + Err("exp() only accepts unitless numbers".to_string()) } } Round => { diff --git a/src/units.rs b/src/units.rs index d7c8f32..a0d95fd 100644 --- a/src/units.rs +++ b/src/units.rs @@ -397,20 +397,22 @@ fn integer_power(base: D128, exp: isize) -> D128 { pub fn convert(number: Number, to_unit: Vec<(Unit, isize)>) -> Result { if number.primitive_unit() != primitive_unit(&to_unit) { return Err(format!( - "Cannot convert from {:?} to {:?}", - number.unit, to_unit + "Cannot convert {} to {}", + number, + Number::with_unit(d!(0), to_unit).plural() )); } let value = number.value; - if number.primitive_unit() == vec![(Kelvin, 1)] { + if number.primitive_unit() == Temperature.primitive() { if number.unit.len() != 1 || to_unit.len() != 1 || number.unit[0].1 != 1 || to_unit[0].1 != 1 { return Err(format!( - "Cannot convert from {:?} to {:?}", - number.unit, to_unit + "Cannot convert {} to {}", + number, + Number::with_unit(d!(0), to_unit).plural() )); } let ok = |new_value| Ok(Number::with_unit(new_value, to_unit.clone())); @@ -425,8 +427,9 @@ pub fn convert(number: Number, to_unit: Vec<(Unit, isize)>) -> Result ok((value + d!(459.67)) * d!(5) / d!(9)), (Fahrenheit, Celsius) => ok((value - d!(32)) / d!(1.8)), _ => Err(format!( - "Error converting temperature {:?} to {:?}", - number.unit, to_unit + "Error converting temperature {} to {}", + number, + Number::with_unit(d!(0), to_unit).plural() )), } } else { @@ -465,7 +468,7 @@ pub fn add(left: Number, right: Number) -> Result { let (left, right) = convert_to_lowest(left, right)?; Ok(Number::with_unit(left.value + right.value, left.unit)) } else { - Err(format!("Cannot add {:?} and {:?}", left.unit, right.unit)) + Err(format!("Cannot add {} and {}", left, right)) } } @@ -479,10 +482,7 @@ pub fn subtract(left: Number, right: Number) -> Result { let (left, right) = convert_to_lowest(left, right)?; Ok(Number::with_unit(left.value - right.value, left.unit)) } else { - Err(format!( - "Cannot subtract {:?} by {:?}", - left.unit, right.unit - )) + Err(format!("Cannot subtract {} by {}", left, right)) } } @@ -636,10 +636,7 @@ pub fn to_ideal_unit(number: Number) -> Number { /// Temperatures don't work pub fn multiply(left: Number, right: Number) -> Result { if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { - Err(format!( - "Cannot multiply {:?} and {:?}", - left.unit, right.unit - )) + Err(format!("Cannot multiply {} and {}", left, right)) } else { let mut new_number = left; new_number.value *= right.value; @@ -736,7 +733,7 @@ pub fn multiply(left: Number, right: Number) -> Result { /// Temperatures don't work. pub fn divide(left: Number, right: Number) -> Result { if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { - Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) + Err(format!("Cannot divide {} by {}", left, right)) } else { let mut new_number = left; new_number.value /= right.value; @@ -759,13 +756,13 @@ pub fn divide(left: Number, right: Number) -> Result { /// Temperatures don't work. pub fn modulo(left: Number, right: Number) -> Result { if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { - Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) + Err(format!("Cannot modulo {} by {}", left, right)) } else if left.primitive_unit() == right.primitive_unit() { // 5 km % 3 m let (left, right) = convert_to_lowest(left, right)?; Ok(Number::with_unit(left.value % right.value, left.unit)) } else { - Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit)) + Err(format!("Cannot modulo {} by {}", left, right)) } } @@ -778,10 +775,7 @@ pub fn modulo(left: Number, right: Number) -> Result { pub fn pow(left: Number, right: Number) -> Result { // I tried converting `right` to use powi, but somehow that was slower if left.contains_primitive(Temperature) || right.has_unit() { - Err(format!( - "Cannot raise {:?} to the power of {:?}", - left.unit, right.unit - )) + Err(format!("Cannot raise {} to the power of {}", left, right)) } else if left.is_unitless() { let result = left.value.pow(right.value); let new_number = Number::new_unitless(result); -- 2.51.2 From fff4fef5ac5ef145d799cd09805c7140c6189469 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 11:43:02 +0200 Subject: [PATCH 12/15] Make primitive units fully sorted --- src/lib.rs | 7 +------ src/units.rs | 38 +++++++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c8304e6..ba35ee4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,12 +97,7 @@ impl Number { fn get_unit_string(&self, plural: bool) -> String { let mut s = String::new(); let mut units = self.unit.clone(); - units.sort_by_key(|u| { - ( - u.1 < 0, // multiplications first - Reverse(u.1.abs()), // largest first, like "sqm seconds" - ) - }); + sort_units(&mut units); let mut positives = units.iter().filter(|u| u.1 > 0).peekable(); while let Some(unit) = positives.next() { if unit.1 <= 0 { diff --git a/src/units.rs b/src/units.rs index a0d95fd..414b3cd 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,5 +1,6 @@ use crate::Number; use fastnum::{D128, dec128 as d}; +use std::cmp::Reverse; #[derive(Clone, Copy, PartialEq, Debug)] /// An enum of all possible unit types, like [`Length`], [`DigitalStorage`] etc. @@ -43,7 +44,7 @@ pub enum UnitType { } impl UnitType { fn primitive(&self) -> Vec<(Unit, isize)> { - match self { + let units = match self { Time => vec![(Second, 1)], Length => vec![(Meter, 1)], Area => vec![(Meter, 2)], @@ -53,20 +54,39 @@ impl UnitType { DataTransferRate => vec![(Bit, 1), (Second, -1)], FlopCount => vec![(Flop, 1)], FlopRate => vec![(Flop, 1), (Second, -1)], - Energy => vec![(Kilogram, 1), (Meter, 2), (Second, -2)], - Power => vec![(Kilogram, 1), (Meter, 2), (Second, -3)], + Energy => vec![(Meter, 2), (Kilogram, 1), (Second, -2)], + Power => vec![(Meter, 2), (Kilogram, 1), (Second, -3)], ElectricCurrent => vec![(Ampere, 1)], - Resistance => vec![(Kilogram, 1), (Meter, 2), (Second, -3), (Ampere, -2)], - Voltage => vec![(Kilogram, 1), (Meter, 2), (Second, -3), (Ampere, -1)], - Pressure => vec![(Kilogram, 1), (Meter, -1), (Second, -2)], + Resistance => vec![(Meter, 2), (Kilogram, 1), (Second, -3), (Ampere, -2)], + Voltage => vec![(Meter, 2), (Kilogram, 1), (Second, -3), (Ampere, -1)], + Pressure => vec![(Kilogram, 1), (Second, -2), (Meter, -1)], Frequency => vec![(Second, -1)], Speed => vec![(Meter, 1), (Second, -1)], Temperature => vec![(Kelvin, 1)], + }; + #[cfg(debug_assertions)] + { + let u0 = units.clone(); + let mut u1 = units.clone(); + sort_units(&mut u1); + assert!(u0 == u1) } + units } } use UnitType::*; +/// Sort for display and comparison purposes. +pub fn sort_units(primitives: &mut Vec<(Unit, isize)>) { + primitives.sort_by_key(|u| { + ( + u.1 < 0, // multiplications first + Reverse(u.1.abs()), // largest first, like "sqm seconds" + u.0, // then sort by unit, to have a fully deterministic order + ) + }); +} + pub fn primitive_unit(unit: &Vec<(Unit, isize)>) -> Vec<(Unit, isize)> { let mut primitives: Vec<(Unit, isize)> = Vec::new(); for (unit, exponent) in unit { @@ -79,6 +99,7 @@ pub fn primitive_unit(unit: &Vec<(Unit, isize)>) -> Vec<(Unit, isize)> { } } primitives.retain(|(_, exponent)| exponent != &0); + sort_units(&mut primitives); primitives } @@ -652,7 +673,6 @@ pub fn multiply(left: Number, right: Number) -> Result { } // todo: 8 megabytes per second * 1 minute // todo: 8 megaFLOP per second * 1 minute - // todo: 1 amp * 1 ohm = 1 volt // } else if lcat == DataTransferRate && rcat == Time { // // 8 megabytes per second * 1 minute @@ -719,10 +739,6 @@ pub fn multiply(left: Number, right: Number) -> Result { // }; // let compute_work = Number::new(result, Flop); // Ok(convert(compute_work, final_unit)?) - // } else if lcat == ElectricCurrent && rcat == Resistance { - // // 1 amp * 1 ohm = 1 volt - // let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight()); - // Ok(to_ideal_unit(Number::new(result, Watt))) // } } -- 2.51.2 From b9ee8b3d7c4233e3f6112ccbc63ce8cb2d2dc2c8 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 11:43:24 +0200 Subject: [PATCH 13/15] Add tests --- src/evaluator.rs | 6 +-- src/lib.rs | 51 +++++++++++-------- src/lookup.rs | 5 +- src/units.rs | 124 +++++++++++++++++++---------------------------- 4 files changed, 86 insertions(+), 100 deletions(-) diff --git a/src/evaluator.rs b/src/evaluator.rs index b9bc804..576d645 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -249,7 +249,7 @@ fn evaluate_node(ast_node: &AstNode) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::eval; + use crate::{eval, units::Unit::*}; fn eval_default<'a>(input: &'a str) -> Number { let result = eval(input, true, false).unwrap(); @@ -257,7 +257,7 @@ mod tests { } fn eval_num<'a>(input: &'a str) -> String { let result = eval(input, true, false).unwrap(); - assert_eq!(result.unit, Unit::NoUnit); + assert!(result.is_unitless()); result.to_string() } @@ -269,7 +269,7 @@ mod tests { assert_eq!(eval_num("(3)-2"), "1"); assert_eq!( eval_default("-1km to m"), - Number::new(d!(-1000), Unit::Meter) + Number::with_basic_unit(d!(-1000), Meter) ); assert_eq!(eval_num("2*-3*0.5"), "-3"); assert_eq!(eval_num("-3^2"), "-9"); diff --git a/src/lib.rs b/src/lib.rs index ba35ee4..91879a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,28 +22,22 @@ //! } //! ``` -use crate::units::{Unit, UnitType, primitive_unit}; +use crate::units::{Unit, UnitType, primitive_unit, sort_units}; use fastnum::{D128, dec128 as d}; -use std::{ - cmp::Reverse, - fmt::{self, Display}, -}; +use std::fmt::{self, Debug, Display}; use web_time::Instant; /// Turns an [`AstNode`](parser::AstNode) into a [`Number`] pub mod evaluator; /// Turns a string into [`Token`]s -#[rustfmt::skip] pub mod lexer; -#[rustfmt::skip] mod lookup; /// Turns [`Token`]s into an [`AstNode`](parser::AstNode) pub mod parser; /// Units, and functions you can use with them -#[rustfmt::skip] pub mod units; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone)] /// A number with a `Unit`. /// /// Example: @@ -52,10 +46,7 @@ pub mod units; /// use cpc::units::Unit; /// use fastnum::dec128; /// -/// let x = Number { -/// value: dec128!(100), -/// unit: Unit::Meter, -/// }; +/// let x = Number::with_basic_unit(dec128!(100), Unit::Meter); /// ``` pub struct Number { /// The value of the number @@ -151,6 +142,26 @@ impl Display for Number { write!(f, "{output}") } } +impl Debug for Number { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let unit_strings: Vec<_> = self + .unit + .iter() + .map(|u| format!("{:?}^{}", u.0, u.1)) + .collect(); + write!( + f, + "Number({} {})", + self.get_simplified_value(), + unit_strings.join(" ") + ) + } +} +impl PartialEq for Number { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.primitive_unit() == other.primitive_unit() + } +} #[derive(Clone, Debug, PartialEq)] /// Math operators like [`Multiply`](Operator::Multiply), parentheses, etc. @@ -397,15 +408,15 @@ mod tests { #[test] fn test_evaluations() { - assert_eq!(default_eval("-2(-3)"), Number::new(d!(6), Unit::NoUnit)); - assert_eq!(default_eval("-2(3)"), Number::new(d!(-6), Unit::NoUnit)); - assert_eq!(default_eval("(3)-2"), Number::new(d!(1), Unit::NoUnit)); + assert_eq!(default_eval("-2(-3)"), Number::new_unitless(d!(6))); + assert_eq!(default_eval("-2(3)"), Number::new_unitless(d!(-6))); + assert_eq!(default_eval("(3)-2"), Number::new_unitless(d!(1))); assert_eq!( default_eval("-1km to m"), - Number::new(d!(-1000), Unit::Meter) + Number::with_basic_unit(d!(-1000), Unit::Meter) ); - assert_eq!(default_eval("2*-3*0.5"), Number::new(d!(-3), Unit::NoUnit)); - assert_eq!(default_eval("-3^2"), Number::new(d!(-9), Unit::NoUnit)); - assert_eq!(default_eval("-1+2"), Number::new(d!(1), Unit::NoUnit)); + assert_eq!(default_eval("2*-3*0.5"), Number::new_unitless(d!(-3))); + assert_eq!(default_eval("-3^2"), Number::new_unitless(d!(-9))); + assert_eq!(default_eval("-1+2"), Number::new_unitless(d!(1))); } } diff --git a/src/lookup.rs b/src/lookup.rs index ca57454..d4c11d6 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -1,8 +1,9 @@ -use crate::NamedNumber::*; use crate::NamedNumber; -use fastnum::{dec128 as d, D128}; +use crate::NamedNumber::*; +use fastnum::{D128, dec128 as d}; /// Returns the corresponding [`D128`] of a [`NamedNumber`] +#[rustfmt::skip] pub fn lookup_named_number(named_number: &NamedNumber) -> D128 { match named_number { Hundred => d!(100), diff --git a/src/units.rs b/src/units.rs index 414b3cd..5c1b660 100644 --- a/src/units.rs +++ b/src/units.rs @@ -108,7 +108,7 @@ pub fn primitive_unit(unit: &Vec<(Unit, isize)>) -> Vec<(Unit, isize)> { // and evaluator macro_rules! create_units { ( $( $variant:ident : $properties:expr ),*, ) => { - #[derive(Clone, Copy, PartialEq, Debug)] + #[derive(Clone, Copy, PartialEq, Debug, Eq, PartialOrd, Ord)] /// A Unit enum. Note that it can also be [`NoUnit`]. pub enum Unit { $($variant),* @@ -671,75 +671,6 @@ pub fn multiply(left: Number, right: Number) -> Result { let new_number = to_ideal_unit(new_number); Ok(new_number) } - // todo: 8 megabytes per second * 1 minute - // todo: 8 megaFLOP per second * 1 minute - - // } else if lcat == DataTransferRate && rcat == Time { - // // 8 megabytes per second * 1 minute - // let data_rate_value = left.value * left.unit.weight(); - // let seconds = convert(right, Second)?; - // let result = data_rate_value * seconds.value; - // let final_unit = match left.unit { - // BitsPerSecond => Bit, - // KilobitsPerSecond => Kilobit, - // MegabitsPerSecond => Megabit, - // GigabitsPerSecond => Gigabit, - // TerabitsPerSecond => Terabit, - // PetabitsPerSecond => Petabit, - // ExabitsPerSecond => Exabit, - // ZettabitsPerSecond => Zettabit, - // YottabitsPerSecond => Yottabit, - // KibibitsPerSecond => Kibibit, - // MebibitsPerSecond => Mebibit, - // GibibitsPerSecond => Gibibit, - // TebibitsPerSecond => Tebibit, - // PebibitsPerSecond => Pebibit, - // ExbibitsPerSecond => Exbibit, - // ZebibitsPerSecond => Zebibit, - // YobibitsPerSecond => Yobibit, - // BytesPerSecond => Byte, - // KilobytesPerSecond => Kilobyte, - // MegabytesPerSecond => Megabyte, - // GigabytesPerSecond => Gigabyte, - // TerabytesPerSecond => Terabyte, - // PetabytesPerSecond => Petabyte, - // ExabytesPerSecond => Exabyte, - // ZettabytesPerSecond => Zettabyte, - // YottabytesPerSecond => Yottabyte, - // KibibytesPerSecond => Kibibyte, - // MebibytesPerSecond => Mebibyte, - // GibibytesPerSecond => Gibibyte, - // TebibytesPerSecond => Tebibyte, - // PebibytesPerSecond => Pebibyte, - // ExbibytesPerSecond => Exbibyte, - // ZebibytesPerSecond => Zebibyte, - // YobibytesPerSecond => Yobibyte, - // _ => Bit, - // }; - // let data_storage = Number::new(result, Bit); - // Ok(convert(data_storage, final_unit)?) - // } else if lcat == FlopRate && rcat == Time { - // // 8 megaFLOP per second * 1 minute - // let compute_perf_value = left.value * left.unit.weight(); - // let seconds = convert(right, Second)?; - // let result = compute_perf_value * seconds.value; - // let final_unit = match left.unit { - // FlopPerSecond => Flop, - // KiloFlopPerSecond => KiloFlop, - // MegaFlopPerSecond => MegaFlop, - // GigaFlopPerSecond => GigaFlop, - // TeraFlopPerSecond => TeraFlop, - // PetaFlopPerSecond => PetaFlop, - // ExaFlopPerSecond => ExaFlop, - // ZettaFlopPerSecond => ZettaFlop, - // YottaFlopPerSecond => YottaFlop, - // RonnaFlopPerSecond => RonnaFlop, - // QuettaFlopPerSecond => QuettaFlop, - // _ => Flop, - // }; - // let compute_work = Number::new(result, Flop); - // Ok(convert(compute_work, final_unit)?) - // } } /// Divide a [`Number`] by another [`Number`]. @@ -835,9 +766,9 @@ mod tests { let value_string = &value.to_string(); let value_d128 = D128::from_str(value_string, Context::default()).unwrap(); - let number = Number::new(value_d128, unit); + let number = Number::with_basic_unit(value_d128, unit); - let result = convert(number, to_unit); + let result = convert(number, vec![(to_unit, 1)]); let string_result = &result.unwrap().value.to_string(); let float_result = f64::from_str(string_result).unwrap(); @@ -1182,10 +1113,10 @@ mod tests { assert_float_eq!(convert_test(1000.0, Terahertz, Petahertz), 1.0); assert_float_eq!(convert_test(60.0, Hertz, RevolutionsPerMinute), 1.0); - assert_float_eq!(convert_test(3.6, KilometersPerHour, MetersPerSecond), 1.0); + // assert_float_eq!(convert_test(3.6, KilometersPerHour, MetersPerSecond), 1.0); assert_float_eq!(convert_test(0.3048, MetersPerSecond, FeetPerSecond), 1.0); - assert_float_eq!(convert_test(1.609344, KilometersPerHour, MilesPerHour), 1.0); - assert_float_eq!(convert_test(1.852, KilometersPerHour, Knot), 1.0); + // assert_float_eq!(convert_test(1.609344, KilometersPerHour, MilesPerHour), 1.0); + // assert_float_eq!(convert_test(1.852, KilometersPerHour, Knot), 1.0); assert_float_eq!(convert_test(274.15, Kelvin, Celsius), 1.0); assert_float_eq!(convert_test(300.0, Kelvin, Fahrenheit), 80.33); @@ -1194,4 +1125,47 @@ mod tests { assert_float_eq!(convert_test(80.33, Fahrenheit, Kelvin), 300.0); assert_float_eq!(convert_test(5.0, Fahrenheit, Celsius), -15.0); } + + #[track_caller] + fn eval_test(a: &str, b: &str) { + let result_a = crate::eval(a, true, false).unwrap(); + let result_b = crate::eval(b, true, false).unwrap(); + assert_eq!(result_a, result_b, "{a} != {b}"); + } + #[track_caller] + fn eval_approx_test(a: &str, b: &str) { + let result_a = crate::eval(a, true, false).unwrap(); + let result_b = crate::eval(b, true, false).unwrap(); + let diff_pct: D128 = result_a.value / result_b.value - 1; + let diff_pct = diff_pct.abs(); + assert!(diff_pct <= d!(0.00000001), "{a} !≈ {b}") + } + + #[test] + fn test_unit_evals() { + eval_test("3.6km/1h", "3.6 kph"); + eval_test("0.3048 m/s to ft/s", "1 ft/s"); + eval_test("1.609344 km/1h to mph", "1 mph"); + eval_approx_test("1.852 kph to knots", "1 knots"); + eval_test("120 seconds to minutes", "2 minutes"); + eval_test("100 cm to m", "1 m"); + eval_test("1 km2 to m2", "1000000 m2"); + eval_test("1 liter to ml", "1000 ml"); + eval_test("1 kg to g", "1000 g"); + eval_test("1 KB to bytes", "1000 bytes"); + eval_test("1 MBps to KBps", "1000 KBps"); + eval_test("1 KFLOP to FLOP", "1000 FLOP"); + eval_test("1 KFLOPs to FLOPs", "1000 FLOPs"); + eval_test("1 kWh to Wh", "1000 Wh"); + eval_test("1 kW to W", "1000 W"); + eval_test("1000 mA to A", "1 A"); + eval_test("1000 mΩ to Ω", "1 Ω"); + eval_test("1000 mV to V", "1 V"); + eval_test("1 bar to Pa", "100000 Pa"); + eval_test("1 kHz to Hz", "1000 Hz"); + eval_approx_test("1 km/h to m/s", "0.27777777777777777777 m/s"); + eval_test("0 C to K", "273.15 K"); + eval_test("8 megabytes per second * 1 minute", "480mb"); + eval_test("8 megaFLOP per second * 1 minute", "480megaFLOP"); + } } -- 2.51.2 From 09fbd9acba27ae6d5d179969034d3adc04367e04 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 14:13:39 +0200 Subject: [PATCH 14/15] Simplify DigitalStorage and FlopCount units --- src/units.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/units.rs b/src/units.rs index 5c1b660..7ebd47b 100644 --- a/src/units.rs +++ b/src/units.rs @@ -646,6 +646,52 @@ pub fn to_ideal_unit(number: Number) -> Number { } else { return Number::with_basic_unit(value / Millivolt.weight(), Millivolt); } + } else if primitive == DigitalStorage.primitive() { + let bits = &[ + Bit, Kilobit, Megabit, Gigabit, Terabit, Petabit, Exabit, Zettabit, Yottabit, + ]; + let bibits = &[ + Bit, Kibibit, Mebibit, Gibibit, Tebibit, Pebibit, Exbibit, Zebibit, Yobibit, + ]; + let bytes = &[ + Byte, Kilobyte, Megabyte, Gigabyte, Terabyte, Petabyte, Exabyte, Zettabyte, Yottabyte, + ]; + let bibytes = &[ + Byte, Kibibyte, Mebibyte, Gibibyte, Tebibyte, Pebibyte, Exbibyte, Zebibyte, Yobibyte, + ]; + for unit in &number.unit { + if unit.0.category() == DigitalStorage || unit.0.category() == DataTransferRate { + let weight = unit.0.weight(); + let candidates = if weight % 8192 == d!(0) { + bibytes + } else if weight % 8000 == d!(0) { + bytes + } else if weight % 1024 == d!(0) { + bibits + } else { + bits + }; + let unit = candidates + .iter() + .rev() + .find(|&&u| value >= u.weight()) + .copied() + .unwrap_or(candidates[0]); + return Number::with_basic_unit(value / unit.weight(), unit); + } + } + } else if primitive == FlopCount.primitive() { + let candidates = [ + Flop, KiloFlop, MegaFlop, GigaFlop, TeraFlop, PetaFlop, ExaFlop, ZettaFlop, YottaFlop, + RonnaFlop, QuettaFlop, + ]; + let unit = candidates + .iter() + .rev() + .find(|&&u| value >= u.weight()) + .copied() + .unwrap_or(candidates[0]); + return Number::with_basic_unit(value / unit.weight(), unit); } number } -- 2.51.2 From c97b780bdef64c2fefb6d0a572d708b6fb7c2604 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 24 Jun 2026 14:34:32 +0200 Subject: [PATCH 15/15] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9250195..67f0ccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## Next +- Rewrite units to be exponent-based, making units much more powerful +- Add generic unit multiplication/division, such as `1kg/1m` or `1m*1kg`. +- Add generic unit simplification. For example, `1kg*1sqm/1s/1s/1s/1a` now becomes `1 volt` - Fix `^` allowing numbers to be raised to the power of units ## 3.1.0 - 2025 Jun 20 -- 2.51.2