Bläddra i källkod

Safe versions of round/ceil/floor/trunc

David Peter 1 år sedan
förälder
incheckning
ff57ae9adf

+ 2 - 2
book/src/example-paper_size.md

@@ -1,7 +1,7 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # Paper sizes
-<a href="https://numbat.dev/?q=%23+Compute+ISO+216+paper+sizes+for+the+A+series%0A%23%0A%23+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FISO_216%0A%0Astruct+PaperSize+%7B%0A++++width%3A+Length%2C%0A++++height%3A+Length%2C%0A%7D%0A%0Afn+paper_size_A%28n%3A+Scalar%29+-%3E+PaperSize+%3D%0A++if+n+%3D%3D+0%0A++++then%0A++++++PaperSize+%7B%0A++++++++width%3A+841+mm%2C%0A++++++++height%3A+1189+mm%0A++++++%7D%0A++++else%0A++++++PaperSize+%7B%0A++++++++width%3A+floor%28paper_size_A%28n+-+1%29.height+%2F+2%29%2C%0A++++++++height%3A+paper_size_A%28n+-+1%29.width%2C%0A++++++%7D%0A%0A%0Afn+paper_area%28size%3A+PaperSize%29+-%3E+Area+%3D%0A++++size.width+%2A+size.height%0A%0A%0Afn+size_as_string%28size%3A+PaperSize%29+%3D+%22%7Bsize.width%3A%3E4%7D+%C3%97+%7Bsize.height%3A%3E5%7D+++%7Bpaper_area%28size%29+-%3E+cm%C2%B2%3A%3E6.1f%7D%22%0Afn+row%28n%29+%3D+%22A%7Bn%3A%3C3%7D+++%7Bsize_as_string%28paper_size_A%28n%29%29%7D%22%0A%0Aprint%28%22Name++++Width+++++Height++++++++Area++%22%29%0Aprint%28%22----+++-------+++--------+++----------%22%29%0Aprint%28join%28map%28row%2C+range%280%2C+10%29%29%2C+%22%5Cn%22%29%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Compute+ISO+216+paper+sizes+for+the+A+series%0A%23%0A%23+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FISO_216%0A%0Astruct+PaperSize+%7B%0A++++width%3A+Length%2C%0A++++height%3A+Length%2C%0A%7D%0A%0Afn+paper_size_A%28n%3A+Scalar%29+-%3E+PaperSize+%3D%0A++if+n+%3D%3D+0%0A++++then%0A++++++PaperSize+%7B%0A++++++++width%3A+841+mm%2C%0A++++++++height%3A+1189+mm%0A++++++%7D%0A++++else%0A++++++PaperSize+%7B%0A++++++++width%3A+floor_in%28mm%2C+paper_size_A%28n+-+1%29.height+%2F+2%29%2C%0A++++++++height%3A+paper_size_A%28n+-+1%29.width%2C%0A++++++%7D%0A%0A%0Afn+paper_area%28size%3A+PaperSize%29+-%3E+Area+%3D%0A++++size.width+%2A+size.height%0A%0A%0Afn+size_as_string%28size%3A+PaperSize%29+%3D+%22%7Bsize.width%3A%3E4%7D+%C3%97+%7Bsize.height%3A%3E5%7D+++%7Bpaper_area%28size%29+-%3E+cm%C2%B2%3A%3E6.1f%7D%22%0Afn+row%28n%29+%3D+%22A%7Bn%3A%3C3%7D+++%7Bsize_as_string%28paper_size_A%28n%29%29%7D%22%0A%0Aprint%28%22Name++++Width+++++Height++++++++Area++%22%29%0Aprint%28%22----+++-------+++--------+++----------%22%29%0Aprint%28join%28map%28row%2C+range%280%2C+10%29%29%2C+%22%5Cn%22%29%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Compute ISO 216 paper sizes for the A series
@@ -22,7 +22,7 @@ fn paper_size_A(n: Scalar) -> PaperSize =
       }
     else
       PaperSize {
-        width: floor(paper_size_A(n - 1).height / 2),
+        width: floor_in(mm, paper_size_A(n - 1).height / 2),
         height: paper_size_A(n - 1).width,
       }
 

