Skip to content

Standard Library

Math ​

std::math is thin, and it's thin on purpose: most of what is in it is one machine instruction rather than a function call.

echo
echo std::math::sqrt(9.0);      // 3.000000
echo std::math::PI;             // 3.141593

So reach for these freely. sqrt in a loop costs what the hardware costs, not what a call costs.

The constants have no $ ​

echo
echo std::math::PI;         // 3.141593
echo std::math::E;          // 2.718282
echo std::math::SQRT_2;     // 1.414214

They are constants, not variables, which in Echo is a real distinction rather than a naming convention. A constant has no storage at all: its expression is copied into each place the name is used. That's what lets one live at file scope, where a variable cannot.

The circle constants, E, and the logs and roots are float64, because a float literal is double precision unless it ends in f. Where you want single precision, use the _F32 twin:

echo
echo std::math::cos(std::math::PI);         // -1.000000
echo std::math::cos(std::math::PI_F32);     // -1.000000, but through the float32 overload

See Constants for what a $-less declaration actually does, including the consequence that a constant whose expression calls a function calls it once per use site.

The integer extrema are the other group. Reach for them when a conversion would wrap, not when you want to write 9223372036854775807 from memory:

echo
echo std::math::MAX_INT64;       // 9223372036854775807
echo std::math::MIN_INT64;       // -9223372036854775808
echo std::math::MAX_UINT64;      // 18446744073709551615
echo usize::max() == std::math::MAX_USIZE;   // 1

usize::max() is the same number as MAX_USIZE, spelled on the type. Use whichever you are already looking at. MIN_INT64 is -MAX_INT64 - 1, so the magnitude never has to be a literal int64 cannot hold. isize / usize twins sit beside them; today they match int64 / uint64 because the pointer width is 8.

Every function comes in two, and the argument picks ​

There is a float overload and a float64 overload of everything, and they are a plain overload set:

echo
echo std::math::sqrt(2.0);      // 1.414214, the float64 one
echo std::math::sqrt(2.0f);     // 1.414214, the float32 one

They are separate functions all the way down, so single-precision math never round-trips through a double. The f suffix is the only literal suffix Echo has, and this is the place you'll use it most.

Trigonometry, exponentials, logarithms ​

echo
echo std::math::sin(0.0);           // 0.000000
echo std::math::atan2(1.0, 1.0);    // 0.785398
echo std::math::pow(2.0, 10.0);     // 1024.000000
echo std::math::exp2(10.0);         // 1024.000000
echo std::math::log2(1024.0);       // 10.000000

atan2 takes y first, matching C. The full list is in the table at the bottom of this page: sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, sqrt, pow, exp, exp2, exp10, log, log2, log10.

What is not there yet: fmod, hypot, cbrt, signbit, isnan. They are on the list.

Rounding, and the two functions that disagree about 2.5 ​

echo
echo std::math::floor(2.7);         // 2.000000
echo std::math::ceil(2.1);          // 3.000000
echo std::math::trunc(-2.7);        // -2.000000
echo std::math::round(2.5);         // 3.000000
echo std::math::roundeven(2.5);     // 2.000000

round rounds half away from zero. roundeven is banker's rounding, which rounds an exact half to the nearest even number, and on 2.5 that is the difference between 3 and 2. If you're summing a lot of rounded values and care about drift, roundeven is the one you want. There is also rint, which rounds according to the current rounding mode.

min and max spell out their integer overloads ​

Beside the two float overloads, min and max each carry four integer ones: int32, int64, uint32, uint64.

echo
echo std::math::min(3, 9);          // 3
echo std::math::max(3.0, 9.0);      // 9.000000

There is no generic min, because comparing signed and unsigned integers are two different operations and a single body would have nothing to choose between them with. What you see of that is a mixed-signedness call having no single best candidate:

echo
int32 $a = 3;
uint32 $b = 9;

