Unit converter
Convert lengths, weights, temperatures and volumes from any unit to any other of the same kind, and back again, with every typed unit and value checked first - from a text menu.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Converts lengths, weights, temperatures and volumes from any unit to any other unit of the same kind, and reverses the last conversion. A text menu; units are typed by their symbol, and each kind lists its units before asking.
main.eml- the menu loop and the checks on what is typedunits.eml- the units: which kind each one measures, the factors that convert length, weight and volume through the metre, the gram and the millilitre, and the scale and offset that convert temperature through Celsiustext.eml- trimming, joining words, and showing a number to six significant digits
What is checked before anything is converted: a unit must exist and measure the kind chosen - typing kg while converting a length is refused, not converted; a value must be a finite number; a length, weight or volume cannot be negative, and no temperature can be below absolute zero. "Reverse last" works from the exact result, not from the six digits shown for it.
Sessions: sessions/basic.in converts in all four kinds and reverses two conversions; sessions/bad-input.in asks to reverse before anything was converted, types an unknown and an empty choice, units that do not exist, are empty or measure another kind, values that are empty, not numbers or not finite, a negative length and temperatures below absolute zero.
Built on the verified corpus cases unit-temperature-converter (Celsius and Fahrenheit, both ways) and dimensional-unit-guard (a unit carries the dimension it measures, and a conversion across dimensions is refused rather than computed).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 5
Nothing to reverse yet.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 9
Pick a number from 1 to 6.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice>
Pick a number from 1 to 6.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> parsec
Unknown unit: parsec. length units: mm cm m km in ft yd mi
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from>
Type a unit.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> kg
kg is a weight unit, not a length unit.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> km
to> mi
value in km> abc
Not a number: abc
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> km
to> m
value in km>
Type a number.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> km
to> m
value in km> -3
A length cannot be negative.
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 3
temperature units: C F K
from> F
to> C
value in F> -500
-500 F is below absolute zero (-459.67 F).
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 3
temperature units: C F K
from> K
to> C
value in K> -0.5
-0.5 K is below absolute zero (0 K).
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 2
weight units: mg g kg t oz lb
from> g
to> oz
value in g> nan
Not a finite number: nan
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 2
weight units: mg g kg t oz lb
from> g
to> oz
value in g> inf
Not a finite number: inf
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 4
volume units: ml l m3 tsp tbsp floz cup pt qt gal
from> l
to> l
value in l> -0
0 l = 0 l
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 5
0 l = 0 l
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 6
Bye.
What was typed (43 lines)
5
9
1
parsec
1
1
kg
1
km
mi
abc
1
km
m
1
km
m
-3
3
F
C
-500
3
K
C
-0.5
2
g
oz
nan
2
g
oz
inf
4
l
l
-0
5
6
basic
interpreter: byte-equal
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> km
to> mi
value in km> 10
10 km = 6.21371 mi
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 5
6.21371 mi = 10 km
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 2
weight units: mg g kg t oz lb
from> lb
to> kg
value in lb> 150
150 lb = 68.0389 kg
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 3
temperature units: C F K
from> F
to> C
value in F> 98.6
98.6 F = 37 C
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 3
temperature units: C F K
from> C
to> F
value in C> 100
100 C = 212 F
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 3
temperature units: C F K
from> K
to> C
value in K> 0
0 K = -273.15 C
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 4
volume units: ml l m3 tsp tbsp floz cup pt qt gal
from> gal
to> l
value in gal> 2
2 gal = 7.57082 l
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 4
volume units: ml l m3 tsp tbsp floz cup pt qt gal
from> cup
to> ml
value in cup> 1
1 cup = 236.588 ml
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 5
236.588 ml = 1 cup
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 1
length units: mm cm m km in ft yd mi
from> ft
to> cm
value in ft> 6
6 ft = 182.88 cm
== Unit converter ==
1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit
choice> 6
Bye.
What was typed (35 lines)
1
km
mi
10
5
2
lb
kg
150
3
F
C
98.6
3
C
F
100
3
K
C
0
4
gal
l
2
4
cup
ml
1
5
1
ft
cm
6
6
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P002 unit converter: lengths, weights, temperatures and volumes, from any
# unit to any other unit of the same kind, and back again. What is typed is
# checked before anything is converted: the unit must exist and measure the
# kind chosen, and the value must be a finite number the quantity can have -
# no negative length, nothing below absolute zero.
import units
import text
def ask_unit(prompt, kind):
# A unit of this kind, or None once the problem has been shown.
text.trim(input(prompt)) => u
units.kind_of(u) => k
if k == None:
if u == "":
"Type a unit." ^0
else:
("Unknown unit: " + u + ". " + kind + " units: " + text.join(units.symbols(kind))) ^0
return None
if k != kind:
(u + " is a " + k + " unit, not a " + kind + " unit.") ^0
return None
return u
def ask_value(prompt, u, kind):
# A value a quantity in unit u can have, or None once the problem has
# been shown.
text.trim(input(prompt)) => typed
if typed == "":
"Type a number." ^0
return None
try:
float(typed) => x
except ValueError:
("Not a number: " + typed) ^0
return None
if not (x - x == 0):
("Not a finite number: " + typed) ^0
return None
if x < units.lowest(u):
if kind == "temperature":
(text.num(x) + " " + u + " is below absolute zero (" + text.num(units.lowest(u)) + " " + u + ").") ^0
else:
("A " + kind + " cannot be negative.") ^0
return None
return x
def convert_once(kind, last):
# One conversion of this kind. Returns the conversion "reverse last"
# works from next: this one, or the previous one if nothing was converted.
(kind + " units: " + text.join(units.symbols(kind))) ^0
ask_unit("from> ", kind) => frm
if frm == None:
return last
ask_unit("to> ", kind) => to
if to == None:
return last
ask_value("value in " + frm + "> ", frm, kind) => x
if x == None:
return last
units.convert(x, frm, to) => y
(text.num(x) + " " + frm + " = " + text.num(y) + " " + to) ^0
return [x, frm, to, y]
None => last
True => running
while running:
"" ^0
"== Unit converter ==" ^0
"1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit" ^0
text.trim(input("choice> ")) => choice
if choice == "1" or choice == "2" or choice == "3" or choice == "4":
convert_once(units.kinds[int(choice) - 1], last) => last
elif choice == "5":
if last == None:
"Nothing to reverse yet." ^0
else:
# From the exact result, not the six digits shown for it.
units.convert(last[3], last[2], last[1]) => back
(text.num(last[3]) + " " + last[2] + " = " + text.num(back) + " " + last[1]) ^0
[last[3], last[2], last[1], back] => last
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import units
import text
def ask_unit(prompt, kind):
u = text.trim(input(prompt))
k = units.kind_of(u)
if k == None:
if u == "":
print("Type a unit.")
else:
print("Unknown unit: " + u + ". " + kind + " units: " + text.join(units.symbols(kind)))
return None
if k != kind:
print(u + " is a " + k + " unit, not a " + kind + " unit.")
return None
return u
def ask_value(prompt, u, kind):
typed = text.trim(input(prompt))
if typed == "":
print("Type a number.")
return None
try:
x = float(typed)
except ValueError:
print("Not a number: " + typed)
return None
if not x - x == 0:
print("Not a finite number: " + typed)
return None
if x < units.lowest(u):
if kind == "temperature":
print(text.num(x) + " " + u + " is below absolute zero (" + text.num(units.lowest(u)) + " " + u + ").")
else:
print("A " + kind + " cannot be negative.")
return None
return x
def convert_once(kind, last):
print(kind + " units: " + text.join(units.symbols(kind)))
frm = ask_unit("from> ", kind)
if frm == None:
return last
to = ask_unit("to> ", kind)
if to == None:
return last
x = ask_value("value in " + frm + "> ", frm, kind)
if x == None:
return last
y = units.convert(x, frm, to)
print(text.num(x) + " " + frm + " = " + text.num(y) + " " + to)
return [x, frm, to, y]
last = None
running = True
while running:
print("")
print("== Unit converter ==")
print("1) length 2) weight 3) temperature 4) volume 5) reverse last 6) quit")
choice = text.trim(input("choice> "))
if choice == "1" or choice == "2" or choice == "3" or choice == "4":
last = convert_once(units.kinds[int(choice) - 1], last)
elif choice == "5":
if last == None:
print("Nothing to reverse yet.")
else:
back = units.convert(last[3], last[2], last[1])
print(text.num(last[3]) + " " + last[2] + " = " + text.num(back) + " " + last[1])
last = [last[3], last[2], last[1], back]
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
units.eml
eml# P002 unit converter - the units. Length, weight and volume convert through
# a base unit by a factor: the metre, the gram and the millilitre.
# Temperature cannot: its scales start from different zeros, so it converts
# through Celsius with an offset as well as a scale. The factors are the
# exact definitions (1 in = 2.54 cm, 1 lb = 453.59237 g, 1 US gallon =
# 3785.411784 ml), not rounded values.
["length", "weight", "temperature", "volume"] => kinds
{
"length": ["mm", "cm", "m", "km", "in", "ft", "yd", "mi"],
"weight": ["mg", "g", "kg", "t", "oz", "lb"],
"temperature": ["C", "F", "K"],
"volume": ["ml", "l", "m3", "tsp", "tbsp", "floz", "cup", "pt", "qt", "gal"],
} => unit_lists
# How many base units one unit is. The volumes from tsp on are US customary.
{
"mm": 0.001,
"cm": 0.01,
"m": 1.0,
"km": 1000.0,
"in": 0.0254,
"ft": 0.3048,
"yd": 0.9144,
"mi": 1609.344,
"mg": 0.001,
"g": 1.0,
"kg": 1000.0,
"t": 1000000.0,
"oz": 28.349523125,
"lb": 453.59237,
"ml": 1.0,
"l": 1000.0,
"m3": 1000000.0,
"tsp": 4.92892159375,
"tbsp": 14.78676478125,
"floz": 29.5735295625,
"cup": 236.5882365,
"pt": 473.176473,
"qt": 946.352946,
"gal": 3785.411784,
} => factors
def kind_of(u):
# The kind a unit measures, or None for a unit this converter does not know.
for k in kinds:
if u in unit_lists[k]:
return k
return None
def symbols(kind):
return unit_lists[kind]
def to_celsius(x, u):
if u == "F":
return (x - 32) * 5 / 9
if u == "K":
return x - 273.15
return x
def from_celsius(c, u):
if u == "F":
return c * 9 / 5 + 32
if u == "K":
return c + 273.15
return c
def convert(x, frm, to):
# x in unit frm, expressed in unit to. Both units measure the same kind:
# the menu checks that before it asks for a value.
if kind_of(frm) == "temperature":
return from_celsius(to_celsius(x, frm), to)
return x * factors[frm] / factors[to]
def lowest(u):
# The smallest value a quantity in unit u can have: absolute zero for a
# temperature, nothing below zero for everything else.
if u == "C":
return -273.15
if u == "F":
return -459.67
return 0.0
Python projection (units.py)
kinds = ["length", "weight", "temperature", "volume"]
unit_lists = {"length": ["mm", "cm", "m", "km", "in", "ft", "yd", "mi"], "weight": ["mg", "g", "kg", "t", "oz", "lb"], "temperature": ["C", "F", "K"], "volume": ["ml", "l", "m3", "tsp", "tbsp", "floz", "cup", "pt", "qt", "gal"]}
factors = {"mm": 0.001, "cm": 0.01, "m": 1.0, "km": 1000.0, "in": 0.0254, "ft": 0.3048, "yd": 0.9144, "mi": 1609.344, "mg": 0.001, "g": 1.0, "kg": 1000.0, "t": 1000000.0, "oz": 28.349523125, "lb": 453.59237, "ml": 1.0, "l": 1000.0, "m3": 1000000.0, "tsp": 4.92892159375, "tbsp": 14.78676478125, "floz": 29.5735295625, "cup": 236.5882365, "pt": 473.176473, "qt": 946.352946, "gal": 3785.411784}
def kind_of(u):
for k in kinds:
if u in unit_lists[k]:
return k
return None
def symbols(kind):
return unit_lists[kind]
def to_celsius(x, u):
if u == "F":
return (x - 32) * 5 / 9
if u == "K":
return x - 273.15
return x
def from_celsius(c, u):
if u == "F":
return c * 9 / 5 + 32
if u == "K":
return c + 273.15
return c
def convert(x, frm, to):
if kind_of(frm) == "temperature":
return from_celsius(to_celsius(x, frm), to)
return x * factors[frm] / factors[to]
def lowest(u):
if u == "C":
return -273.15
if u == "F":
return -459.67
return 0.0
text.eml
eml# P002 unit converter - text helpers. The interpreter that checks every
# session does not run string methods yet, so trimming and joining are
# written out here.
def trim(s):
# s without the spaces at either end.
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def join(words):
# The words with one space between each two.
"" => out
for w in words:
if out != "":
out + " " => out
out + w => out
return out
def num(x):
# Six significant digits, as a calculator shows them. Adding 0.0 turns
# -0.0 into 0.0, so a typed "-0" never comes back as "-0".
return "%g" % (x + 0.0)
Python projection (text.py)
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def join(words):
out = ""
for w in words:
if out != "":
out = out + " "
out = out + w
return out
def num(x):
return "%g" % (x + 0.0)