+ 2 - 2
book/src/example-population_growth.md

@@ -1,7 +1,7 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # Population growth
-<a href="https://numbat.dev/?q=%23+Exponential+model+for+population+growth%0A%0Alet+initial_population+%3D+50_000+people%0Alet+growth_rate+%3D+2%25+per+year%0A%0Afn+predict_population%28t%29+%3D%0A++++initial_population+%C3%97+e%5E%28growth_rate%C2%B7t%29+%7C%3E+round%0A%0Aprint%28%22Population+in++20+years%3A+%7Bpredict_population%2820+years%29%7D%22%29%0Aprint%28%22Population+in+100+years%3A+%7Bpredict_population%281+century%29%7D%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Exponential+model+for+population+growth%0A%0Alet+initial_population+%3D+50_000+people%0Alet+growth_rate+%3D+2%25+per+year%0A%0Afn+predict_population%28t%29+%3D%0A++++initial_population+%C3%97+e%5E%28growth_rate%C2%B7t%29+%7C%3E+round_in%28people%29%0A%0Aprint%28%22Population+in++20+years%3A+%7Bpredict_population%2820+years%29%7D%22%29%0Aprint%28%22Population+in+100+years%3A+%7Bpredict_population%281+century%29%7D%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Exponential model for population growth
@@ -10,7 +10,7 @@ let initial_population = 50_000 people
 let growth_rate = 2% per year
 
 fn predict_population(t) =
-    initial_population × e^(growth_rate·t) |> round
+    initial_population × e^(growth_rate·t) |> round_in(people)
 
 print("Population in  20 years: {predict_population(20 years)}")
 print("Population in 100 years: {predict_population(1 century)}")

+ 26 - 27
book/src/example-xkcd_2585.md

@@ -1,40 +1,39 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # XKCD 2585
-<a href="https://numbat.dev/?q=%23+Rounding%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2585%2F%0A%0A17+mph%0A%0Aans+-%3E+meters%2Fsec++++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+kph+++++++++++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+kph+++++++++++-%3E+round%0Aans+-%3E+furlongs%2Fhour+-%3E+round%0Aans+-%3E+mph+++++++++++-%3E+round%0Aans+-%3E+m%2Fs+++++++++++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+yards%2Fsec+++++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+m%2Fs+++++++++++-%3E+round%0Aans+-%3E+mph+++++++++++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+yards%2Fsec+++++-%3E+round%0Aans+-%3E+fathoms%2Fsec+++-%3E+round%0Aans+-%3E+knots+++++++++-%3E+round%0Aans+-%3E+furlongs%2Fmin++-%3E+round%0Aans+-%3E+mph+++++++++++-%3E+round%0A%0Aprint%28%22I+can+ride+my+bike+at+%7Bans%7D.%22%29%0Aprint%28%22If+you+round.%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Rounding%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2585%2F%0A%0Alet+speed+%3D+17+mph+%7C%3E%0A++round_in%28meters%2Fsec%29+%7C%3E%0A++round_in%28knots%29+%7C%3E%0A++round_in%28fathoms%2Fsec%29+%7C%3E%0A++round_in%28furlongs%2Fmin%29+%7C%3E%0A++round_in%28fathoms%2Fsec%29+%7C%3E%0A++round_in%28kph%29+%7C%3E%0A++round_in%28knots%29+%7C%3E%0A++round_in%28kph%29+%7C%3E%0A++round_in%28furlongs%2Fhour%29+%7C%3E%0A++round_in%28mi%2Fh%29+%7C%3E%0A++round_in%28m%2Fs%29+%7C%3E%0A++round_in%28furlongs%2Fmin%29+%7C%3E%0A++round_in%28yards%2Fsec%29+%7C%3E%0A++round_in%28fathoms%2Fsec%29+%7C%3E%0A++round_in%28m%2Fs%29+%7C%3E%0A++round_in%28mph%29+%7C%3E%0A++round_in%28furlongs%2Fmin%29+%7C%3E%0A++round_in%28knots%29+%7C%3E%0A++round_in%28yards%2Fsec%29+%7C%3E%0A++round_in%28fathoms%2Fsec%29+%7C%3E%0A++round_in%28knots%29+%7C%3E%0A++round_in%28furlongs%2Fmin%29+%7C%3E%0A++round_in%28mph%29%0A%0Aprint%28%22I+can+ride+my+bike+at+%7Bspeed%7D.%22%29%0Aprint%28%22If+you+round.%22%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Rounding
 #
 # https://xkcd.com/2585/
 
