Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Numbers

There is one numeric type, number, and it is a 64-bit IEEE-754 double. Integers and fractions are the same type, and the distinction you care about is whether a particular value happens to be integral.

Writing a Number

There are five ways to write a numeric literal, and they all produce the same type:

echo 42
echo 3.5
echo 0b1011
echo 0c17
echo 0xff
echo 6.02e23
echo 1.5e-3
42
3.5
11
15
255
602000000000000000000000
0.0015

0b is binary, 0c is octal, and 0x is hexadecimal. The letters in a hexadecimal literal may be either case, so 0xff and 0xFF are the same number. Note that octal is 0c, not the 0o some other languages use.

The exponent form takes e or E, and the exponent may be negative. 1e3 is 1000; the mantissa needs no decimal point. Note that the exponent is a way of writing the literal, not a property the value keeps: 6.02e23 prints as its full decimal expansion, because that is the number it is.

Digit Separators

An underscore between digits is ignored, which makes long numbers readable:

echo 1_000_000
echo 1_0.5_5
1000000
10.55

Separators work in decimal literals only. 0xdead_beef and 0b1010_1010 are syntax errors, not clever formatting.

Two Forms That Do Not Exist

A literal needs a digit on both sides of its decimal point. Neither of these parses:

echo .5
echo 5.

Write 0.5 and 5.0. The second case matters more than it looks, because 5. is also how a method call on a literal starts — which is the subject of the next-but-one section.

Methods, Not Functions

Everything you would reach into a math library for is a method on the number:

echo 2.sqrt()
echo 8.log2()
echo 100.log10()
echo 100.log()
echo 1.exp()
echo 2.cbrt()
1.4142135623730951
3
2
4.605170185988092
2.718281828459045
1.2599210498948732

log() is the natural logarithm. There is also log1p() and expm1() for the precision-sensitive forms near zero.

The full trigonometric set is there: sin, cos, tan, asin, acos, atan, atan2, and the hyperbolic sinh, cosh, tanh, asinh, acosh, atanh.

echo 1.atan2(1)
0.7853981633974483

Calling a Method on a Literal

A numeric literal takes a method directly. No parentheses, no temporary variable, in every base and with a decimal point or without:

echo 2.sqrt()
echo 3.7.round()
echo 255.hex()
echo 0xff.bin()
echo 1e3.int()
echo 2n.bits()
1.4142135623730951
4
ff
11111111
1000
2

There is exactly one case where you need parentheses, and it is a negative literal. A method call binds tighter than unary minus, so the minus applies to the result rather than to the number:

echo -3.abs()
echo (-3).abs()
-3
3

-3.abs() is -(3.abs()), which is -3. When the receiver is negative, parenthesise it — or put it in a variable, where the question disappears:

var n = -3

echo n.abs()
3

The same rule covers any expression you want to call a method on: wrap it, because the call would otherwise bind to the last term alone.

echo (1 / 0).is_inf()
echo (2 ** 10).hex()
true
400

Rounding

echo 2.5.ceil()
echo 2.5.floor()
echo 2.5.trunc()
echo 2.5.int()
echo (-2.5).int()
3
2
2
2
-2

trunc() and int() both drop the fractional part, rounding towards zero.

round() rounds half away from zero:

echo 2.5.round()
echo 3.5.round()
echo (-2.5).round()
3
4
-3

fixed() rounds to a number of decimal places and gives you a number back:

echo 2.567.fixed(2)
2.57

fraction() gives the digits after the decimal point as a whole number:

echo 2.567.fraction()
567

Sign, Magnitude and Comparison

echo (-5).sign()
echo (-3).abs()
echo 5.max(9)
echo 5.min(9)
-1
3
9
5

sign() is -1, 0 or 1.

Bases and Characters

echo 255.bin()
echo 255.oct()
echo 255.hex()
echo 65.chr()
11111111
377
ff
A

chr() turns a code point into a one-character string; 'A'.ord() goes back the other way.

The Special Values

echo (0 / 0).is_nan()
echo (1 / 0).is_inf()
echo 1.is_finite()
true
true
true

NaN is falsy, like zero, so var x = a / b or fallback replaces the NaN from a 0 / 0 with the fallback. Test with is_nan() when you need to tell the two apart.

NaN is also not equal to itself, as the standard requires, so x == x is a valid way to spot one.

Other Methods

factorial() for small integers, to_string(), to_bool() and to_bigint() for conversion.

echo 5.factorial()
echo 17.to_bigint()
120
17n

The math Module

Constants live in math, because a constant is not a method on anything:

import math

echo math.PI
echo math.E
echo math.Infinity
echo math.NaN
3.141592653589793
2.718281828459045
inf
NaN

It also carries LOG_2, LOG_10, LOG_2_E, LOG_10_E, ROOT_2, ROOT_3 and ROOT_HALF.

Bigints

When 2^53 is not enough, use a bigint. Write one with an n suffix:

var a = 2n ** 100n
echo a
echo a.bits()
1267650600228229401496703205376n
101

Bigints are arbitrary precision. They never overflow and never lose a digit.

They also never mix with numbers implicitly:

catch {
  echo 5n + 3
} as e {
  echo e.message
}
operator '+' not defined for call signature (bigint, number)

Convert explicitly, in whichever direction you need:

echo 5.to_bigint() * 2n
echo (2n ** 100n).to_number()
10n
1267650600228229400000000000000

Going to number is lossy once you are past 2^53, which is the whole reason bigints exist. Going the other way is exact.

The same separation holds in type annotations. bigint is a type name alongside number and int, and it accepts nothing else:

def scale(n: bigint, factor: number) {
  return n * factor.to_bigint()
}

echo scale(2n, 50)

catch {
  scale(2, 50)
} as e {
  echo e.message
}
100n
scale() expects parameter 'n' (argument 1) to be a bigint, got number

The number-theory methods are the reason bigints are worth having:

echo 100n.gcd(75n)
echo 100n.lcm(75n)
echo 2n.modpow(10n, 1000n)
echo 3n.modinv(11n)
echo 144n.sqrt()
25n
300n
24n
4n
12n

modpow(exponent, modulus) is the operation every public-key algorithm is built on, and it is computed without ever materialising the full power. modinv gives the modular multiplicative inverse.

There is also nth_root(), cbrt(), bit(), set_bit(), trailing_zeros(), is_zero(), is_even(), is_odd(), abs(), sign(), max(), min(), pow(), and bin()/oct()/hex().

A bigint is the right choice when exactness past 2^53 is the point: cryptography, currency in minor units, factorials, identifiers that must survive a round trip. For everything else — measurements, coordinates, counters, ratios — a number is the type you want.