diff --git a/engine/src/units.zig b/engine/src/units.zig index 310c88f..b12a10c 100644 --- a/engine/src/units.zig +++ b/engine/src/units.zig @@ -92,6 +92,9 @@ pub const UnitCategory = enum { pub const UnitDef = struct { /// Canonical short name (e.g. "km"). name: []const u8, + /// What a person reads in a list: "Kilometer", "Meter per second". US spelling, + /// singular, ASCII. Display only - the engine never resolves a name through it. + label: []const u8, /// Alternative spellings, including long forms and plurals. aliases: []const []const u8 = &.{}, category: UnitCategory, @@ -140,6 +143,10 @@ pub const UnitDef = struct { /// source of truth and the exact conversion path is unaffected. const UnitSpec = struct { name: []const u8, + /// Required, with no default: a unit cannot be added to a table without a name a + /// frontend can show. These were guessed from the aliases before, which is how + /// `m/s` came out as "Meterpersecond". + label: []const u8, aliases: []const []const u8 = &.{}, /// Exact factor as a decimal or `p/q`. Null means the factor is irrational /// (pi-based) and only `approx_factor` applies. @@ -164,6 +171,7 @@ fn define(comptime category: UnitCategory, comptime specs: []const UnitSpec) [sp out[i] = .{ .name = spec.name, + .label = spec.label, .aliases = spec.aliases, .category = category, .to_base_factor = factor, @@ -213,56 +221,56 @@ pub const ConvertResult = struct { // Each table's base unit has factor "1" and no offset. const length_units = define(.length, &.{ - .{ .name = "nm", .aliases = &.{ "nanometer", "nanometers", "nanometre", "nanometres" }, .factor = "1e-9" }, - .{ .name = "um", .aliases = &.{ "micrometer", "micrometers", "micron", "microns" }, .factor = "1e-6" }, - .{ .name = "mm", .aliases = &.{ "millimeter", "millimeters", "millimetre", "millimetres" }, .factor = "0.001" }, - .{ .name = "cm", .aliases = &.{ "centimeter", "centimeters", "centimetre", "centimetres" }, .factor = "0.01" }, - .{ .name = "m", .aliases = &.{ "meter", "meters", "metre", "metres" }, .factor = "1" }, - .{ .name = "km", .aliases = &.{ "kilometer", "kilometers", "kilometre", "kilometres" }, .factor = "1000" }, - .{ .name = "in", .aliases = &.{ "inch", "inches" }, .factor = "0.0254" }, - .{ .name = "ft", .aliases = &.{ "foot", "feet" }, .factor = "0.3048" }, - .{ .name = "yd", .aliases = &.{ "yard", "yards" }, .factor = "0.9144" }, - .{ .name = "mi", .aliases = &.{ "mile", "miles" }, .factor = "1609.344" }, - .{ .name = "nmi", .aliases = &.{ "nauticalmile", "nauticalmiles" }, .factor = "1852" }, - .{ .name = "ly", .aliases = &.{ "lightyear", "lightyears" }, .factor = "9460730472580800" }, - .{ .name = "au", .aliases = &.{ "astronomicalunit", "astronomicalunits" }, .factor = "149597870700" }, + .{ .name = "nm", .label = "Nanometer", .aliases = &.{ "nanometer", "nanometers", "nanometre", "nanometres" }, .factor = "1e-9" }, + .{ .name = "um", .label = "Micrometer", .aliases = &.{ "micrometer", "micrometers", "micron", "microns" }, .factor = "1e-6" }, + .{ .name = "mm", .label = "Millimeter", .aliases = &.{ "millimeter", "millimeters", "millimetre", "millimetres" }, .factor = "0.001" }, + .{ .name = "cm", .label = "Centimeter", .aliases = &.{ "centimeter", "centimeters", "centimetre", "centimetres" }, .factor = "0.01" }, + .{ .name = "m", .label = "Meter", .aliases = &.{ "meter", "meters", "metre", "metres" }, .factor = "1" }, + .{ .name = "km", .label = "Kilometer", .aliases = &.{ "kilometer", "kilometers", "kilometre", "kilometres" }, .factor = "1000" }, + .{ .name = "in", .label = "Inch", .aliases = &.{ "inch", "inches" }, .factor = "0.0254" }, + .{ .name = "ft", .label = "Foot", .aliases = &.{ "foot", "feet" }, .factor = "0.3048" }, + .{ .name = "yd", .label = "Yard", .aliases = &.{ "yard", "yards" }, .factor = "0.9144" }, + .{ .name = "mi", .label = "Mile", .aliases = &.{ "mile", "miles" }, .factor = "1609.344" }, + .{ .name = "nmi", .label = "Nautical mile", .aliases = &.{ "nauticalmile", "nauticalmiles" }, .factor = "1852" }, + .{ .name = "ly", .label = "Light-year", .aliases = &.{ "lightyear", "lightyears" }, .factor = "9460730472580800" }, + .{ .name = "au", .label = "Astronomical unit", .aliases = &.{ "astronomicalunit", "astronomicalunits" }, .factor = "149597870700" }, // A parsec is exactly 648000/pi au, so it has no exact decimal form. - .{ .name = "pc", .aliases = &.{ "parsec", "parsecs" }, .approx_factor = 3.0856775814913673e16 }, + .{ .name = "pc", .label = "Parsec", .aliases = &.{ "parsec", "parsecs" }, .approx_factor = 3.0856775814913673e16 }, }); const mass_units = define(.mass, &.{ - .{ .name = "mg", .aliases = &.{ "milligram", "milligrams" }, .factor = "1e-6" }, - .{ .name = "g", .aliases = &.{ "gram", "grams" }, .factor = "0.001" }, - .{ .name = "kg", .aliases = &.{ "kilogram", "kilograms" }, .factor = "1" }, - .{ .name = "t", .aliases = &.{ "tonne", "tonnes", "metricton", "metrictons" }, .factor = "1000" }, - .{ .name = "oz", .aliases = &.{ "ounce", "ounces" }, .factor = "0.028349523125" }, - .{ .name = "lb", .aliases = &.{ "lbs", "pound", "pounds" }, .factor = "0.45359237" }, - .{ .name = "st", .aliases = &.{ "stone", "stones" }, .factor = "6.35029318" }, - .{ .name = "ton", .aliases = &.{ "shortton", "shorttons" }, .factor = "907.18474" }, - .{ .name = "lt", .aliases = &.{ "longton", "longtons" }, .factor = "1016.0469088" }, + .{ .name = "mg", .label = "Milligram", .aliases = &.{ "milligram", "milligrams" }, .factor = "1e-6" }, + .{ .name = "g", .label = "Gram", .aliases = &.{ "gram", "grams" }, .factor = "0.001" }, + .{ .name = "kg", .label = "Kilogram", .aliases = &.{ "kilogram", "kilograms" }, .factor = "1" }, + .{ .name = "t", .label = "Metric ton", .aliases = &.{ "tonne", "tonnes", "metricton", "metrictons" }, .factor = "1000" }, + .{ .name = "oz", .label = "Ounce", .aliases = &.{ "ounce", "ounces" }, .factor = "0.028349523125" }, + .{ .name = "lb", .label = "Pound", .aliases = &.{ "lbs", "pound", "pounds" }, .factor = "0.45359237" }, + .{ .name = "st", .label = "Stone", .aliases = &.{ "stone", "stones" }, .factor = "6.35029318" }, + .{ .name = "ton", .label = "Short ton", .aliases = &.{ "shortton", "shorttons" }, .factor = "907.18474" }, + .{ .name = "lt", .label = "Long ton", .aliases = &.{ "longton", "longtons" }, .factor = "1016.0469088" }, }); // Base: Celsius. These are the only units in the engine that need an offset. // Fahrenheit and Rankine scale by exactly 5/9, which has no terminating decimal // form, hence the fraction notation. const temperature_units = define(.temperature, &.{ - .{ .name = "C", .aliases = &.{ "celsius", "centigrade" }, .factor = "1" }, - .{ .name = "F", .aliases = &.{"fahrenheit"}, .factor = "5/9", .offset = "-160/9" }, - .{ .name = "K", .aliases = &.{"kelvin"}, .factor = "1", .offset = "-273.15" }, - .{ .name = "R", .aliases = &.{ "rankine", "Ra" }, .factor = "5/9", .offset = "-273.15" }, + .{ .name = "C", .label = "Celsius", .aliases = &.{ "celsius", "centigrade" }, .factor = "1" }, + .{ .name = "F", .label = "Fahrenheit", .aliases = &.{"fahrenheit"}, .factor = "5/9", .offset = "-160/9" }, + .{ .name = "K", .label = "Kelvin", .aliases = &.{"kelvin"}, .factor = "1", .offset = "-273.15" }, + .{ .name = "R", .label = "Rankine", .aliases = &.{ "rankine", "Ra" }, .factor = "5/9", .offset = "-273.15" }, }); const time_units = define(.time, &.{ - .{ .name = "ns", .aliases = &.{ "nanosecond", "nanoseconds" }, .factor = "1e-9" }, - .{ .name = "us", .aliases = &.{ "microsecond", "microseconds" }, .factor = "1e-6" }, - .{ .name = "ms", .aliases = &.{ "millisecond", "milliseconds" }, .factor = "0.001" }, - .{ .name = "s", .aliases = &.{ "sec", "secs", "second", "seconds" }, .factor = "1" }, - .{ .name = "min", .aliases = &.{ "minute", "minutes" }, .factor = "60" }, - .{ .name = "h", .aliases = &.{ "hr", "hrs", "hour", "hours" }, .factor = "3600" }, - .{ .name = "d", .aliases = &.{ "day", "days" }, .factor = "86400" }, - .{ .name = "wk", .aliases = &.{ "week", "weeks" }, .factor = "604800" }, + .{ .name = "ns", .label = "Nanosecond", .aliases = &.{ "nanosecond", "nanoseconds" }, .factor = "1e-9" }, + .{ .name = "us", .label = "Microsecond", .aliases = &.{ "microsecond", "microseconds" }, .factor = "1e-6" }, + .{ .name = "ms", .label = "Millisecond", .aliases = &.{ "millisecond", "milliseconds" }, .factor = "0.001" }, + .{ .name = "s", .label = "Second", .aliases = &.{ "sec", "secs", "second", "seconds" }, .factor = "1" }, + .{ .name = "min", .label = "Minute", .aliases = &.{ "minute", "minutes" }, .factor = "60" }, + .{ .name = "h", .label = "Hour", .aliases = &.{ "hr", "hrs", "hour", "hours" }, .factor = "3600" }, + .{ .name = "d", .label = "Day", .aliases = &.{ "day", "days" }, .factor = "86400" }, + .{ .name = "wk", .label = "Week", .aliases = &.{ "week", "weeks" }, .factor = "604800" }, // Julian year, the usual convention for "a year" in unit conversion. - .{ .name = "yr", .aliases = &.{ "year", "years" }, .factor = "31557600" }, + .{ .name = "yr", .label = "Year (Julian)", .aliases = &.{ "year", "years" }, .factor = "31557600" }, }); // Base: byte. Decimal (kB) and binary (KiB) prefixes are both provided, and bits @@ -274,103 +282,103 @@ const time_units = define(.time, &.{ // // A bit is an eighth of a byte, so every factor here is exact. const digital_storage_units = define(.digital_storage, &.{ - .{ .name = "b", .aliases = &.{ "bit", "bits" }, .factor = "0.125" }, - .{ .name = "kb", .aliases = &.{ "kilobit", "kilobits" }, .factor = "125" }, - .{ .name = "Mb", .aliases = &.{ "megabit", "megabits" }, .factor = "1.25e5" }, - .{ .name = "Gb", .aliases = &.{ "gigabit", "gigabits" }, .factor = "1.25e8" }, - .{ .name = "Tb", .aliases = &.{ "terabit", "terabits" }, .factor = "1.25e11" }, - .{ .name = "Pb", .aliases = &.{ "petabit", "petabits" }, .factor = "1.25e14" }, - .{ .name = "B", .aliases = &.{ "byte", "bytes" }, .factor = "1" }, - .{ .name = "kB", .aliases = &.{ "kilobyte", "kilobytes" }, .factor = "1e3" }, - .{ .name = "MB", .aliases = &.{ "megabyte", "megabytes" }, .factor = "1e6" }, - .{ .name = "GB", .aliases = &.{ "gigabyte", "gigabytes" }, .factor = "1e9" }, - .{ .name = "TB", .aliases = &.{ "terabyte", "terabytes" }, .factor = "1e12" }, - .{ .name = "PB", .aliases = &.{ "petabyte", "petabytes" }, .factor = "1e15" }, - .{ .name = "KiB", .aliases = &.{ "kibibyte", "kibibytes" }, .factor = "1024" }, - .{ .name = "MiB", .aliases = &.{ "mebibyte", "mebibytes" }, .factor = "1048576" }, - .{ .name = "GiB", .aliases = &.{ "gibibyte", "gibibytes" }, .factor = "1073741824" }, - .{ .name = "TiB", .aliases = &.{ "tebibyte", "tebibytes" }, .factor = "1099511627776" }, - .{ .name = "PiB", .aliases = &.{ "pebibyte", "pebibytes" }, .factor = "1125899906842624" }, + .{ .name = "b", .label = "Bit", .aliases = &.{ "bit", "bits" }, .factor = "0.125" }, + .{ .name = "kb", .label = "Kilobit", .aliases = &.{ "kilobit", "kilobits" }, .factor = "125" }, + .{ .name = "Mb", .label = "Megabit", .aliases = &.{ "megabit", "megabits" }, .factor = "1.25e5" }, + .{ .name = "Gb", .label = "Gigabit", .aliases = &.{ "gigabit", "gigabits" }, .factor = "1.25e8" }, + .{ .name = "Tb", .label = "Terabit", .aliases = &.{ "terabit", "terabits" }, .factor = "1.25e11" }, + .{ .name = "Pb", .label = "Petabit", .aliases = &.{ "petabit", "petabits" }, .factor = "1.25e14" }, + .{ .name = "B", .label = "Byte", .aliases = &.{ "byte", "bytes" }, .factor = "1" }, + .{ .name = "kB", .label = "Kilobyte", .aliases = &.{ "kilobyte", "kilobytes" }, .factor = "1e3" }, + .{ .name = "MB", .label = "Megabyte", .aliases = &.{ "megabyte", "megabytes" }, .factor = "1e6" }, + .{ .name = "GB", .label = "Gigabyte", .aliases = &.{ "gigabyte", "gigabytes" }, .factor = "1e9" }, + .{ .name = "TB", .label = "Terabyte", .aliases = &.{ "terabyte", "terabytes" }, .factor = "1e12" }, + .{ .name = "PB", .label = "Petabyte", .aliases = &.{ "petabyte", "petabytes" }, .factor = "1e15" }, + .{ .name = "KiB", .label = "Kibibyte", .aliases = &.{ "kibibyte", "kibibytes" }, .factor = "1024" }, + .{ .name = "MiB", .label = "Mebibyte", .aliases = &.{ "mebibyte", "mebibytes" }, .factor = "1048576" }, + .{ .name = "GiB", .label = "Gibibyte", .aliases = &.{ "gibibyte", "gibibytes" }, .factor = "1073741824" }, + .{ .name = "TiB", .label = "Tebibyte", .aliases = &.{ "tebibyte", "tebibytes" }, .factor = "1099511627776" }, + .{ .name = "PiB", .label = "Pebibyte", .aliases = &.{ "pebibyte", "pebibytes" }, .factor = "1125899906842624" }, }); // km/h is exactly 5/18 m/s and a knot exactly 463/900 m/s. const speed_units = define(.speed, &.{ - .{ .name = "m/s", .aliases = &.{ "mps", "meterpersecond", "meterspersecond" }, .factor = "1" }, - .{ .name = "km/h", .aliases = &.{ "kph", "kmh", "kilometerperhour", "kilometersperhour" }, .factor = "1000/3600" }, - .{ .name = "mph", .aliases = &.{ "mileperhour", "milesperhour" }, .factor = "0.44704" }, - .{ .name = "ft/s", .aliases = &.{ "fps", "footpersecond", "feetpersecond" }, .factor = "0.3048" }, - .{ .name = "kn", .aliases = &.{ "knot", "knots" }, .factor = "1852/3600" }, - .{ .name = "c", .aliases = &.{ "lightspeed", "speedoflight" }, .factor = "299792458" }, + .{ .name = "m/s", .label = "Meter per second", .aliases = &.{ "mps", "meterpersecond", "meterspersecond" }, .factor = "1" }, + .{ .name = "km/h", .label = "Kilometer per hour", .aliases = &.{ "kph", "kmh", "kilometerperhour", "kilometersperhour" }, .factor = "1000/3600" }, + .{ .name = "mph", .label = "Mile per hour", .aliases = &.{ "mileperhour", "milesperhour" }, .factor = "0.44704" }, + .{ .name = "ft/s", .label = "Foot per second", .aliases = &.{ "fps", "footpersecond", "feetpersecond" }, .factor = "0.3048" }, + .{ .name = "kn", .label = "Knot", .aliases = &.{ "knot", "knots" }, .factor = "1852/3600" }, + .{ .name = "c", .label = "Speed of light", .aliases = &.{ "lightspeed", "speedoflight" }, .factor = "299792458" }, }); const area_units = define(.area, &.{ - .{ .name = "mm2", .aliases = &.{ "mm^2", "squaremillimeter", "squaremillimeters" }, .factor = "1e-6" }, - .{ .name = "cm2", .aliases = &.{ "cm^2", "squarecentimeter", "squarecentimeters" }, .factor = "1e-4" }, - .{ .name = "m2", .aliases = &.{ "m^2", "squaremeter", "squaremeters" }, .factor = "1" }, - .{ .name = "km2", .aliases = &.{ "km^2", "squarekilometer", "squarekilometers" }, .factor = "1e6" }, - .{ .name = "in2", .aliases = &.{ "in^2", "squareinch", "squareinches" }, .factor = "0.00064516" }, - .{ .name = "ft2", .aliases = &.{ "ft^2", "squarefoot", "squarefeet" }, .factor = "0.09290304" }, - .{ .name = "yd2", .aliases = &.{ "yd^2", "squareyard", "squareyards" }, .factor = "0.83612736" }, - .{ .name = "mi2", .aliases = &.{ "mi^2", "squaremile", "squaremiles" }, .factor = "2589988.110336" }, - .{ .name = "ha", .aliases = &.{ "hectare", "hectares" }, .factor = "10000" }, - .{ .name = "acre", .aliases = &.{"acres"}, .factor = "4046.8564224" }, + .{ .name = "mm2", .label = "Square millimeter", .aliases = &.{ "mm^2", "squaremillimeter", "squaremillimeters" }, .factor = "1e-6" }, + .{ .name = "cm2", .label = "Square centimeter", .aliases = &.{ "cm^2", "squarecentimeter", "squarecentimeters" }, .factor = "1e-4" }, + .{ .name = "m2", .label = "Square meter", .aliases = &.{ "m^2", "squaremeter", "squaremeters" }, .factor = "1" }, + .{ .name = "km2", .label = "Square kilometer", .aliases = &.{ "km^2", "squarekilometer", "squarekilometers" }, .factor = "1e6" }, + .{ .name = "in2", .label = "Square inch", .aliases = &.{ "in^2", "squareinch", "squareinches" }, .factor = "0.00064516" }, + .{ .name = "ft2", .label = "Square foot", .aliases = &.{ "ft^2", "squarefoot", "squarefeet" }, .factor = "0.09290304" }, + .{ .name = "yd2", .label = "Square yard", .aliases = &.{ "yd^2", "squareyard", "squareyards" }, .factor = "0.83612736" }, + .{ .name = "mi2", .label = "Square mile", .aliases = &.{ "mi^2", "squaremile", "squaremiles" }, .factor = "2589988.110336" }, + .{ .name = "ha", .label = "Hectare", .aliases = &.{ "hectare", "hectares" }, .factor = "10000" }, + .{ .name = "acre", .label = "Acre", .aliases = &.{"acres"}, .factor = "4046.8564224" }, }); // Base: liter. const volume_units = define(.volume, &.{ - .{ .name = "mL", .aliases = &.{ "ml", "milliliter", "milliliters" }, .factor = "0.001" }, - .{ .name = "L", .aliases = &.{ "l", "liter", "liters", "litre", "litres" }, .factor = "1" }, - .{ .name = "m3", .aliases = &.{ "m^3", "cubicmeter", "cubicmeters" }, .factor = "1000" }, - .{ .name = "cm3", .aliases = &.{ "cm^3", "cc", "cubiccentimeter", "cubiccentimeters" }, .factor = "0.001" }, - .{ .name = "in3", .aliases = &.{ "in^3", "cubicinch", "cubicinches" }, .factor = "0.016387064" }, - .{ .name = "ft3", .aliases = &.{ "ft^3", "cubicfoot", "cubicfeet" }, .factor = "28.316846592" }, - .{ .name = "gal", .aliases = &.{ "gallon", "gallons" }, .factor = "3.785411784" }, - .{ .name = "qt", .aliases = &.{ "quart", "quarts" }, .factor = "0.946352946" }, - .{ .name = "pt", .aliases = &.{ "pint", "pints" }, .factor = "0.473176473" }, - .{ .name = "cup", .aliases = &.{"cups"}, .factor = "0.2365882365" }, - .{ .name = "floz", .aliases = &.{ "fluidounce", "fluidounces" }, .factor = "0.0295735295625" }, - .{ .name = "tbsp", .aliases = &.{ "tablespoon", "tablespoons" }, .factor = "0.01478676478125" }, - .{ .name = "tsp", .aliases = &.{ "teaspoon", "teaspoons" }, .factor = "0.00492892159375" }, - .{ .name = "bbl", .aliases = &.{ "barrel", "barrels" }, .factor = "158.987294928" }, + .{ .name = "mL", .label = "Milliliter", .aliases = &.{ "ml", "milliliter", "milliliters" }, .factor = "0.001" }, + .{ .name = "L", .label = "Liter", .aliases = &.{ "l", "liter", "liters", "litre", "litres" }, .factor = "1" }, + .{ .name = "m3", .label = "Cubic meter", .aliases = &.{ "m^3", "cubicmeter", "cubicmeters" }, .factor = "1000" }, + .{ .name = "cm3", .label = "Cubic centimeter", .aliases = &.{ "cm^3", "cc", "cubiccentimeter", "cubiccentimeters" }, .factor = "0.001" }, + .{ .name = "in3", .label = "Cubic inch", .aliases = &.{ "in^3", "cubicinch", "cubicinches" }, .factor = "0.016387064" }, + .{ .name = "ft3", .label = "Cubic foot", .aliases = &.{ "ft^3", "cubicfoot", "cubicfeet" }, .factor = "28.316846592" }, + .{ .name = "gal", .label = "Gallon (US)", .aliases = &.{ "gallon", "gallons" }, .factor = "3.785411784" }, + .{ .name = "qt", .label = "Quart (US)", .aliases = &.{ "quart", "quarts" }, .factor = "0.946352946" }, + .{ .name = "pt", .label = "Pint (US)", .aliases = &.{ "pint", "pints" }, .factor = "0.473176473" }, + .{ .name = "cup", .label = "Cup (US)", .aliases = &.{"cups"}, .factor = "0.2365882365" }, + .{ .name = "floz", .label = "Fluid ounce (US)", .aliases = &.{ "fluidounce", "fluidounces" }, .factor = "0.0295735295625" }, + .{ .name = "tbsp", .label = "Tablespoon (US)", .aliases = &.{ "tablespoon", "tablespoons" }, .factor = "0.01478676478125" }, + .{ .name = "tsp", .label = "Teaspoon (US)", .aliases = &.{ "teaspoon", "teaspoons" }, .factor = "0.00492892159375" }, + .{ .name = "bbl", .label = "Barrel (oil)", .aliases = &.{ "barrel", "barrels" }, .factor = "158.987294928" }, }); const energy_units = define(.energy, &.{ - .{ .name = "J", .aliases = &.{ "joule", "joules" }, .factor = "1" }, - .{ .name = "kJ", .aliases = &.{ "kilojoule", "kilojoules" }, .factor = "1000" }, - .{ .name = "cal", .aliases = &.{ "calorie", "calories" }, .factor = "4.184" }, - .{ .name = "kcal", .aliases = &.{ "kilocalorie", "kilocalories" }, .factor = "4184" }, - .{ .name = "Wh", .aliases = &.{ "watthour", "watthours" }, .factor = "3600" }, - .{ .name = "kWh", .aliases = &.{ "kilowatthour", "kilowatthours" }, .factor = "3600000" }, - .{ .name = "BTU", .aliases = &.{ "btu", "britishthermalunit" }, .factor = "1055.05585262" }, - .{ .name = "eV", .aliases = &.{ "electronvolt", "electronvolts" }, .factor = "1.602176634e-19" }, - .{ .name = "erg", .aliases = &.{"ergs"}, .factor = "1e-7" }, + .{ .name = "J", .label = "Joule", .aliases = &.{ "joule", "joules" }, .factor = "1" }, + .{ .name = "kJ", .label = "Kilojoule", .aliases = &.{ "kilojoule", "kilojoules" }, .factor = "1000" }, + .{ .name = "cal", .label = "Calorie", .aliases = &.{ "calorie", "calories" }, .factor = "4.184" }, + .{ .name = "kcal", .label = "Kilocalorie", .aliases = &.{ "kilocalorie", "kilocalories" }, .factor = "4184" }, + .{ .name = "Wh", .label = "Watt-hour", .aliases = &.{ "watthour", "watthours" }, .factor = "3600" }, + .{ .name = "kWh", .label = "Kilowatt-hour", .aliases = &.{ "kilowatthour", "kilowatthours" }, .factor = "3600000" }, + .{ .name = "BTU", .label = "British thermal unit", .aliases = &.{ "btu", "britishthermalunit" }, .factor = "1055.05585262" }, + .{ .name = "eV", .label = "Electronvolt", .aliases = &.{ "electronvolt", "electronvolts" }, .factor = "1.602176634e-19" }, + .{ .name = "erg", .label = "Erg", .aliases = &.{"ergs"}, .factor = "1e-7" }, }); const pressure_units = define(.pressure, &.{ - .{ .name = "Pa", .aliases = &.{ "pascal", "pascals" }, .factor = "1" }, - .{ .name = "kPa", .aliases = &.{ "kilopascal", "kilopascals" }, .factor = "1000" }, - .{ .name = "MPa", .aliases = &.{ "megapascal", "megapascals" }, .factor = "1e6" }, - .{ .name = "bar", .aliases = &.{"bars"}, .factor = "100000" }, - .{ .name = "mbar", .aliases = &.{ "millibar", "millibars" }, .factor = "100" }, - .{ .name = "atm", .aliases = &.{ "atmosphere", "atmospheres" }, .factor = "101325" }, + .{ .name = "Pa", .label = "Pascal", .aliases = &.{ "pascal", "pascals" }, .factor = "1" }, + .{ .name = "kPa", .label = "Kilopascal", .aliases = &.{ "kilopascal", "kilopascals" }, .factor = "1000" }, + .{ .name = "MPa", .label = "Megapascal", .aliases = &.{ "megapascal", "megapascals" }, .factor = "1e6" }, + .{ .name = "bar", .label = "Bar", .aliases = &.{"bars"}, .factor = "100000" }, + .{ .name = "mbar", .label = "Millibar", .aliases = &.{ "millibar", "millibars" }, .factor = "100" }, + .{ .name = "atm", .label = "Atmosphere", .aliases = &.{ "atmosphere", "atmospheres" }, .factor = "101325" }, // psi is exactly 4.4482216152605 N over 0.00064516 m2. - .{ .name = "psi", .aliases = &.{"poundpersquareinch"}, .factor = "4.4482216152605/0.00064516" }, - .{ .name = "torr", .aliases = &.{"torrs"}, .factor = "101325/760" }, - .{ .name = "mmHg", .aliases = &.{"mmhg"}, .factor = "133.322387415" }, - .{ .name = "inHg", .aliases = &.{"inhg"}, .factor = "3386.388640341" }, + .{ .name = "psi", .label = "Pound per square inch", .aliases = &.{"poundpersquareinch"}, .factor = "4.4482216152605/0.00064516" }, + .{ .name = "torr", .label = "Torr", .aliases = &.{"torrs"}, .factor = "101325/760" }, + .{ .name = "mmHg", .label = "Millimeter of mercury", .aliases = &.{"mmhg"}, .factor = "133.322387415" }, + .{ .name = "inHg", .label = "Inch of mercury", .aliases = &.{"inhg"}, .factor = "3386.388640341" }, }); // Base: bits per second. const data_rate_units = define(.data_rate, &.{ - .{ .name = "bps", .aliases = &.{ "bitpersecond", "bitspersecond" }, .factor = "1" }, - .{ .name = "kbps", .aliases = &.{"kilobitpersecond"}, .factor = "1e3" }, - .{ .name = "Mbps", .aliases = &.{"megabitpersecond"}, .factor = "1e6" }, - .{ .name = "Gbps", .aliases = &.{"gigabitpersecond"}, .factor = "1e9" }, - .{ .name = "Tbps", .aliases = &.{"terabitpersecond"}, .factor = "1e12" }, - .{ .name = "Bps", .aliases = &.{ "bytepersecond", "bytespersecond" }, .factor = "8" }, - .{ .name = "kBps", .aliases = &.{"kilobytepersecond"}, .factor = "8e3" }, - .{ .name = "MBps", .aliases = &.{"megabytepersecond"}, .factor = "8e6" }, - .{ .name = "GBps", .aliases = &.{"gigabytepersecond"}, .factor = "8e9" }, + .{ .name = "bps", .label = "Bit per second", .aliases = &.{ "bitpersecond", "bitspersecond" }, .factor = "1" }, + .{ .name = "kbps", .label = "Kilobit per second", .aliases = &.{"kilobitpersecond"}, .factor = "1e3" }, + .{ .name = "Mbps", .label = "Megabit per second", .aliases = &.{"megabitpersecond"}, .factor = "1e6" }, + .{ .name = "Gbps", .label = "Gigabit per second", .aliases = &.{"gigabitpersecond"}, .factor = "1e9" }, + .{ .name = "Tbps", .label = "Terabit per second", .aliases = &.{"terabitpersecond"}, .factor = "1e12" }, + .{ .name = "Bps", .label = "Byte per second", .aliases = &.{ "bytepersecond", "bytespersecond" }, .factor = "8" }, + .{ .name = "kBps", .label = "Kilobyte per second", .aliases = &.{"kilobytepersecond"}, .factor = "8e3" }, + .{ .name = "MBps", .label = "Megabyte per second", .aliases = &.{"megabytepersecond"}, .factor = "8e6" }, + .{ .name = "GBps", .label = "Gigabyte per second", .aliases = &.{"gigabytepersecond"}, .factor = "8e9" }, }); // Base: radian. Every unit here except mrad is defined as a fraction of a turn, @@ -378,13 +386,13 @@ const data_rate_units = define(.data_rate, &.{ // radians is inherently inexact. Only these units and the parsec are marked // approximate. const angle_units = define(.angle, &.{ - .{ .name = "rad", .aliases = &.{ "radian", "radians" }, .factor = "1" }, - .{ .name = "mrad", .aliases = &.{ "milliradian", "milliradians" }, .factor = "0.001" }, - .{ .name = "deg", .aliases = &.{ "degree", "degrees" }, .approx_factor = std.math.pi / 180.0 }, - .{ .name = "grad", .aliases = &.{ "gradian", "gradians", "gon" }, .approx_factor = std.math.pi / 200.0 }, - .{ .name = "turn", .aliases = &.{ "turns", "rev", "revolution", "revolutions" }, .approx_factor = std.math.tau }, - .{ .name = "arcmin", .aliases = &.{ "arcminute", "arcminutes" }, .approx_factor = std.math.pi / 10800.0 }, - .{ .name = "arcsec", .aliases = &.{ "arcsecond", "arcseconds" }, .approx_factor = std.math.pi / 648000.0 }, + .{ .name = "rad", .label = "Radian", .aliases = &.{ "radian", "radians" }, .factor = "1" }, + .{ .name = "mrad", .label = "Milliradian", .aliases = &.{ "milliradian", "milliradians" }, .factor = "0.001" }, + .{ .name = "deg", .label = "Degree", .aliases = &.{ "degree", "degrees" }, .approx_factor = std.math.pi / 180.0 }, + .{ .name = "grad", .label = "Gradian", .aliases = &.{ "gradian", "gradians", "gon" }, .approx_factor = std.math.pi / 200.0 }, + .{ .name = "turn", .label = "Turn", .aliases = &.{ "turns", "rev", "revolution", "revolutions" }, .approx_factor = std.math.tau }, + .{ .name = "arcmin", .label = "Arcminute", .aliases = &.{ "arcminute", "arcminutes" }, .approx_factor = std.math.pi / 10800.0 }, + .{ .name = "arcsec", .label = "Arcsecond", .aliases = &.{ "arcsecond", "arcseconds" }, .approx_factor = std.math.pi / 648000.0 }, }); /// All unit tables, indexed in the same order as `UnitCategory`. @@ -541,7 +549,7 @@ pub fn convert(value: f64, from_name: []const u8, to_name: []const u8) Error!Con /// /// `value_text` is the part before the unit and is left unevaluated: it may be a /// plain number or a whole expression ("2*3 km to ft"). Keeping evaluation out of -/// this module avoids a dependency on the evaluator, so callers evaluate it with +/// this file avoids a dependency on the evaluator, so callers evaluate it with /// whatever environment they already have. pub const ConversionRequest = struct { value_text: []const u8, @@ -1591,3 +1599,21 @@ test "OOM safety: exact conversion releases everything at any failure point" { } return error.OomSweepNeverCompleted; } + +test "every unit has a display name a list can show" { + // The field is required, so a unit without one does not compile. This holds the + // names to the form a frontend relies on: present, capitalised, printable ASCII, + // and distinct within a category, so a list never shows two rows alike. + for (categories) |table| { + for (table, 0..) |unit, i| { + try std.testing.expect(unit.label.len > 0); + try std.testing.expect(std.ascii.isUpper(unit.label[0])); + for (unit.label) |c| try std.testing.expect(c >= 0x20 and c < 0x7f); + for (table[i + 1 ..]) |other| { + try std.testing.expect(!std.mem.eql(u8, unit.label, other.label)); + } + } + } + try std.testing.expectEqualStrings("Meter per second", findUnit("m/s").?.label); + try std.testing.expectEqualStrings("Celsius", findUnit("C").?.label); +}