-17 mph
+let speed = 17 mph |>
+  round_in(meters/sec) |>
+  round_in(knots) |>
+  round_in(fathoms/sec) |>
+  round_in(furlongs/min) |>
+  round_in(fathoms/sec) |>
+  round_in(kph) |>
+  round_in(knots) |>
+  round_in(kph) |>
+  round_in(furlongs/hour) |>
+  round_in(mi/h) |>
+  round_in(m/s) |>
+  round_in(furlongs/min) |>
+  round_in(yards/sec) |>
+  round_in(fathoms/sec) |>
+  round_in(m/s) |>
+  round_in(mph) |>
+  round_in(furlongs/min) |>
+  round_in(knots) |>
+  round_in(yards/sec) |>
+  round_in(fathoms/sec) |>
+  round_in(knots) |>
+  round_in(furlongs/min) |>
+  round_in(mph)
 
-ans -> meters/sec    -> round
-ans -> knots         -> round
-ans -> fathoms/sec   -> round
-ans -> furlongs/min  -> round
-ans -> fathoms/sec   -> round
-ans -> kph           -> round
-ans -> knots         -> round
-ans -> kph           -> round
-ans -> furlongs/hour -> round
-ans -> mph           -> round
-ans -> m/s           -> round
-ans -> furlongs/min  -> round
-ans -> yards/sec     -> round
-ans -> fathoms/sec   -> round
-ans -> m/s           -> round
-ans -> mph           -> round
-ans -> furlongs/min  -> round
-ans -> knots         -> round
-ans -> yards/sec     -> round
-ans -> fathoms/sec   -> round
-ans -> knots         -> round
-ans -> furlongs/min  -> round
-ans -> mph           -> round
-
-print("I can ride my bike at {ans}.")
+print("I can ride my bike at {speed}.")
 print("If you round.")
 ```
 

+ 3 - 3
book/src/example-xkcd_2812.md

@@ -1,7 +1,7 @@
 <!-- This file is autogenerated! Do not modify it -->
 
 # XKCD 2812
-<a href="https://numbat.dev/?q=%23+Solar+panel+placement%0A%23%0A%23+Solar+energy+tip%3A+To+maximize+sun+exposure%2C+always%0A%23+orient+your+panels+downward+and+install+them+on+the%0A%23+surface+of+the+sun.%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2812%2F%0A%23%0A%23+%5B1%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSolar_luminosity%0A%23+%5B2%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSun%0A%0Alet+net_metering_rate+%3D+%24+0.20+%2F+kWh%0Alet+panel_area+%3D+1+m%C2%B2%0Alet+panel_efficiency+%3D+20+%25%0A%0Afn+savings%28i%3A+Irradiance%29+-%3E+Money+%2F+Time+%3D%0A++++net_metering_rate+%C3%97+i+%C3%97+panel_area+%C3%97+panel_efficiency+-%3E+%24%2Fyear%0A%0Aprint%28%22Option+A%3A+On+the+roof%2C+south+facing%22%29%0A%0Alet+savings_a+%3D+savings%284+kWh%2Fm%C2%B2%2Fday%29%0A%0Aprint%28savings_a+%7C%3E+round%29%0A%0Aprint%28%29%0Aprint%28%22Option+B%3A+On+the+sun%2C+downward+facing%22%29%0A%0Adimension+Luminosity+%3D+Power%0A%0Alet+sun_luminosity%3A+Luminosity+%3D+3.828e26+W++%23+%5B1%5D%0Alet+sun_area%3A+Area+%3D+6.09e12+km%5E2++++++++++++%23+%5B2%5D%0A%0Alet+savings_b+%3D+savings%28sun_luminosity+%2F+sun_area%29%0A%0Aprint%28savings_b+%7C%3E+round%29%0A"><i class="fa fa-play"></i> Run this example</a>
+<a href="https://numbat.dev/?q=%23+Solar+panel+placement%0A%23%0A%23+Solar+energy+tip%3A+To+maximize+sun+exposure%2C+always%0A%23+orient+your+panels+downward+and+install+them+on+the%0A%23+surface+of+the+sun.%0A%23%0A%23+https%3A%2F%2Fxkcd.com%2F2812%2F%0A%23%0A%23+%5B1%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSolar_luminosity%0A%23+%5B2%5D+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSun%0A%0Alet+net_metering_rate+%3D+%24+0.20+%2F+kWh%0Alet+panel_area+%3D+1+m%C2%B2%0Alet+panel_efficiency+%3D+20+%25%0A%0Afn+savings%28i%3A+Irradiance%29+-%3E+Money+%2F+Time+%3D%0A++++net_metering_rate+%C3%97+i+%C3%97+panel_area+%C3%97+panel_efficiency+-%3E+%24%2Fyear%0A%0Aprint%28%22Option+A%3A+On+the+roof%2C+south+facing%22%29%0A%0Alet+savings_a+%3D+savings%284+kWh%2Fm%C2%B2%2Fday%29%0A%0Aprint%28savings_a+%7C%3E+round_in%28%24%2Fyear%29%29%0A%0Aprint%28%29%0Aprint%28%22Option+B%3A+On+the+sun%2C+downward+facing%22%29%0A%0Adimension+Luminosity+%3D+Power%0A%0Alet+sun_luminosity%3A+Luminosity+%3D+3.828e26+W++%23+%5B1%5D%0Alet+sun_area%3A+Area+%3D+6.09e12+km%5E2++++++++++++%23+%5B2%5D%0A%0Alet+savings_b+%3D+savings%28sun_luminosity+%2F+sun_area%29%0A%0Aprint%28savings_b+%7C%3E+round_in%28%24%2Fyear%29%29%0A"><i class="fa fa-play"></i> Run this example</a>
 
 ``` numbat
 # Solar panel placement
