Math
The arithmetic operators stop at the four operations. Square roots, powers, logarithms, angles and rounding live in the Math package, as functions on float64 — with float32 versions of each, chosen by the type of the argument.
Two things are true of all of them, and both matter more than any single function. Every one has a domain — the inputs it has an answer for — and outside it the answer is a special value, not an error. And every answer is a float: the nearest float to the true answer, not the answer itself.
The program imports what it uses, and lists Math as a dependency:
import Math::{ Abs, Cbrt, Ceil, Cos, DegToRad, Exp, Floor, Log, Log10, Log2, Pi, Pow, Round, Sin,
Sqrt, Trunc };
Roots and powers
PrintLine("Sqrt(2) {}", Sqrt(2.0));
PrintLine("Cbrt(-8) {}", Cbrt(-8.0));
PrintLine("Pow(2, 10) {}", Pow(2.0, 10.0));
PrintLine("Sqrt(-1) {}", Sqrt(-1.0));
Sqrt wants a number that is not negative; Cbrt, the cube root, takes any sign, so Cbrt(-8.0) is -2.0. Pow(x, y) is x to the power y. And Sqrt(-1.0) is outside the square root's domain: the answer is NaN, as in Float special, and the program carries on.
Notice the arguments are written 2.0, not 2. The functions take floats, and an integer is not converted on its own.
Logarithms
PrintLine("Log(Exp(1)) {}", Log(Exp(1.0)));
PrintLine("Log2(1024) {}", Log2(1024.0));
PrintLine("Log10(0.001) {}", Log10(0.001));
PrintLine("Log(0) {}", Log(0.0));
Log is the natural logarithm, base e, and the inverse of Exp; the others say their base in their name. Log2(1024.0) is 10.0 because 2¹⁰ = 1024. Logarithms want a number greater than zero, and Log(0.0) is -Inf.
| Function | Wants | Outside the domain |
|---|---|---|
Sqrt | x ≥ 0 | NaN |
Cbrt | any x | — |
Log, Log2, Log10 | x > 0 | -Inf at 0, NaN below |
Check the input first when it might fall outside — a NaN produced deep inside a calculation is much harder to trace than an if before it.
Angles
Trigonometry works in radians, where a half turn is Pi. DegToRad converts from degrees:
PrintLine("Sin(30 deg) {}", Sin(DegToRad(30.0)));
PrintLine("Cos(60 deg) {}", Cos(DegToRad(60.0)));
PrintLine("Sin(Pi) {}", Sin(Pi));
On paper, sin 30° and cos 60° are both exactly ½, and sin π is exactly 0. The program prints 0.49999999999999994, 0.5000000000000001 and 1.2246467991473532e-16. Nothing is wrong: Pi is the nearest float64 to π, not π itself, and every step rounds to the nearest float.
So a float result is compared within a tolerance, never with ==:
let tolerance = 1e-9;
PrintLine("Sin(Pi) is 0? {}", Abs(Sin(Pi)) < tolerance);
Rounding, in four senses
"Round" can mean four different things. They agree on most positive numbers and part ways on negative ones — which is where a program usually finds out it chose the wrong one:
let value = -2.5;
PrintLine("Floor(-2.5) {} towards minus infinity", Floor(value));
PrintLine("Ceil(-2.5) {} towards plus infinity", Ceil(value));
PrintLine("Trunc(-2.5) {} towards zero", Trunc(value));
PrintLine("Round(-2.5) {} to nearest, halves away from zero", Round(value));
| Function | Rounds | 2.5 | −2.5 |
|---|---|---|---|
Floor | down, towards minus infinity | 2.0 | −3.0 |
Ceil | up, towards plus infinity | 3.0 | −2.0 |
Trunc | towards zero | 2.0 | −2.0 |
Round | to nearest, halves away from zero | 3.0 | −3.0 |
All four return a float. Trunc is what as does when it converts a float to an integer; to round to the nearest whole number, Round first and convert after.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// The arithmetic operators stop at the four operations. Roots, powers, logarithms, angles and
// rounding live in the `Math` package, as functions on `float64` (with `float32` versions too).
//
// Every one of them has a domain, the inputs it has an answer for, and outside it the answer is
// one of the special values from FloatSpecial rather than an error: `Sqrt(-1)` is NaN, `Log(0)` is
// minus infinity. Check the input first when it might fall outside.
//
// And every answer is a float, so it is the nearest float to the true answer, not the answer
// itself. `Sin(Pi)` is not 0, because `Pi` is not exactly pi. Compare results with a tolerance,
// never with `==`.
import Io::PrintLine;
import Math::{ Abs, Cbrt, Ceil, Cos, DegToRad, Exp, Floor, Log, Log10, Log2, Pi, Pow, Round, Sin,
Sqrt, Trunc };
func Main() -> int {
// Roots and powers. Sqrt wants x >= 0; Cbrt takes any sign.
PrintLine("Sqrt(2) {}", Sqrt(2.0));
PrintLine("Cbrt(-8) {}", Cbrt(-8.0));
PrintLine("Pow(2, 10) {}", Pow(2.0, 10.0));
PrintLine("Sqrt(-1) {}", Sqrt(-1.0));
PrintLine("");
// Logarithms want x > 0. `Log` is the natural one; the others say their base.
PrintLine("Log(Exp(1)) {}", Log(Exp(1.0)));
PrintLine("Log2(1024) {}", Log2(1024.0));
PrintLine("Log10(0.001) {}", Log10(0.001));
PrintLine("Log(0) {}", Log(0.0));
PrintLine("");
// Trigonometry works in radians, where a half turn is Pi. DegToRad converts from degrees.
PrintLine("Sin(30 deg) {}", Sin(DegToRad(30.0)));
PrintLine("Cos(60 deg) {}", Cos(DegToRad(60.0)));
PrintLine("Sin(Pi) {}", Sin(Pi));
// So a float result is compared within a tolerance.
let tolerance = 1e-9;
PrintLine("Sin(Pi) is 0? {}", Abs(Sin(Pi)) < tolerance);
PrintLine("");
// Rounding, in its four senses. They differ on negative numbers, which is where a program
// usually finds out it chose the wrong one.
let value = -2.5;
PrintLine("Floor(-2.5) {} towards minus infinity", Floor(value));
PrintLine("Ceil(-2.5) {} towards plus infinity", Ceil(value));
PrintLine("Trunc(-2.5) {} towards zero", Trunc(value));
PrintLine("Round(-2.5) {} to nearest, halves away from zero", Round(value));
return 0;
}
Besides Io, its Rux.toml lists Math under [Dependencies].
Run it
cd Examples/Numbers/Math
rux run
Sqrt(2) 1.4142135623730951
Cbrt(-8) -2.0
Pow(2, 10) 1024.0
Sqrt(-1) NaN
Log(Exp(1)) 1.0
Log2(1024) 10.0
Log10(0.001) -3.0
Log(0) -Inf
Sin(30 deg) 0.49999999999999994
Cos(60 deg) 0.5000000000000001
Sin(Pi) 1.2246467991473532e-16
Sin(Pi) is 0? true
Floor(-2.5) -3.0 towards minus infinity
Ceil(-2.5) -2.0 towards plus infinity
Trunc(-2.5) -2.0 towards zero
Round(-2.5) -3.0 to nearest, halves away from zero
Common mistakes
Sqrt(2) fails with error: no matching overload for 'Sqrt' with argument types (int), and the notes list the two candidates, Sqrt(x: float32) and Sqrt(x: float64). Write 2.0, or convert a variable with as float64.Sin(30.0) is the sine of 30 radians, about -0.988, not ½. Convert first: Sin(DegToRad(30.0)).==.Cos(DegToRad(60.0)) == 0.5 is false, because the result is 0.5000000000000001. Compare the difference with a tolerance: Abs(result - 0.5) < 1e-9.Try it yourself
- Import
Hypotand work out the hypotenuse of a 3–4–5 triangle. - Convert
Piback to degrees withRadToDeg. - Round −2.4 and −2.6 to the nearest whole number and convert them to
int32. Then do the same withasalone and compare. - Call
Sqrt(2.0f32). How many digits does thefloat32version print?
Learn more
16.10 Endian
Store and load integers as bytes in an explicit order, big endian or little endian, and see what reading them in the wrong order does.
Overview
Containers from the Collections package: vectors that grow, arrays sized at run time, double-ended queues, and hash and tree maps and sets — and how to choose between them.