echo std::math::min($a, $b);
// error: The call to 'min' is ambiguous. These overloads all match equally well:
//          std::math::min(int32, int32)
//          std::math::min(uint32, uint32)

Cast the operands to one type first. That's the honest answer: a min that silently picked one would be choosing which of your two values gets reinterpreted, and it would be wrong about half the time.

There are no int8 or int16 overloads, which means a min over two uint8s widens. That costs nothing in practice and it's still a gap.

abs takes any number you give it ​

echo
echo std::math::abs(-3);        // 3
echo std::math::abs(-3.0);      // 3.000000
echo std::math::abs(-3.0f);     // 3.000000

Unlike min, abs does have a generic form, sitting in the same overload set as the two float ones. The ordinary rules sort it out: a float argument matches a float overload exactly, and an integer one instantiates the generic. Concrete beats generic, so a float never goes near the generic body. See Generics.

Here's the wrinkle. The generic works by comparing against zero, which over an unsigned type is never true, so abs on a uint32 compiles and hands you the value back unchanged. The answer is right, since the absolute value of an unsigned number is itself, but you have written a call that cannot do anything.

clamp is float only ​

echo
echo std::math::clamp(11.0, 0.0, 5.0);      // 5.000000

There are exactly two overloads, float and float64, both min(max($value, $min), $max). Clamping integers has no candidate, and because an integer literal converts to both float widths equally well, what you get is an ambiguity rather than a missing-overload message:

echo
echo std::math::clamp(11, 0, 5);
// error: The call to 'clamp' is ambiguous. These overloads all match equally well:
//          std::math::clamp(float32, float32, float32)
//          std::math::clamp(float64, float64, float64)

min and max have the integer overloads, so std::math::min(std::math::max(11, 0), 5) works today. The missing integer clamp is a gap in the library rather than in the language, and it is on the list.

A constant is not a compile-time number ​

This is the one that surprises people, and it's not a std::math rule at all:

echo
const if (std::math::PI > 3.0) {
    echo 1;
}
// error: a floating-point value is not something the compiler folds - its arithmetic belongs to the
//        target rather than to the tree.

const if folds integer and boolean expressions. Floating-point arithmetic belongs to the target, not to the tree, so the compiler refuses to answer rather than answering with the host's idea of the result. Use an ordinary if. It is one branch, and any optimizer will fold it for the target that actually matters.

The whole surface ​

GroupFunctionsNotes
trigonometrysin cos tan asin acos atan atan2 sinh cosh tanhatan2 takes y first
roots and powerssqrt pow
exponentialsexp exp2 exp10
logarithmslog log2 log10
roundingfloor ceil trunc round roundeven rintround and roundeven disagree on an exact half
signabs copysigncopysign($magnitude, $sign), magnitude first
selectionmin maxno generic form, so cast mixed signedness first
fusedfma($a, $b, $c)$a * $b + $c with a single rounding
clampingclamp($value, $min, $max)float widths only

Every one of these has a float and a float64 overload. min and max add int32, int64, uint32 and uint64. abs adds a generic form that covers the integer types.

ConstantValueTwin
PI3.141592653589793PI_F32
TAU6.283185307179586TAU_F32
HALF_PI1.5707963267948966HALF_PI_F32
E2.718281828459045E_F32
LN_20.6931471805599453LN_2_F32
LN_102.302585092994046LN_10_F32
SQRT_21.4142135623730951SQRT_2_F32
MAX_INT8 .. MAX_INT64width maximumMIN_INT8 .. MIN_INT64
MAX_UINT8 .. MAX_UINT64width maximum
MAX_ISIZE / MAX_USIZEpointer-width maximumMIN_ISIZE

Next ​

  • Constants for what a $-less declaration is and where one may live.
  • Types for float against float64, and the f suffix.
  • Expressions for what happens when you mix widths in one expression.

Echo is a work in progress. Nothing here is a promise of stability.