@@ -26,7 +26,7 @@ print("Option A: On the roof, south facing")
 
 let savings_a = savings(4 kWh/m²/day)
 
-print(savings_a |> round)
+print(savings_a |> round_in($/year))
 
 print()
 print("Option B: On the sun, downward facing")
@@ -38,7 +38,7 @@ let sun_area: Area = 6.09e12 km^2            # [2]
 
 let savings_b = savings(sun_luminosity / sun_area)
 
-print(savings_b |> round)
+print(savings_b |> round_in($/year))
 ```
 
 <p align="center" style="margin-top: 2em"><a href="https://xkcd.com/2812/"><img src="https://imgs.xkcd.com/comics/solar_panel_placement.png" alt="XKCD 2812" style="max-width: 100%"></a><br>Source: <a href="https://xkcd.com/2812/">https://xkcd.com/2812/</a></p>

+ 36 - 8
book/src/list-functions-math.md

@@ -45,35 +45,63 @@ fn sqr<D: Dim>(x: D) -> D^2
 ```
 
 ### `round` (Rounding)
-Round to the nearest integer. If the value is half-way between two integers, round away from \\( 0 \\).
+Round to the nearest integer. If the value is half-way between two integers, round away from \\( 0 \\). See also: `round_in`.
 More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.round).
 
 ```nbt
-fn round<T: Dim>(x: T) -> T
+fn round(x: Scalar) -> Scalar
+```
+
+### `round_in` (Rounding)
+Round to the nearest multiple of `base`. For example: `round_in(m, 5.3 m) == 5 m`.
+
+```nbt
+fn round_in<D: Dim>(base: D, value: D) -> D
 ```
 
 ### `floor` (Floor function)
-Returns the largest integer less than or equal to \\( x \\).
+Returns the largest integer less than or equal to \\( x \\). See also: `floor_in`.
 More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.floor).
 
 ```nbt
-fn floor<T: Dim>(x: T) -> T
+fn floor(x: Scalar) -> Scalar
+```
+
+### `floor_in` (Floor function)
+Returns the largest integer multiple of `base` less than or equal to `value`. For example: `floor_in(m, 5.7 m) == 5 m`.
+
+```nbt
+fn floor_in<D: Dim>(base: D, value: D) -> D
 ```
 
 ### `ceil` (Ceil function)
