use crate::{
Dimension, SiPrefix, dimension_exponents::DynDimensionExponents,
scale_exponents::ScaleExponents,
};
#[cfg(not(test))]
extern crate alloc;
#[cfg(not(test))]
use alloc::format;
#[cfg(not(test))]
use alloc::string::String;
#[cfg(not(test))]
use alloc::string::ToString;
#[cfg(not(test))]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy)]
pub struct UnitLiteralConfig {
pub verbose: bool,
pub prefer_si_units: bool,
}
impl Default for UnitLiteralConfig {
fn default() -> Self {
Self {
verbose: false,
prefer_si_units: true,
}
}
}
pub fn generate_unit_literal(
exponents: DynDimensionExponents,
scale_factors: ScaleExponents,
config: UnitLiteralConfig,
) -> String {
let exponents_vec = exponents.0.to_vec();
let base_systematic_literal = generate_systematic_unit_name_with_scale_factors(
exponents_vec.clone(),
scale_factors,
config.verbose,
);
let pure_systematic = generate_systematic_unit_name(exponents_vec.clone(), config.verbose);
let found_unit_literal = base_systematic_literal != pure_systematic;
let systematic_literal = if found_unit_literal {
base_systematic_literal
} else {
generate_prefixed_systematic_unit(
exponents,
scale_factors,
&base_systematic_literal,
config.verbose,
)
};
if !config.prefer_si_units {
return systematic_literal;
}
if let Some(info) = lookup_dimension_name(exponents_vec) {
if let Some(si_shortname) = if config.verbose {
info.unit_si_shortname
} else {
info.unit_si_shortname_symbol
} {
let prefixed_si_unit =
generate_prefixed_si_unit(scale_factors, si_shortname, config.verbose);
if prefixed_si_unit != systematic_literal && !found_unit_literal {
prefixed_si_unit
} else {
systematic_literal
}
} else {
systematic_literal
}
} else {
systematic_literal
}
}
pub fn get_storage_unit_name(
scale_factors: ScaleExponents,
dimension_exponents: DynDimensionExponents,
long_name: bool,
) -> String {
let unit_literal = generate_unit_literal(
dimension_exponents,
scale_factors,
UnitLiteralConfig {
verbose: long_name,
prefer_si_units: true,
},
);
if !unit_literal.is_empty() {
unit_literal
} else {
let exponents_vec = dimension_exponents.0.to_vec();
generate_systematic_unit_name(exponents_vec, long_name)
}
}
pub fn generate_systematic_unit_name_with_scale_factors(
exponents: Vec<i16>,
scale_factors: ScaleExponents,
long_name: bool,
) -> String {
if exponents.iter().all(|&exp| exp == i16::MIN) {
return "?".to_string();
}
let is_pure = exponents.iter().filter(|&exp| *exp != 0).count() == 1;
if is_pure {
if let Some(unit_name) =
lookup_unit_literal_by_scale_factors(&exponents, scale_factors, long_name)
{
return unit_name.to_string();
}
}
generate_systematic_unit_name_with_format_and_scale(
exponents,
Some(scale_factors),
long_name,
UnitFormat::Unicode,
)
}
pub fn generate_systematic_unit_name(exponents: Vec<i16>, long_name: bool) -> String {
generate_systematic_unit_name_with_format(exponents, long_name, UnitFormat::Unicode)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnitFormat {
Unicode,
Ucum,
}
pub fn generate_systematic_unit_name_with_format(
exponents: Vec<i16>,
long_name: bool,
format: UnitFormat,
) -> String {
generate_systematic_unit_name_with_format_and_scale(exponents, None, long_name, format)
}
pub fn generate_systematic_unit_name_with_format_and_scale(
exponents: Vec<i16>,
scale_factors: Option<ScaleExponents>,
long_name: bool,
format: UnitFormat,
) -> String {
if exponents.len() != 8 {
return "?".to_string();
}
let dimension_exponents = DynDimensionExponents([
exponents[0],
exponents[1],
exponents[2],
exponents[3],
exponents[4],
exponents[5],
exponents[6],
exponents[7],
]);
let base_result = generate_systematic_composite_unit_name_with_scale(
dimension_exponents,
scale_factors,
long_name,
);
match format {
UnitFormat::Unicode => base_result,
UnitFormat::Ucum => {
convert_unicode_to_ucum_format(&base_result)
}
}
}
fn convert_unicode_to_ucum_format(unicode_unit: &str) -> String {
unicode_unit.to_string()
}
pub fn lookup_unit_literal_by_scale_factors(
exponents: &[i16],
scale_factors: ScaleExponents,
long_name: bool,
) -> Option<&'static str> {
if exponents.len() != 8 {
return None;
}
let dyn_exponents = DynDimensionExponents([
exponents[0],
exponents[1],
exponents[2],
exponents[3],
exponents[4],
exponents[5],
exponents[6],
exponents[7],
]);
if let Some(dimension) = Dimension::find_dimension_by_exponents(dyn_exponents) {
if let Some(exact_unit) = dimension.units.iter().find(|unit| {
unit.scale == scale_factors && unit.conversion_factor == 1.0 }) {
return Some(if long_name {
exact_unit.name
} else {
exact_unit.symbols[0]
});
}
dimension
.units
.iter()
.find(|unit| unit.scale == ScaleExponents::IDENTITY && unit.conversion_factor == 1.0)
.map(|unit| {
if long_name {
unit.name
} else {
unit.symbols[0]
}
})
} else {
None
}
}
pub struct DimensionNames {
pub dimension_name: &'static str,
pub unit_si_shortname_symbol: Option<&'static str>,
pub unit_si_shortname: Option<&'static str>,
}
pub fn lookup_dimension_name(exponents: Vec<i16>) -> Option<DimensionNames> {
if exponents.len() != 8 {
return None;
}
let dyn_exponents = DynDimensionExponents([
exponents[0],
exponents[1],
exponents[2],
exponents[3],
exponents[4],
exponents[5],
exponents[6],
exponents[7],
]);
Dimension::find_dimension_by_exponents(dyn_exponents).and_then(|dim_info| {
let is_pure_atomic = exponents.iter().filter(|&exp| *exp != 0).count() == 1;
if is_pure_atomic {
let has_exact_match = dim_info.units.iter().any(|unit| {
unit.scale == ScaleExponents::IDENTITY && unit.conversion_factor == 1.0
});
if !has_exact_match {
return None;
}
}
let preferred_unit = dim_info
.units
.iter()
.find(|unit| unit.scale == ScaleExponents::IDENTITY && unit.conversion_factor == 1.0)
.or_else(|| dim_info.units.first());
let unit_symbol = preferred_unit.and_then(|unit| unit.symbols.first().copied());
let unit_long_name = preferred_unit.map(|unit| unit.name);
Some(DimensionNames {
dimension_name: dim_info.name,
unit_si_shortname_symbol: unit_symbol, unit_si_shortname: unit_long_name, })
})
}
pub fn generate_prefixed_systematic_unit(
_exponents: DynDimensionExponents,
scale_factors: ScaleExponents,
base_unit: &str,
long_name: bool,
) -> String {
let total_scale_p10 = calculate_total_scale_p10(
scale_factors.0[0],
scale_factors.0[1],
scale_factors.0[2],
scale_factors.0[3],
);
let is_pure_unit = !base_unit.contains("ยท");
let effective_scale_p10 = if is_pure_unit {
let base_scale_offset = Dimension::find_unit_by_symbol(base_unit)
.or_else(|| Dimension::find_unit_by_name(base_unit))
.map(|(_unit, _dimension)| _unit.scale.log10().unwrap_or(0))
.unwrap_or(0);
total_scale_p10 - base_scale_offset
} else {
total_scale_p10
};
if let Some(prefix) = get_si_prefix(effective_scale_p10, long_name) {
if !base_unit.contains("ยท")
&& (base_unit.contains("^") || base_unit.contains("ยฒ") || base_unit.contains("ยณ"))
{
format!("{}({})", prefix, base_unit)
} else {
format!("{}{}", prefix, base_unit)
}
} else {
let is_pure_power_of_10 = scale_factors.log10().is_some();
if is_pure_power_of_10 {
generate_si_unit_with_scale(effective_scale_p10, base_unit, long_name)
} else {
let scale_factors_str = format_scale_factors(
scale_factors.0[0],
scale_factors.0[1],
scale_factors.0[2],
scale_factors.0[3],
);
if scale_factors_str.is_empty() {
base_unit.to_string()
} else {
format!("{}{}", scale_factors_str, base_unit)
}
}
}
}
pub fn generate_prefixed_si_unit(
scale_factors: ScaleExponents,
base_si_unit: &str,
long_name: bool,
) -> String {
let total_scale_p10 = calculate_total_scale_p10(
scale_factors.0[0],
scale_factors.0[1],
scale_factors.0[2],
scale_factors.0[3],
);
let effective_scale_p10 =
if let Some((_unit, _dimension)) = Dimension::find_unit_by_symbol(base_si_unit) {
let base_scale_offset = _unit.scale.log10().unwrap_or(0);
total_scale_p10 - base_scale_offset
} else {
if let Some((_unit, _dimension)) = Dimension::find_unit_by_name(base_si_unit) {
let base_scale_offset = _unit.scale.log10().unwrap_or(0);
total_scale_p10 - base_scale_offset
} else {
total_scale_p10
}
};
if let Some(prefix) = get_si_prefix(effective_scale_p10, long_name) {
if !base_si_unit.contains("ยท")
&& (base_si_unit.contains("^")
|| base_si_unit.contains("ยฒ")
|| base_si_unit.contains("ยณ"))
{
format!("{}({})", prefix, base_si_unit)
} else {
format!("{}{}", prefix, base_si_unit)
}
} else {
let is_pure_power_of_10 = scale_factors.log10().is_some();
if is_pure_power_of_10 {
generate_si_unit_with_scale(effective_scale_p10, base_si_unit, long_name)
} else {
let scale_factors_str = format_scale_factors(
scale_factors.0[0],
scale_factors.0[1],
scale_factors.0[2],
scale_factors.0[3],
);
if scale_factors_str.is_empty() {
base_si_unit.to_string()
} else {
format!("{}{}", scale_factors_str, base_si_unit)
}
}
}
}
fn calculate_total_scale_p10(scale_p2: i16, scale_p3: i16, scale_p5: i16, scale_pi: i16) -> i16 {
let scale_exponents = ScaleExponents([scale_p2, scale_p3, scale_p5, scale_pi]);
scale_exponents.log10().unwrap_or(0)
}
fn generate_si_unit_with_scale(
total_scale_p10: i16,
base_si_unit: &str,
_long_name: bool,
) -> String {
if total_scale_p10 == 0 {
base_si_unit.to_string()
} else {
format!(
"10{} {}",
crate::to_unicode_superscript(total_scale_p10, false),
base_si_unit
)
}
}
fn format_scale_factors(scale_p2: i16, scale_p3: i16, scale_p5: i16, scale_pi: i16) -> String {
let scale_exponents = ScaleExponents([scale_p2, scale_p3, scale_p5, scale_pi]);
if scale_exponents.log10().is_some() {
return String::new();
}
let mut value = 1.0;
if scale_p2 != 0 {
value *= 2.0_f64.powi(scale_p2 as i32);
}
if scale_p3 != 0 {
value *= 3.0_f64.powi(scale_p3 as i32);
}
if scale_p5 != 0 {
value *= 5.0_f64.powi(scale_p5 as i32);
}
if scale_pi != 0 {
value *= core::f64::consts::PI.powi(scale_pi as i32);
}
if value == 1.0 {
String::new()
} else {
format!("({})", format_float_with_sig_figs(value, 3))
}
}
fn format_float_with_sig_figs(value: f64, sig_figs: usize) -> String {
if value == 0.0 {
return "0".to_string();
}
let abs_value = value.abs();
let magnitude = abs_value.log10().floor() as i32;
let scale_factor = 10_f64.powi(sig_figs as i32 - 1 - magnitude as i32);
let rounded = (value * scale_factor).round() / scale_factor;
let formatted = if magnitude >= 0 {
let precision = (sig_figs as i32 - magnitude - 1).max(0) as usize;
format!("{:.precision$}", rounded, precision = precision)
} else {
format!(
"{:.precision$}",
rounded,
precision = (sig_figs as i32 + magnitude.abs()) as usize
)
};
formatted
}
fn get_si_prefix(power_of_10: i16, long_name: bool) -> Option<&'static str> {
SiPrefix::ALL
.iter()
.find(|prefix| prefix.factor_log10() == power_of_10)
.map(|prefix| {
if long_name {
prefix.name()
} else {
prefix.symbol()
}
})
}
pub fn generate_systematic_composite_unit_name(
dimension_exponents: DynDimensionExponents,
long_name: bool,
) -> String {
generate_systematic_composite_unit_name_with_scale(dimension_exponents, None, long_name)
}
pub fn generate_systematic_composite_unit_name_with_scale(
dimension_exponents: DynDimensionExponents,
scale_factors: Option<ScaleExponents>,
long_name: bool,
) -> String {
let exponents = dimension_exponents.0;
if exponents.iter().all(|&exp| exp == i16::MIN) {
return "?".to_string();
}
let is_pure = exponents.iter().filter(|&exp| *exp != 0).count() == 1;
let mut result = String::new();
let mut first = true;
for (index, &exp) in exponents.iter().enumerate() {
if exp == 0 {
continue;
}
let (unit_name, unit_symbol, base_scale_offset) =
if let Some(dimension) = Dimension::BASIS.get(index) {
let matched_unit = if let Some(scale) = scale_factors {
dimension
.units
.iter()
.find(|unit| unit.scale == scale && unit.conversion_factor == 1.0)
.or_else(|| {
if scale == ScaleExponents::IDENTITY {
None } else {
dimension.units.iter().find(|unit| {
unit.scale == ScaleExponents::IDENTITY
&& unit.conversion_factor == 1.0
})
}
})
} else {
None
};
if let Some(unit) = matched_unit.or_else(|| dimension.units.first()) {
let base_scale_offset = unit.scale.log10().unwrap_or(0);
(unit.name, unit.symbols[0], base_scale_offset)
} else {
("?", "?", 0)
}
} else {
("?", "?", 0)
};
let is_compound_unit = exponents.iter().filter(|&&exp| exp != 0).count() > 1;
let (adjusted_unit_name, adjusted_unit_symbol) =
if is_compound_unit && base_scale_offset != 0 && index == 0 {
if long_name {
match unit_name {
"gram" => ("kilogram", unit_symbol),
_ => (unit_name, unit_symbol),
}
} else {
match unit_symbol {
"g" => (unit_name, "kg"),
_ => (unit_name, unit_symbol),
}
}
} else {
(unit_name, unit_symbol)
};
let base_name = if long_name {
adjusted_unit_name
} else {
adjusted_unit_symbol
};
let unit_part = if exp == 1 {
base_name.to_string()
} else {
format!("{}{}", base_name, crate::to_unicode_superscript(exp, false))
};
if !first {
result.push_str("ยท");
}
result.push_str(&unit_part);
first = false;
}
if is_pure {
result
} else {
format!("({})", result)
}
}
pub fn get_storage_unit_name_by_dimension_name(
scale_factors: ScaleExponents,
dimension_name: &str,
long_name: bool,
) -> String {
let dimension_exponents = match dimension_name {
"Mass" => DynDimensionExponents([1, 0, 0, 0, 0, 0, 0, 0]),
"Length" => DynDimensionExponents([0, 1, 0, 0, 0, 0, 0, 0]),
"Time" => DynDimensionExponents([0, 0, 1, 0, 0, 0, 0, 0]),
"Current" => DynDimensionExponents([0, 0, 0, 1, 0, 0, 0, 0]),
"Temperature" => DynDimensionExponents([0, 0, 0, 0, 1, 0, 0, 0]),
"Amount" => DynDimensionExponents([0, 0, 0, 0, 0, 1, 0, 0]),
"Luminous Intensity" => DynDimensionExponents([0, 0, 0, 0, 0, 0, 1, 0]),
"Angle" => DynDimensionExponents([0, 0, 0, 0, 0, 0, 0, 1]),
_ => return "unknown".to_string(),
};
get_storage_unit_name(scale_factors, dimension_exponents, long_name)
}