use std::collections::HashMap; use crate::{ calc_result::CalcResult, expressions::{parser::Node, token::Error, types::CellReferenceIndex}, model::Model, }; enum Temperature { Kelvin, Celsius, Rankine, Reaumur, Fahrenheit, } // To Kelvin // T_K = T_C + 273.15 // T_K = 5/9 * T_rank // T_K = (T_R-273.15)*4/5 // T_K = 5/9 ( T_F + 459.67) fn convert_temperature( value: f64, from_temperature: Temperature, to_temperature: Temperature, ) -> f64 { let from_t_kelvin = match from_temperature { Temperature::Kelvin => value, Temperature::Celsius => value + 273.15, Temperature::Rankine => 5.0 * value / 9.0, Temperature::Reaumur => 5.0 * value / 4.0 + 273.15, Temperature::Fahrenheit => 5.0 / 9.0 * (value + 459.67), }; match to_temperature { Temperature::Kelvin => from_t_kelvin, Temperature::Celsius => from_t_kelvin - 273.5, Temperature::Rankine => 9.0 * from_t_kelvin / 5.0, Temperature::Reaumur => 4.0 * (from_t_kelvin - 273.15) / 5.0, Temperature::Fahrenheit => 9.0 * from_t_kelvin / 5.0 - 459.67, } } impl Model { // CONVERT(number, from_unit, to_unit) pub(crate) fn fn_convert(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 3 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let from_unit = match self.get_string(&args[1], cell) { Ok(s) => s, Err(error) => return error, }; let to_unit = match self.get_string(&args[2], cell) { Ok(s) => s, Err(error) => return error, }; let prefix = HashMap::from([ ("Y", 1E+24), ("Z", 1E+21), ("E", 1000000000000000000.0), ("P", 1000000000000000.0), ("T", 1000000000000.0), ("G", 1000000000.0), ("M", 1000000.0), ("k", 1000.0), ("h", 100.0), ("da", 10.0), ("e", 10.0), ("d", 0.1), ("c", 0.01), ("m", 0.001), ("u", 0.000001), ("n", 0.000000001), ("p", 1E-12), ("f", 1E-15), ("a", 1E-18), ("z", 1E-21), ("y", 1E-24), ("Yi", 2.0_f64.powf(80.0)), ("Ei", 2.0_f64.powf(70.0)), ("Yi", 2.0_f64.powf(80.0)), ("Zi", 2.0_f64.powf(70.0)), ("Ei", 2.0_f64.powf(60.0)), ("Pi", 2.0_f64.powf(50.0)), ("Ti", 2.0_f64.powf(40.0)), ("Gi", 2.0_f64.powf(30.0)), ("Mi", 2.0_f64.powf(20.0)), ("ki", 2.0_f64.powf(10.0)), ]); let mut units = HashMap::new(); let weight = HashMap::from([ ("g", 1.0), ("sg", 14593.9029372064), ("lbm", 453.59237), ("u", 1.660538782E-24), ("ozm", 28.349523125), ("grain", 0.06479891), ("cwt", 45359.237), ("shweight", 45359.237), ("uk_cwt", 50802.34544), ("lcwt", 50802.34544), ("stone", 6350.29318), ("ton", 907184.74), ("brton", 1016046.9088), // g-sheets has a different value for this ("LTON", 1016046.9088), ("uk_ton", 1016046.9088), ]); units.insert("weight", weight); let distance = HashMap::from([ ("m", 1.0), ("mi", 1609.344), ("Nmi", 1852.0), ("in", 0.0254), ("ft", 0.3048), ("yd", 0.9144), ("ang", 0.0000000001), ("ell", 1.143), ("ly", 9460730472580800.0), ("parsec", 30856775812815500.0), ("pc", 30856775812815500.0), ("Picapt", 0.000352777777777778), ("Pica", 0.000352777777777778), ("pica", 0.00423333333333333), ("survey_mi", 1609.34721869444), ]); units.insert("distance", distance); let time = HashMap::from([ ("yr", 31557600.0), ("day", 86400.0), ("d", 86400.0), ("hr", 3600.0), ("mn", 60.0), ("min", 60.0), ("sec", 1.0), ("s", 1.0), ]); units.insert("time", time); let pressure = HashMap::from([ ("Pa", 1.0), ("p", 1.0), ("atm", 101325.0), ("at", 101325.0), ("mmHg", 133.322), ("psi", 6894.75729316836), ("Torr", 133.322368421053), ]); units.insert("pressure", pressure); let force = HashMap::from([ ("N", 1.0), ("dyn", 0.00001), ("dy", 0.00001), ("lbf", 4.4482216152605), ("pond", 0.00980665), ]); units.insert("force", force); let energy = HashMap::from([ ("J", 1.0), ("e", 0.0000001), ("c", 4.184), ("cal", 4.1868), ("eV", 1.602176487E-19), ("ev", 1.602176487E-19), ("HPh", 2684519.53769617), ("hh", 2684519.53769617), ("Wh", 3600.0), ("wh", 3600.0), ("flb", 1.3558179483314), ("BTU", 1055.05585262), ("btu", 1055.05585262), ]); units.insert("energy", energy); let power = HashMap::from([ ("HP", 745.69987158227), ("h", 745.69987158227), ("PS", 735.49875), ("W", 1.0), ("w", 1.0), ]); units.insert("power", power); let magnetism = HashMap::from([("T", 1.0), ("ga", 0.0001)]); units.insert("magnetism", magnetism); let volume = HashMap::from([ ("tsp", 0.00000492892159375), ("tspm", 0.000005), ("tbs", 0.00001478676478125), ("oz", 0.0000295735295625), ("cup", 0.0002365882365), ("pt", 0.000473176473), ("us_pt", 0.000473176473), ("uk_pt", 0.00056826125), ("qt", 0.000946352946), ("uk_qt", 0.0011365225), ("gal", 0.003785411784), ("uk_gal", 0.00454609), ("l", 0.001), ("L", 0.001), ("lt", 0.001), ("ang3", 1E-30), ("ang^3", 1E-30), ("barrel", 0.158987294928), ("bushel", 0.03523907016688), ("ft3", 0.028316846592), ("ft^3", 0.028316846592), ("in3", 0.000016387064), ("in^3", 0.000016387064), ("ly3", 8.46786664623715E+47), ("ly^3", 8.46786664623715E+47), ("m3", 1.0), ("m^3", 1.0), ("mi3", 4168181825.44058), ("mi^3", 4168181825.44058), ("yd3", 0.764554857984), ("yd^3", 0.764554857984), ("Nmi3", 6352182208.0), ("Nmi^3", 6352182208.0), ("Picapt3", 4.39039566186557E-11), ("Picapt^3", 4.39039566186557E-11), ("Pica3", 4.39039566186557E-11), ("Pica^3", 4.39039566186557E-11), ("GRT", 2.8316846592), ("regton", 2.8316846592), ("MTON", 1.13267386368), ]); units.insert("volume", volume); let area = HashMap::from([ ("uk_acre", 4046.8564224), ("us_acre", 4046.87260987425), ("ang2", 1E-20), ("ang^2", 1E-20), ("ar", 100.0), ("ft2", 0.09290304), ("ft^2", 0.09290304), ("ha", 10000.0), ("in2", 0.00064516), ("in^2", 0.00064516), ("ly2", 8.95054210748189E+31), ("ly^2", 8.95054210748189E+31), ("m2", 1.0), ("m^2", 1.0), ("Morgen", 2500.0), ("mi2", 2589988.110336), ("mi^2", 2589988.110336), ("Nmi2", 3429904.0), ("Nmi^2", 3429904.0), ("Picapt2", 0.000000124452160493827), ("Pica2", 0.000000124452160493827), ("Pica^2", 0.000000124452160493827), ("Picapt^2", 0.000000124452160493827), ("yd2", 0.83612736), ("yd^2", 0.83612736), ]); units.insert("area", area); let information = HashMap::from([("bit", 1.0), ("byte", 8.0)]); units.insert("information", information); let speed = HashMap::from([ ("admkn", 0.514773333333333), ("kn", 0.514444444444444), ("m/h", 0.000277777777777778), ("m/hr", 0.000277777777777778), ("m/s", 1.0), ("m/sec", 1.0), ("mph", 0.44704), ]); units.insert("speed", speed); let temperature = HashMap::from([ ("C", 1.0), ("cel", 1.0), ("F", 1.0), ("fah", 1.0), ("K", 1.0), ("kel", 1.0), ("Rank", 1.0), ("Reau", 1.0), ]); units.insert("temperature", temperature); // only some units admit prefixes (the is no kC, kilo Celsius, for instance) let mks = [ "Pa", "p", "atm", "at", "mmHg", "g", "u", "m", "ang", "ly", "parsec", "pc", "ang2", "ang^2", "ar", "m2", "m^2", "N", "dyn", "dy", "pond", "J", "e", "c", "cal", "eV", "ev", "Wh", "wh", "W", "w", "T", "ga", "uk_pt", "l", "L", "lt", "ang3", "ang^3", "m3", "m^3", "bit", "byte", "m/h", "m/hr", "m/s", "m/sec", "mph", "K", "kel", ]; let volumes = ["ang3", "ang^3", "m3", "m^3"]; // We need all_units to make sure tha pc is interpreted as parsec and not pico centimeters // We could have this list hard coded, of course. let mut all_units = Vec::new(); for unit in units.values() { for &unit_name in unit.keys() { all_units.push(unit_name); } } let mut to_unit_prefix = 1.0; let mut from_unit_prefix = 1.0; // kind of units (weight, distance, time, ...) let mut to_unit_kind = ""; let mut from_unit_kind = ""; let mut to_unit_name = ""; let mut from_unit_name = ""; for (&name, unit) in &units { for (&unit_name, unit_value) in unit { if let Some(pk) = from_unit.strip_suffix(unit_name) { if pk.is_empty() { from_unit_kind = name; from_unit_prefix = 1.0 * unit_value; from_unit_name = unit_name; } else if let Some(modifier) = prefix.get(pk) { if mks.contains(&unit_name) && !all_units.contains(&from_unit.as_str()) { // We make sure: // 1. It is a unit that admits a modifier (like metres or grams) // 2. from_unit is not itself a unit let scale = if name == "area" && unit_name != "ar" { // 1 km2 is actually 10^6 m2 *modifier * modifier } else if name == "volume" && volumes.contains(&unit_name) { // don't look at me I don't make the rules! *modifier * modifier * modifier } else { *modifier }; from_unit_kind = name; from_unit_prefix = scale * unit_value; from_unit_name = unit_name; } } } if let Some(pk) = to_unit.strip_suffix(unit_name) { if pk.is_empty() { to_unit_kind = name; to_unit_prefix = 1.0 * unit_value; to_unit_name = unit_name; } else if let Some(modifier) = prefix.get(pk) { if mks.contains(&unit_name) && !all_units.contains(&to_unit.as_str()) { let scale = if name == "area" && unit_name != "ar" { *modifier * modifier } else if name == "volume" && volumes.contains(&unit_name) { *modifier * modifier * modifier } else { *modifier }; to_unit_kind = name; to_unit_prefix = scale * unit_value; to_unit_name = unit_name; } } } if !from_unit_kind.is_empty() && !to_unit_kind.is_empty() { break; } } if !from_unit_kind.is_empty() && !to_unit_kind.is_empty() { break; } } if from_unit_kind != to_unit_kind { return CalcResult::new_error(Error::NA, cell, "Different units".to_string()); } // Let's check if it is temperature; if from_unit_kind.is_empty() { return CalcResult::new_error(Error::NA, cell, "Unit not found".to_string()); } if from_unit_kind == "temperature" { // Temperature requires formula conversion // Kelvin (K, k), Celsius (C,cel), Rankine (Rank), Réaumur (Reau) let to_temperature = match to_unit_name { "K" | "kel" => Temperature::Kelvin, "C" | "cel" => Temperature::Celsius, "Rank" => Temperature::Rankine, "Reau" => Temperature::Reaumur, "F" | "fah" => Temperature::Fahrenheit, _ => { return CalcResult::new_error(Error::ERROR, cell, "Internal error".to_string()); } }; let from_temperature = match from_unit_name { "K" | "kel" => Temperature::Kelvin, "C" | "cel" => Temperature::Celsius, "Rank" => Temperature::Rankine, "Reau" => Temperature::Reaumur, "F" | "fah" => Temperature::Fahrenheit, _ => { return CalcResult::new_error(Error::ERROR, cell, "Internal error".to_string()); } }; let t = convert_temperature(value * from_unit_prefix, from_temperature, to_temperature) / to_unit_prefix; return CalcResult::Number(t); } CalcResult::Number(value * from_unit_prefix / to_unit_prefix) } }