-Returns the smallest integer greater than or equal to \\( x \\).
+Returns the smallest integer greater than or equal to \\( x \\). See also: `ceil_in`.
 More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.ceil).
 
 ```nbt
-fn ceil<T: Dim>(x: T) -> T
+fn ceil(x: Scalar) -> Scalar
+```
+
+### `ceil_in` (Ceil function)
+Returns the smallest integer multuple of `base` greater than or equal to `value`. For example: `ceil_in(m, 5.3 m) == 6 m`.
+
+```nbt
+fn ceil_in<D: Dim>(base: D, value: D) -> D
 ```
 
 ### `trunc` (Truncation)
-Returns the integer part of \\( x \\). Non-integer numbers are always truncated towards zero.
+Returns the integer part of \\( x \\). Non-integer numbers are always truncated towards zero. See also: `trunc_in`.
 More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.trunc).
 
 ```nbt
-fn trunc<T: Dim>(x: T) -> T
+fn trunc(x: Scalar) -> Scalar
+```
+
+### `trunc_in` (Truncation)
+Truncates to an integer multiple of `base` (towards zero). For example: `trunc_in(m, -5.7 m) == -5 m`.
+
+```nbt
+fn trunc_in<D: Dim>(base: D, value: D) -> D
 ```
 
 ### `mod` (Modulo)

+ 1 - 1
examples/paper_size.nbt

@@ -16,7 +16,7 @@ fn paper_size_A(n: Scalar) -> PaperSize =
       }
     else
       PaperSize {
-        width: floor(paper_size_A(n - 1).height / 2),
+        width: floor_in(mm, paper_size_A(n - 1).height / 2),
         height: paper_size_A(n - 1).width,
       }
 

+ 1 - 1
examples/population_growth.nbt

@@ -4,7 +4,7 @@ let initial_population = 50_000 people
 let growth_rate = 2% per year
 
 fn predict_population(t) =
-    initial_population × e^(growth_rate·t) |> round
+    initial_population × e^(growth_rate·t) |> round_in(people)
 
 print("Population in  20 years: {predict_population(20 years)}")
 print("Population in 100 years: {predict_population(1 century)}")

+ 46 - 0
examples/tests/core.nbt

@@ -23,3 +23,49 @@ assert_eq(value_of(1.2345 m),     1.2345)
 
 assert_eq(value_of(1 m^2/s),      1)
 assert_eq(value_of(1.2345 m^2/s), 1.2345)
+
+# round, round_in
+
+assert_eq(round(1.234), 1)
+
+assert_eq(1.234 m |> round_in(m), 1 m)
+assert_eq(1.234 m |> round_in(cm), 123 cm)
+assert_eq(1.234 m |> round_in(mm), 1234 mm)
+
+assert_eq(1.234 m |> round_in(10 m), 0)
+assert_eq(1.234 m |> round_in(1 m), 1 m)
+assert_eq(1.234 m |> round_in(0.1 m), 1.2 m, 1e-9 m)
+assert_eq(1.234 m |> round_in(0.01 m), 1.23 m, 1e-9 m)
+
+assert_eq(1234 |> round_in(1000), 1000)
+assert_eq(1234 |> round_in(100), 1200)
+assert_eq(1234 |> round_in(10), 1230)
+assert_eq(1234 |> round_in(1), 1234)
+assert_eq(1234 |> round_in(0.1), 1234)
+
+# floor, floor_in
+
+assert_eq(floor(1.2), 1)
+assert_eq(floor(1.8), 1)
+assert_eq(floor(-1.2), -2)
+
+assert_eq(1.8 m |> floor_in(m), 1 m)
+assert_eq(1.8 m |> floor_in(cm), 180 cm)
+
+# ceil, ceil_in
+
+assert_eq(ceil(1.2), 2)
+assert_eq(ceil(-1.2), -1)
+
+assert_eq(1.2 m |> ceil_in(m), 2 m)
+assert_eq(1.2 m |> ceil_in(cm), 120 cm)
+
+# trunc, trunc_in
+
+assert_eq(trunc(1.2), 1)
+assert_eq(trunc(1.8), 1)
+assert_eq(trunc(-1.2), -1)
+assert_eq(trunc(-1.8), -1)
+
+assert_eq(1.8 m |> trunc_in(m), 1 m)
+assert_eq(1.8 m |> trunc_in(cm), 180 cm)

