std/number
Every numeric type of the language, the traits their arithmetic and bit operations go through, Real, and ByteSize.
The generated reference of std/number has every public construct with its signature, its doc comment and its examples.
std/number declares every numeric type, the traits their operators go through (Numeric, Signed, Integer, Bits, Real),
the two error types a numeric conversion fails with, and ByteSize, a number of bytes in its units. Every name below
is already in scope through the prelude.
Import#
use Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64 from "std/number"
use Float32, Float64, Numeric, Signed, Bits from "std/number"const value: Int8 = 100
print value.absolute()Declarations#
Numeric, Signed, Bits, Real#
trait Numeric with Add, Subtract, Multiply, Divide, Remainder, Equals, Compare, Show, TryFrom<String, NumberParseError> {
static zero: Self
static one: Self
}
trait Signed with Numeric, Negate {
fn absolute(): Self
}
trait Bits {
fn bitwiseAnd(other: Self): Self
fn bitwiseOr(other: Self): Self
fn bitwiseExclusiveOr(other: Self): Self
fn bitwiseNot(): Self
fn shiftedLeft(by: Int64): Self
fn shiftedRight(by: Int64): Self
}Numeric is the arithmetic and comparison every number has; Signed adds absolute() and the unary -. The bit
operators &, |, ^, ~, << and >> are the members of Bits - & binds like *, | and ^ like +, a shift
between * and ** - and the members stay the named form, so
masking and shifting are named methods on Bits instead: value.bitwiseAnd(0xFF),
seed.bitwiseExclusiveOr(byte).shiftedLeft(by: 5). The shift of a signed type is arithmetic (it keeps the sign), the
shift of an unsigned type is logical, and a shift by a negative amount or by the width of the type or more panics, like
every other operation that leaves its range.
trait Real with Signed, Power, Power<Int64> {
static pi: Self
static tau: Self
static e: Self
static epsilon: Self
fn squareRoot(): Self
fn sine(): Self
fn cosine(): Self
fn tangent(): Self
fn arcSine(): Self
fn arcCosine(): Self
fn arcTangent(): Self
fn arcTangentDivided(by: Self): Self
fn exponential(): Self
fn naturalLogarithm(): Self
fn logarithm(base: Self): Self
fn floor(): Self
fn ceiling(): Self
fn round(): Self
fn halved(): Self
fn doubled(): Self
fn radiansOfDegrees(): Self
fn degreesOfRadians(): Self
}Real is Signed plus everything that needs a root or an angle, and it is the bound a library that is generic over its
scalar separates its two halves along: dot and lengthSquared are arithmetic and need only Numeric, while length,
normalized and every rotation need a Real. Angles are in radians throughout, and arcTangentDivided(by:) is the
two-argument arc tangent that uses the sign of both to pick the quadrant - it takes a label because the order of the two
is the one thing everybody gets wrong.
Two implementors. Float64 is the fast one and answers whatever the platform's mathematics library answers, which is not
the same bits on every machine. Fixed (std/linear) is the deterministic one: every operation on it, the square root
and the trigonometry included, is integer arithmetic, so a lockstep simulation and a replay run on it.
Real is also the scalar that has a power by a value of its own kind and by an Int (x ** 0.5, x ** 3), an
exponential(), a naturalLogarithm() and a logarithm(base:) - so a body that is generic over its scalar writes
(Scalar.e ** exponent) or value.logarithm(base: two) exactly as a Float64 body does, and a Fixed answers the
same bits on every machine.
pi, tau, e and epsilon are required constants: every implementor carries them under those names (Float64.pi,
Fixed.tau, Float.e), and a body that is generic over its scalar reads them through its parameter - Scalar.pi - where it
could not write the digits. epsilon is the gap between one and the next value the scalar holds: the machine epsilon
of a Float64, one part of a Fixed. The one such a body needs is Scalar.one, which every Numeric has, and
halved and doubled are the two pieces of arithmetic it would otherwise write with a literal.
Integer#
trait Integer with Numeric {
static minimum: Self
static maximum: Self
fn addedChecked(other: Self): Self?
fn subtractedChecked(other: Self): Self?
fn multipliedChecked(other: Self): Self?
fn dividedChecked(other: Self): Self?
fn remainderChecked(other: Self): Self?
}Every integer type, Int8 to UInt64, is an Integer: a Numeric with a range, and the total twins of its
operators. a.addedChecked(b) is a + b where the sum fits the type and None where + would panic, and the same
for -, *, / and % - the division and the remainder answer None for a zero divisor and for the smallest value
of a signed type divided by -1 as well. All five are defaults of the trait, written over the operators with a
comparison in front, so a body that is generic over Value: Integer has them too.
print Int.maximum.multipliedChecked(2)
print(100.subtractedChecked(1) ?? 0)
// prints None
// prints 99NumberParseError, NumberRangeError#
type NumberParseError with Error {
text: String
}
type NumberRangeError with Error {
message: String
}What a conversion of a number fails with: NumberParseError where text is not a number, NumberRangeError where a
value does not fit the target width.
The integer and floating-point types#
native type Int8 with Signed, Hash, Bits, Power<Int64>
extend Int8 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = -128
static maximum: Self = 127
}
extend Int8 with TryFrom<String, NumberParseError>
extend Int8 with TryFrom<Int64, NumberRangeError>
native type Int16 with Signed, Hash, Bits, Power<Int64>
extend Int16 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = -32768
static maximum: Self = 32767
}
extend Int16 with TryFrom<String, NumberParseError>
extend Int16 with TryFrom<Int64, NumberRangeError>
native type Int32 with Signed, Hash, Bits, Power<Int64>
extend Int32 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = -2147483648
static maximum: Self = 2147483647
}
extend Int32 with TryFrom<String, NumberParseError>
extend Int32 with TryFrom<Int64, NumberRangeError>
native type Int64 with Signed, Hash, Bits, Power<Int64>
extend Int64 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = -9223372036854775807 - 1
static maximum: Self = 9223372036854775807
native static fn parseDigits(text: String, radix: Self): Result<Self, NumberParseError>
}
extend Int64 with From<Int8>
extend Int64 with From<Int16>
extend Int64 with From<Int32>
extend Int64 with From<UInt8>
extend Int64 with From<UInt16>
extend Int64 with From<UInt32>
extend Int64 with TryFrom<String, NumberParseError>
extend Int64 with TryFrom<UInt64, NumberRangeError>
extend Int64 with TryFrom<Float64, NumberRangeError>
extend Int64 with TryFrom<Decimal, NumberRangeError>
extend Int64 with From<Char>
extend Int64 {
fn bytes(): ByteSize
fn kilobytes(): ByteSize
fn megabytes(): ByteSize
fn gigabytes(): ByteSize
fn terabytes(): ByteSize
fn kibibytes(): ByteSize
fn mebibytes(): ByteSize
fn gibibytes(): ByteSize
fn tebibytes(): ByteSize
}
native type UInt8 with Numeric, Hash, Bits, Power<Int64>
extend UInt8 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = 0
static maximum: Self = 255
}
extend UInt8 with TryFrom<String, NumberParseError>
extend UInt8 with TryFrom<Int64, NumberRangeError>
native type UInt16 with Numeric, Hash, Bits, Power<Int64>
extend UInt16 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = 0
static maximum: Self = 65535
}
extend UInt16 with TryFrom<String, NumberParseError>
extend UInt16 with TryFrom<Int64, NumberRangeError>
native type UInt32 with Numeric, Hash, Bits, Power<Int64>
extend UInt32 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = 0
static maximum: Self = 4294967295
}
extend UInt32 with TryFrom<String, NumberParseError>
extend UInt32 with TryFrom<Int64, NumberRangeError>
native type UInt64 with Numeric, Hash, Bits, Power<Int64>
extend UInt64 {
static zero: Self = 0
static one: Self = 1
static minimum: Self = 0
static maximum: Self = ~Self.minimum
native fn addedWrapping(other: Self): Self
native fn multipliedWrapping(other: Self): Self
}
extend UInt64 with TryFrom<String, NumberParseError>
extend UInt64 with TryFrom<Int64, NumberRangeError>
extend UInt64 with TryFrom<Decimal, NumberRangeError>
native type Float32 with Signed
extend Float32 {
static zero: Self = 0.0
static one: Self = 1.0
static nan: Self = 0.0 / 0.0
static infinity: Self = 1.0 / 0.0
static negativeInfinity: Self = -1.0 / 0.0
fn isInfinite(): Bool
fn isFinite(): Bool
native fn bits(): UInt32
native static fn ofBits(bits: UInt32): Self
}
extend Float32 with TryFrom<String, NumberParseError>
extend Float32 with TryFrom<Float64, NumberRangeError>
native type Float64 with Signed, Power
extend Float64 {
static zero: Self = 0.0
static one: Self = 1.0
static pi: Self = 3.141592653589793
static tau: Self = 6.283185307179586
static epsilon: Self = 0.0000000000000002220446049250313
static e: Self = 2.718281828459045
static nan: Self = 0.0 / 0.0
static infinity: Self = 1.0 / 0.0
static negativeInfinity: Self = -1.0 / 0.0
native fn squareRoot(): Self
native fn floor(): Self
native fn ceiling(): Self
native fn round(): Self
native fn isNaN(): Bool
fn isInfinite(): Bool
fn isFinite(): Bool
native fn bits(): UInt64
native static fn ofBits(bits: UInt64): Self
fn isCloseTo(other: Self, tolerance: Self = 0.000001): Bool
}
extend Float64 with Real
extend Float64 with Power<Int64>
extend Float64 with From<Int64>
extend Float64 with From<Float32>
extend Float64 with From<Decimal>
extend Float64 with TryFrom<String, NumberParseError>
type Int = Int64
type UInt = UInt64
type Float = Float64Every type carries its width in its name. Int8 through UInt64 are Hash and Integer, and each has a minimum and
a maximum constant of its own type. Int64 is the type of an integer literal and additionally has parseDigits(text, radix:) for
reading digits in another base (Int.parseDigits("ff", radix: 16), no sign, no prefix, _ allowed between digits) and
addedWrapping/multipliedWrapping on UInt64, the only arithmetic in the language that does not panic on overflow -
it exists for hash functions that need the wrap. Float64 is the type of a decimal literal and additionally has pi,
e, squareRoot(), floor(), ceiling(), round(), isNaN() and isCloseTo(other, tolerance:), because == on a
float is IEEE-754 (nan != nan, 0.0 == -0.0) while compare is a total order (nan is above everything, -0.0
compares equal to 0.0) - which is also why Float32 and Float64 are deliberately not Hash, so a float can never
be a Map key and the nan key does not exist. Int, UInt and Float are plain aliases for the default width,
nothing the language treats specially.
Both Float32 and Float64 also carry nan, infinity and negativeInfinity, and isInfinite() and isFinite()
alongside isNaN() (Float64.nan, Float32.infinity.isInfinite()). nan and infinity are named the way pi and
e are, but a constant expression that computes nan - 0.0 / 0.0 written anywhere else - stays a compile error at
that expression (Floating-point numbers, rule 7): Float64.nan and
Float32.nan are the only two declarations the constant evaluator lets fold to it, because that is what the name of
each of them already says. An infinity has no such exception to make - it is an ordinary IEEE-754 value like any
other, which is why 1.0 / 0.0 needs none to be a constant.
Every integer type is raised by an Int: 2 ** 10 is exact, a power that leaves the range of the type panics the way
* does (arithmetic overflow in \**`), and so does a negative exponent, because its result is no whole number. A Float64is raised by aFloat64(C'spow) or by an Int(the samepow`, which rounds once where repeated
squaring would round at every step), and never panics.
A Float32 is IEEE 754 binary32: every operator rounds its result to the nearest Float32, ties to even, so
0.1 + 0.2 of two Float32 is 0.3 where the Float64 sum is 0.30000000000000004. A literal or a text is read as a
Float64 and rounded the same way, and "{value}" writes the shortest text that reads back to the same Float32.
Float32.tryFrom of a Float64 rounds to the nearest Float32 and fails only for a finite value beyond its range.
bits() is the IEEE-754 pattern of a float as the unsigned integer of its width - a UInt64 for a Float64, a
UInt32 for a Float32 - and ofBits(_) is the float of a pattern: 1.0.bits() is 0x3ff0000000000000, and a
Float32 1.0 is 0x3f800000. Both are a memcpy and never a conversion, so every pattern makes the round trip with
all its bits - -0.0, a subnormal, an infinity and a nan with a payload, a signalling one included - in the binary
and in the VM alike. A call of either is never folded into a constant, because what ofBits makes may be a nan.
Float32 is not a Real yet: it has no squareRoot and no angle members. <math.h> has the float functions, and
Real is one extend over them.
print(2 ** 10)
print(2.0 ** 0.5)
print Float.e.naturalLogarithm()
print((8.0).logarithm(base: 2.0))
// prints 1024
// prints 1.4142135623730951
// prints 1.0
// prints 3.0There is no std/math#
std/math held pi, e, power, exponential, naturalLog, logarithm and the trigonometry as free functions
of Float64. It is gone, because every one of them was a second way to write something the number already has: the
trigonometry is Real's (angle.sine(), y.arcTangentDivided(by: x) for the old arcTangent2(y, x)), pi and e
are Float.pi and Float.e, the power is the operator **, and exponential(), naturalLogarithm() and
logarithm(base:) became members of Real. As members they are generic: a body over Scalar: Real computes with a
Fixed exactly as with a Float, which free functions of Float64 never allowed.
Decimal#
native type Decimal with Signed, Hash
extend Decimal {
static zero: Self = 0
static one: Self = 1
}
extend Decimal {
fn rounded(scale: Int32, rounding: Rounding): Self
fn divided(by: Self, scale: Int32, rounding: Rounding): Self
native fn scale(): Int32
native fn normalized(): Self
native fn unscaledText(): String
static fn of(unscaled: String, scale: Int32): Result<Self, NumberParseError>
}
extend Decimal with From<Int64>
extend Decimal with From<UInt64>
extend Decimal with TryFrom<String, NumberParseError>Exact base-10 arithmetic: a decimal literal adapts to Decimal the same way it adapts to Float64
(const price: Decimal = 19.99) and keeps every digit and its scale. See Decimal:
both back ends compute it, and a value can also be read with Decimal.tryFrom or converted with Decimal.from.
ByteSize#
type ByteSize with Show, Compare, Add, Subtract, Multiply<Int64, ByteSize> {
fn bytes(): Int64
fn fraction(of: ByteSize): Float64
fn kilobytes(): Float64
fn megabytes(): Float64
fn gigabytes(): Float64
fn terabytes(): Float64
fn kibibytes(): Float64
fn mebibytes(): Float64
fn gibibytes(): Float64
fn tebibytes(): Float64
fn compare(other: Self): Ordering
fn add(other: Self): Self
fn subtract(other: Self): Self
fn multiply(other: Int64): Self
fn show(): String
}A number of bytes, the way a Duration is a number of nanoseconds: one Int64, signed, so the difference of two sizes
may be negative. Int64 makes one in two families of units - kilobytes, megabytes, gigabytes and terabytes
count in powers of 1000, as a disk is sold; kibibytes, mebibytes, gibibytes and tebibytes in powers of 1024, as
a memory is counted - and the members of the same names read one back as a Float64. std/number owns Int64, so
64.megabytes() needs no import anywhere, and ByteSize itself is in the prelude.
const limit = 64.megabytes()
print limit.bytes()
print 1536.bytes()
print(2.gibibytes() - 512.mebibytes())
print 256.mebibytes().fraction(of: 1.gibibytes())
// prints 64000000
// prints 1.5 KiB
// prints 1.5 GiB
// prints 0.25show writes the largest binary unit with at least one of it, to one decimal (15.9 GiB), and whole bytes below a
kibibyte (512 B). So 64.megabytes() prints 61.0 MiB: the decimal readers are one call away when a number has to
match what a disk vendor or a file manager says.
Conversions#
Widening between integer types, and from an integer or Float32 to Float64, is From and therefore infallible
(Int64.from(byte) for a UInt8). Narrowing - Int64 down to Int8 or Int32, or Float64 to Int64 - is
TryFrom<Source, NumberRangeError> instead, and so is Float64 to Float32, which rounds to the nearest Float32 and
fails only beyond its range. Decimal.from takes an Int64 or a UInt64 exactly, Float64.from(decimal) rounds to
the nearest Float64, and Int64.tryFrom and UInt64.tryFrom take a Decimal that is whole and in range.
Int64.from(character) is the code point of a Char; the other direction
is std/text's Char.tryFrom.
Related#
- Integers - the widths, the defaults, and what overflow does.
- Floating-point numbers - why
==andcomparedisagree, and why floats are notHash. - Decimal - exact base-ten arithmetic, and its digits through JSON, YAML and TOML.
- std/core -
Add,Subtract,Negateand the other operator traitsNumericandSignedbuild on. - Operators are traits -
**, its precedence, and why^is no operator.