Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixed unit list conversion #568

Merged
merged 13 commits into from
Oct 8, 2024
8 changes: 4 additions & 4 deletions book/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ def generate_example(
print(path_out)

code = []
with open(path_in, "r") as fin:
with open(path_in, "r", encoding="utf-8") as fin:
for line in fin:
if not (strip_asserts and "assert_eq" in line):
code.append(line)

url = f"https://numbat.dev/?q={urllib.parse.quote_plus(''.join(code))}"

with open(path_out, "w") as fout:
with open(path_out, "w", encoding="utf-8") as fout:
fout.write("<!-- This file is autogenerated! Do not modify it -->\n")
fout.write("\n")
fout.write(f"# {title}\n")
Expand Down Expand Up @@ -81,7 +81,7 @@ def xkcd_footer(number, img_name):
)

path_units = SCRIPT_DIR / "src" / "list-units.md"
with open(path_units, "w") as f:
with open(path_units, "w", encoding="utf-8") as f:
print("Generating list of units...", flush=True)
subprocess.run(
["cargo", "run", "--release", "--quiet", "--example=inspect", "units"],
Expand All @@ -92,7 +92,7 @@ def xkcd_footer(number, img_name):

def list_of_functions(file_name, document):
path = SCRIPT_DIR / "src" / f"list-functions-{file_name}.md"
with open(path, "w") as f:
with open(path, "w", encoding="utf-8") as f:
print(f"# {document['title']}\n", file=f, flush=True)

if introduction := document.get("introduction"):
Expand Down
33 changes: 25 additions & 8 deletions book/src/list-functions-other.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,20 +155,37 @@ Get the ionization energy of hydrogen.

Defined in: `units::mixed`

### `unit_list` (Unit list)
Convert a value to a mixed representation using the provided units.

```nbt
fn unit_list<D: Dim>(units: List<D>, value: D) -> List<D>
```

<details>
<summary>Examples</summary>

<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code" onclick=" window.open('https://numbat.dev/?q=5500%20m%20%7C%3E%20unit%5Flist%28%5Bmiles%2C%20yards%2C%20feet%2C%20inches%5D%29')""></button></div><code class="language-nbt hljs numbat">>>> 5500 m |> unit_list([miles, yards, feet, inches])

= [3 mi, 734 yd, 2 ft, 7.43307 in] [List<Length>]
</code></pre>

</details>

### `DMS` (Degrees, minutes, seconds)
Convert an angle to a mixed degrees, (arc)minutes, and (arc)seconds representation. Also called sexagesimal degree notation.
More information [here](https://en.wikipedia.org/wiki/Sexagesimal_degree).

```nbt
fn DMS(alpha: Angle) -> String
fn DMS(alpha: Angle) -> List<Angle>
```

<details>
<summary>Examples</summary>

<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code" onclick=" window.open('https://numbat.dev/?q=46%2E5858%C2%B0%20%2D%3E%20DMS')""></button></div><code class="language-nbt hljs numbat">>>> 46.5858° -> DMS

= "46° 35′ 9″" [String]
= [46°, 35′, 8.88″] [List<Scalar>]
</code></pre>

</details>
Expand All @@ -178,15 +195,15 @@ Convert an angle to a mixed degrees and decimal minutes representation.
More information [here](https://en.wikipedia.org/wiki/Decimal_degrees).

```nbt
fn DM(alpha: Angle) -> String
fn DM(alpha: Angle) -> List<Angle>
```

<details>
<summary>Examples</summary>

<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code" onclick=" window.open('https://numbat.dev/?q=46%2E5858%C2%B0%20%2D%3E%20DM')""></button></div><code class="language-nbt hljs numbat">>>> 46.5858° -> DM

= "46° 35.148′" [String]
= [46°, 35.148′] [List<Scalar>]
</code></pre>

</details>
Expand All @@ -196,15 +213,15 @@ Convert a length to a mixed feet and inches representation.
More information [here](https://en.wikipedia.org/wiki/Foot_(unit)).

```nbt
fn feet_and_inches(length: Length) -> String
fn feet_and_inches(length: Length) -> List<Length>
```

<details>
<summary>Examples</summary>

<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code" onclick=" window.open('https://numbat.dev/?q=180%20cm%20%2D%3E%20feet%5Fand%5Finches')""></button></div><code class="language-nbt hljs numbat">>>> 180 cm -> feet_and_inches

= "5 ft 10.8661 in" [String]
= [5 ft, 10.8661 in] [List<Length>]
</code></pre>

</details>
Expand All @@ -214,15 +231,15 @@ Convert a mass to a mixed pounds and ounces representation.
More information [here](https://en.wikipedia.org/wiki/Pound_(mass)).

```nbt
fn pounds_and_ounces(mass: Mass) -> String
fn pounds_and_ounces(mass: Mass) -> List<Mass>
```

<details>
<summary>Examples</summary>

<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code" onclick=" window.open('https://numbat.dev/?q=1%20kg%20%2D%3E%20pounds%5Fand%5Founces')""></button></div><code class="language-nbt hljs numbat">>>> 1 kg -> pounds_and_ounces

= "2 lb 3.27396 oz" [String]
= [2 lb, 3.27396 oz] [List<Mass>]
</code></pre>

</details>
Expand Down
47 changes: 31 additions & 16 deletions examples/tests/mixed_units.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,48 @@
assert_eq(38° + 53′ + 23″, 38.8897°, 1e-4°)
assert_eq(-(77° + 0′ + 32″), -77.0089°, 1e-4°)

assert_eq(38.8897° -> DMS, "38° 53′ 23″")
assert_eq(-77.0089° -> DMS, "-77° 0′ 32″")
assert_eq("{38.8897° -> DMS}", "{[38°, 53′, 22.92″]}")
assert_eq("{-77.0089° -> DMS}", "{[-77°, -0′, -32.04″]}")

## Stuttgart
assert_eq(48° + 46′ + 32″, 48.7756°, 1e-4°)
assert_eq(9° + 10′ + 58″, 9.1828°, 1e-4°)

assert_eq(48.7756° -> DMS, "48° 46′ 32")
assert_eq(9.1828° -> DMS, " 10′ 58")
assert_eq("{48.7756° -> DMS}", "{[48°, 46′, 32.16″]}")
assert_eq("{9.1828° -> DMS}", "{[9°, 10′, 58.08″]}")

# Degrees, decimal minutes (DM)

assert_eq(38.8897° -> DM, "38° 53.382′")
assert_eq(-77.0089° -> DM, "-77° 0.534′")
assert_eq("{38.8897° -> DM}", "{[38°, 53.382′]}")
assert_eq("{-77.0089° -> DM}", "{[-77°, -0.534′]}")

# Feet and inches

assert_eq(5.5 ft -> feet_and_inches, "5 ft 6 in")
assert_eq(6.75 ft -> feet_and_inches, "6 ft 9 in")
assert_eq(-5.5 ft -> feet_and_inches, "-5 ft 6 in")
assert_eq(0 -> feet_and_inches, "0 ft 0 in")
assert_eq(1 ft -> feet_and_inches, "1 ft 0 in")
assert_eq(2.345 inch -> feet_and_inches, "0 ft 2.345 in")
assert_eq("{5.5 ft -> feet_and_inches}", "{[5 ft, 6 in]}")
assert_eq("{6.75 ft -> feet_and_inches}", "{[6 ft, 9 in]}")
assert_eq("{-5.5 ft -> feet_and_inches}", "{[-5 ft, -6 in]}")
assert_eq("{0 -> feet_and_inches}", "{[0 ft, 0 in]}")
assert_eq("{1 ft -> feet_and_inches}", "{[1 ft, 0 in]}")
assert_eq("{2.345 inch -> feet_and_inches}", "{[0 ft, 2.345 in]}")

# Pounds and ounces

assert_eq(5 lb -> pounds_and_ounces, "5 lb 0 oz")
assert_eq(5.5 lb -> pounds_and_ounces, "5 lb 8 oz")
assert_eq(6.75 lb -> pounds_and_ounces, "6 lb 12 oz")
assert_eq(-5.5 lb -> pounds_and_ounces, "-5 lb 8 oz")
assert_eq("{5 lb -> pounds_and_ounces}", "{[5 lb, 0 oz]}")
assert_eq("{5.5 lb -> pounds_and_ounces}", "{[5 lb, 8 oz]}")
assert_eq("{6.75 lb -> pounds_and_ounces}", "{[6 lb, 12 oz]}")
assert_eq("{-5.5 lb -> pounds_and_ounces}", "{[-5 lb, -8 oz]}")

# Unit list

let test1 = 12 m + 34 cm + 5 mm + 678 µm
assert_eq(test1 |> unit_list([m]) |> head, test1)
assert_eq(test1 |> unit_list([m, cm]) |> sum, test1)
assert_eq(test1 |> unit_list([m, cm, mm]) |> sum, test1)
assert_eq(test1 |> unit_list([m, cm, mm, µm]) |> sum, test1)

let test2 = 12 degree + 34 arcminute + 5 arcsec
assert_eq(test2 |> unit_list([degree]) |> head, test2)
assert_eq(test2 |> unit_list([degree, arcmin]) |> sum, test2)
assert_eq(test2 |> unit_list([degree, arcmin, arcsec]) |> sum, test2)


32 changes: 11 additions & 21 deletions numbat/modules/core/mixed_units.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,15 @@ use core::lists

# Helper functions for mixed-unit conversions. See units::mixed for more.

fn _mixed_units_helper<D: Dim>(q: D, units: List<D>, names: List<String>, round_last: Bool) -> List<String> =
if is_empty(units)
then
[]
else
cons(
if len(units) == 1
then
if round_last
then "{round(q / head(units))}{head(names)}"
else "{q / head(units)}{head(names)}"
else "{trunc(q / head(units))}{head(names)}",
_mixed_units_helper(
q - trunc(q / head(units)) * head(units),
tail(units),
tail(names),
round_last))
fn _zero_length<A: Dim>(val: A) -> A = val * 0 -> val

fn _mixed_units<D: Dim>(q: D, units: List<D>, names: List<String>, round_last: Bool) -> String =
if q < 0
then str_append("-", _mixed_units(-q, units, names, round_last))
else join(_mixed_units_helper(q, units, names, round_last), "")
fn _mixed_unit_list<D: Dim>(val: D, units: List<D>, acc: List<D>) -> List<D> =
if val == 0
then concat(acc, map(_zero_length, units))
else if len(units) == 1
then cons_end(val -> head(units), acc)
else _mixed_unit_list(val - unit_val, tail(units), cons_end(unit_val, acc))
where unit_val: D =
if (len(units) > 0)
then (val |> trunc_in(head(units)))
else error("Units list cannot be empty")
24 changes: 15 additions & 9 deletions numbat/modules/units/mixed.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,36 @@ use core::mixed_units
use units::si
use units::imperial

@name("Unit list")
@description("Convert a value to a mixed representation using the provided units.")
@example("5500 m |> unit_list([miles, yards, feet, inches])")
fn unit_list<D: Dim>(units: List<D>, value: D) -> List<D> = _mixed_unit_list(value, units, [])

@name("Degrees, minutes, seconds")
@description("Convert an angle to a mixed degrees, (arc)minutes, and (arc)seconds representation. Also called sexagesimal degree notation.")
@url("https://en.wikipedia.org/wiki/Sexagesimal_degree")
@example("46.5858° -> DMS")
fn DMS(alpha: Angle) -> String =
_mixed_units(alpha, [deg, arcmin, arcsec], ["° ", "′ ", "″"], true)
fn DMS(alpha: Angle) -> List<Angle> =
unit_list([degree, arcminute, arcsecond], alpha)

@name("Degrees, decimal minutes")
@description("Convert an angle to a mixed degrees and decimal minutes representation.")
@example("46.5858° -> DM")
@url("https://en.wikipedia.org/wiki/Decimal_degrees")
fn DM(alpha: Angle) -> String =
_mixed_units(alpha, [deg, arcmin], ["° ", "′"], false)
@example("46.5858° -> DM")
fn DM(alpha: Angle) -> List<Angle> =
unit_list([degree, arcminute], alpha)

@name("Feet and inches")
@description("Convert a length to a mixed feet and inches representation.")
@url("https://en.wikipedia.org/wiki/Foot_(unit)")
@example("180 cm -> feet_and_inches")
fn feet_and_inches(length: Length) -> String =
_mixed_units(length, [foot, inch], [" ft ", " in"], false)
fn feet_and_inches(length: Length) -> List<Length> =
unit_list([foot, inch], length)

@name("Pounds and ounces")
@description("Convert a mass to a mixed pounds and ounces representation.")
@url("https://en.wikipedia.org/wiki/Pound_(mass)")
@example("1 kg -> pounds_and_ounces")
fn pounds_and_ounces(mass: Mass) -> String =
_mixed_units(mass, [pound, ounce], [" lb ", " oz"], false)
fn pounds_and_ounces(mass: Mass) -> List<Mass> =
unit_list([pound, ounce], mass)

4 changes: 2 additions & 2 deletions numbat/modules/units/si.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,12 @@ unit degree: Angle = π / 180 × radian

@name("Minute of arc")
@url("https://en.wikipedia.org/wiki/Minute_and_second_of_arc")
@aliases(arcminutes, arcmin, ′)
@aliases(arcminutes, arcmin, ′: short)
unit arcminute: Angle = 1 / 60 × degree

@name("Second of arc")
@url("https://en.wikipedia.org/wiki/Minute_and_second_of_arc")
@aliases(arcseconds, arcsec, ″)
@aliases(arcseconds, arcsec, ″: short)
unit arcsecond: Angle = 1 / 60 × arcminute

@name("Are")
Expand Down
Loading