+ 26 - 27
examples/xkcd_2585.nbt

@@ -2,32 +2,31 @@
 #
 # https://xkcd.com/2585/
 
-17 mph
+let speed = 17 mph |>
+  round_in(meters/sec) |>
+  round_in(knots) |>
+  round_in(fathoms/sec) |>
+  round_in(furlongs/min) |>
+  round_in(fathoms/sec) |>
+  round_in(kph) |>
+  round_in(knots) |>
+  round_in(kph) |>
+  round_in(furlongs/hour) |>
+  round_in(mi/h) |>
+  round_in(m/s) |>
+  round_in(furlongs/min) |>
+  round_in(yards/sec) |>
+  round_in(fathoms/sec) |>
+  round_in(m/s) |>
+  round_in(mph) |>
+  round_in(furlongs/min) |>
+  round_in(knots) |>
+  round_in(yards/sec) |>
+  round_in(fathoms/sec) |>
+  round_in(knots) |>
+  round_in(furlongs/min) |>
+  round_in(mph)
 
-ans -> meters/sec    -> round
-ans -> knots         -> round
-ans -> fathoms/sec   -> round
-ans -> furlongs/min  -> round
-ans -> fathoms/sec   -> round
-ans -> kph           -> round
-ans -> knots         -> round
-ans -> kph           -> round
-ans -> furlongs/hour -> round
-ans -> mph           -> round
-ans -> m/s           -> round
-ans -> furlongs/min  -> round
-ans -> yards/sec     -> round
-ans -> fathoms/sec   -> round
-ans -> m/s           -> round
-ans -> mph           -> round
-ans -> furlongs/min  -> round
-ans -> knots         -> round
-ans -> yards/sec     -> round
-ans -> fathoms/sec   -> round
-ans -> knots         -> round
-ans -> furlongs/min  -> round
-ans -> mph           -> round
-
-print("I can ride my bike at {ans}.")
+print("I can ride my bike at {speed}.")
 print("If you round.")
-assert_eq(ans, 45 mph)
+assert_eq(speed, 45 mph)

+ 2 - 2
examples/xkcd_2812.nbt

@@ -20,7 +20,7 @@ print("Option A: On the roof, south facing")
 
 let savings_a = savings(4 kWh/m²/day)
 
-print(savings_a |> round)
+print(savings_a |> round_in($/year))
 assert_eq(savings_a, 58 $/year, 1 $/year)
 
 print()
@@ -33,5 +33,5 @@ let sun_area: Area = 6.09e12 km^2            # [2]
 
 let savings_b = savings(sun_luminosity / sun_area)
 
-print(savings_b |> round)
+print(savings_b |> round_in($/year))
 assert_eq(savings_b, 22 million $/year, 1 million $/year)

+ 26 - 8
numbat/modules/core/functions.nbt

@@ -1,3 +1,5 @@
+use core::scalar
+
 @name("Identity function")
 @description("Return the input value.")
 fn id<A>(x: A) -> A = x
@@ -22,24 +24,40 @@ fn cbrt<D: Dim>(x: D^3) -> D = x^(1/3)
 fn sqr<D: Dim>(x: D) -> D^2 = x^2
 
 @name("Rounding")
-@description("Round to the nearest integer. If the value is half-way between two integers, round away from $0$.")
+@description("Round to the nearest integer. If the value is half-way between two integers, round away from $0$. See also: `round_in`.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.round")
-fn round<T: Dim>(x: T) -> T
+fn round(x: Scalar) -> Scalar
+
+@name("Rounding")
+@description("Round to the nearest multiple of `base`. For example: `round_in(m, 5.3 m) == 5 m`.")
+fn round_in<D: Dim>(base: D, value: D) -> D = round(value / base) × base
 
 @name("Floor function")
-@description("Returns the largest integer less than or equal to $x$.")
+@description("Returns the largest integer less than or equal to $x$. See also: `floor_in`.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.floor")
-fn floor<T: Dim>(x: T) -> T
+fn floor(x: Scalar) -> Scalar
+
+@name("Floor function")
+@description("Returns the largest integer multiple of `base` less than or equal to `value`. For example: `floor_in(m, 5.7 m) == 5 m`.")
+fn floor_in<D: Dim>(base: D, value: D) -> D = floor(value / base) × base
 
 @name("Ceil function")
-@description("Returns the smallest integer greater than or equal to $x$.")
+@description("Returns the smallest integer greater than or equal to $x$. See also: `ceil_in`.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.ceil")
-fn ceil<T: Dim>(x: T) -> T
+fn ceil(x: Scalar) -> Scalar
+
+@name("Ceil function")
+@description("Returns the smallest integer multuple of `base` greater than or equal to `value`. For example: `ceil_in(m, 5.3 m) == 6 m`.")
+fn ceil_in<D: Dim>(base: D, value: D) -> D = ceil(value / base) × base
 
 @name("Truncation")
-@description("Returns the integer part of $x$. Non-integer numbers are always truncated towards zero.")
+@description("Returns the integer part of $x$. Non-integer numbers are always truncated towards zero. See also: `trunc_in`.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.trunc")
-fn trunc<T: Dim>(x: T) -> T
+fn trunc(x: Scalar) -> Scalar
+
+@name("Truncation")
+@description("Truncates to an integer multiple of `base` (towards zero). For example: `trunc_in(m, -5.7 m) == -5 m`.")
+fn trunc_in<D: Dim>(base: D, value: D) -> D = trunc(value / base) × base
 
 @name("Modulo")
 @description("Calculates the least nonnegative remainder of $a (\\mod b)$.")

+ 1 - 1
numbat/modules/datetime/human.nbt

@@ -3,7 +3,7 @@ use core::strings
 use units::si
 use datetime::functions
 
-fn _human_num_days(time: Time) -> Scalar = floor(time / day)
+fn _human_num_days(time: Time) -> Scalar = floor(time / days)
 
 fn _human_join(a: String, b: String) -> String =
   if str_slice(a, 0, 2) == "0 " then b else if str_slice(b, 0, 2) == "0 " then a else "{a} + {b}"

+ 5 - 17
numbat/src/ffi/math.rs

@@ -15,18 +15,6 @@ pub fn mod_(mut args: Args) -> Result<Value> {
     return_quantity!(x_value.rem_euclid(y_value), x.unit().clone())
 }
 
-// A simple math function with signature 'Dim D. Fn[(D) -> D]', which only operates on the value of the quantity
-macro_rules! simple_polymorphic_math_function {
-    ($name:ident, $op:ident) => {
-        pub fn $name(mut args: Args) -> Result<Value> {
-            let arg = quantity_arg!(args);
-
-            let value = arg.unsafe_value().to_f64();
-            return_quantity!(value.$op(), arg.unit().clone())
-        }
-    };
-}
-
 // Similar, but with signature 'Fn[(Scalar) -> Scalar]'
 macro_rules! simple_scalar_math_function {
     ($name:ident, $op:ident) => {
@@ -37,11 +25,11 @@ macro_rules! simple_scalar_math_function {
     };
 }
 
-simple_polymorphic_math_function!(abs, abs);
-simple_polymorphic_math_function!(round, round);
-simple_polymorphic_math_function!(floor, floor);
-simple_polymorphic_math_function!(ceil, ceil);
-simple_polymorphic_math_function!(trunc, trunc);
+simple_scalar_math_function!(abs, abs);
+simple_scalar_math_function!(round, round);
+simple_scalar_math_function!(floor, floor);
+simple_scalar_math_function!(ceil, ceil);
+simple_scalar_math_function!(trunc, trunc);
 
 simple_scalar_math_function!(sin, sin);
 simple_scalar_math_function!(cos, cos);