A number as long as you ask for
A double is a real carried to fifty-three bits. This is the same thing at whatever width you name, with the width kept on the value and a direction for the bits that do not fit.
The four kinds the tower holds are widthless: an integer has as many digits as it needs, a float has exactly fifty-three, a fraction is exact or is not. A real carried to a width is the case in between — a value good to the number of digits the caller pays for, and no better.
let c = @moonnum.Context::new(200)
@moonnum.BigFloat::of_double(0.1)
@moonnum.BigFloat::parse("0.1", context=c).to_decimal(60)
0.1
0.100000000000000000000000000000000000000000000000000000000000
The first is the double nearest to a tenth — printed as 0.1
because that is the shortest text that reads back as it. The second is a
tenth at two hundred bits, which is what a tenth is to forty more places.
Neither of them is a tenth; both say how far they get.
Where the width goes
Fixed also carries a width, and spends it on the distance from
the point: a value far from one keeps few digits, and two values multiplied
together need the width chosen before the computation starts.
BigFloat spends it on the significand instead — the value is
mantissa * 2^exponent — so every value keeps the same count of
digits wherever it sits on the line.
let one = @moonnum.BigFloat::one()
let wide = @moonnum.BigFloat::one(context=c)
let three = @moonnum.BigFloat::of_int(3)
one / three
(wide + wide) / three
0.333333333333333
0.666666666666666666666666666666666666666666666666666666666667
An operator keeps the wider of its two operands and rounds to nearest even. That is the answer an operand set can justify: it is as good as what went in and no better. A caller who wants a particular width says so, and the width travels with the answer, so the next operation inherits it without being told twice.
Five directions, one tie
A result that does not fit is rounded, and how is a choice:
Nearest to the even digit, Floor toward minus
infinity, Ceiling toward plus infinity, Down toward
zero, Up away from zero. One plus half of the last place of a
fifty-three-bit value is the case where all five disagree, and the only case
that is easy to get wrong.
let one = @moonnum.BigFloat::one(context=c)
let half_place = @moonnum.BigFloat::parse(
"1.1102230246251565404236316680908203125e-16", context=c,
)
for rounding in [
@moonnum.Rounding::Nearest, @moonnum.Rounding::Floor,
@moonnum.Rounding::Ceiling, @moonnum.Rounding::Down, @moonnum.Rounding::Up,
] {
let context = @moonnum.Context::new(53, rounding~)
println("\{rounding}: \{context.add(one, half_place).to_significant(17)}")
}
nearest: 1.0
floor: 1.0
ceiling: 1.0000000000000002
down: 1.0
up: 1.0000000000000002
A context is the width and the direction together, because a direction
without a width has nothing to round. Context::add, and its
siblings sub, mul, div and
divide, take both from the context. Addition has no
context-taking method of its own on the type: + already owns
the name add.
The functions, at any width
sqrt, exp, ln and pow are
the ones the series are for; everything circular and hyperbolic is written on
top of them. None of them reads a table of digits: pi is
Machin's formula, ln 2 is two arctangents of a third, and
e is the exponential of one, each computed at the width asked
for. A thousand digits costs a few thousand terms and nothing else.
Two of them exist because the obvious spelling has a hole in it.
exp(x) - 1 is zero for any x small enough that the
exponential is one, and ln(1 + x) is zero for the same reason;
expm1 and log1p are the same two answers taken
without ever forming the one, so a small argument keeps its digits instead of
being swallowed by a rounding that happened before the subtraction.
let two = @moonnum.BigFloat::of_int(2, context=c)
two.sqrt(context=c).to_significant(60)
@moonnum.BigFloat::e(context=c).to_significant(60)
@moonnum.BigFloat::pi(context=c).to_significant(60)
@moonnum.BigFloat::ln2(context=c).to_significant(60)
two.pow(@moonnum.BigFloat::of_double(0.5), context=c).to_significant(60)
1.41421356237309504880168872420969807856967187537694807317668
2.71828182845904523536028747135266249775724709369995957496697
3.14159265358979323846264338327950288419716939937510582097494
0.69314718055994530941723212145817656807550013436025525412068
1.41421356237309504880168872420969807856967187537694807317668
The width is a count of bits, and a digit costs three and a third of them, so a thousand digits is thirty-three hundred and twenty-two bits. Nothing about the arithmetic changes at that size; it is the same code, a longer number.
let thousand = @moonnum.Context::new(3322)
@moonnum.BigFloat::pi(context=thousand).to_decimal(80)
3.14159265358979323846264338327950288419716939937510582097494459230781640628620900
Turning a circle
A sine needs its argument reduced against a quarter turn, and the turn has to
be carried to as many bits as the argument has before its point — otherwise
the remainder is noise. That is the whole difficulty of
sin(1e30), and the reason the answer is worth printing.
let one_radian = @moonnum.BigFloat::one(context=c)
one_radian.sin(context=c).to_significant(40)
one_radian.cos(context=c).to_significant(40)
one_radian.atan(context=c).to_significant(40)
@moonnum.BigFloat::parse("1e30", context=c).sin(context=c).to_significant(20)
one_radian.sinh(context=c).to_significant(40)
one_radian.asinh(context=c).to_significant(40)
0.8414709848078965066525023216302989996226
0.5403023058681397174009366074429766037323
0.7853981633974483096156608458198757210493
-0.09011690191213805803
1.175201193643801456882381850595600815156
0.8813735870195430252326093249797923090282
Past a hundred thousand bits of reduction the library stops rather than answer a remainder of nothing. That is not a limit of the arithmetic; it is the point past which carrying the turn would cost more than the answer is worth, and it is said out loud rather than hidden.
Where there is no value
A real type has three answers that are not numbers: an infinity above, an infinity below, and a NaN. They are kinds of the value, not numbers with a flag, so the arithmetic reads digits only where there are digits. A square root of a negative, a logarithm of a negative, a quotient of zero by zero: each has the answer the standard gives it, and none of them is a number that looks plausible.
(-one).sqrt(context=c)
@moonnum.BigFloat::zero(context=c).ln(context=c)
one / @moonnum.BigFloat::zero(context=c)
one.divide(@moonnum.BigFloat::zero(context=c)).to_string() catch {
@moonnum.ZeroDivision => "division by zero"
_ => "answered"
}
nan
-inf
inf
division by zero
The operator cannot raise, so it answers an infinity; divide
is the same quotient for a caller who would rather be told. That is the same
split the rest of the library makes between / and the functions
that refuse.
When the answer is complex
Those three answers are right for the real type and they are not the whole
story. A square root of a negative has a value; it is just not a real one.
The library carries that value in a second type, BigComplex,
whose two parts are each a BigFloat at whatever width they were
given — and the promotion is one call, written out where it happens, because
a function whose type is a real number should not quietly return something
else.
let quick = @moonnum.Context::new(53)
let minus_one = -@moonnum.BigFloat::one(context=quick)
let zero = @moonnum.BigFloat::zero(context=quick)
minus_one.to_complex().sqrt(context=quick)
minus_one.to_complex().ln(context=quick)
@moonnum.BigComplex::new(
@moonnum.BigFloat::of_int(2, context=quick), zero,
).asin(context=quick)
(0.0+1.0j)
(0.0+3.14159265358979j)
(1.5707963267949-1.31695789692482j)
The branches are the principal ones, and the cut is read from above. That is
not a preference: a BigFloat has no signed zero, so a point on
the cut has one spelling and one meaning — the side approaching from the
upper half plane. A logarithm of a negative is therefore a logarithm plus
half a turn, an arcsine past one has a negative imaginary part, and an
arc-tangent of a purely imaginary argument past j bends the
other way. The reference implementation has no signed zero either, which is
why its answers and these agree without a table of exceptions.
let corner = @moonnum.BigComplex::new(
@moonnum.BigFloat::of_int(3, context=quick),
@moonnum.BigFloat::of_int(4, context=quick).neg(),
)
corner
corner.abs(context=quick)
corner.arg(context=quick)
(3.0-4.0j)
5.0
-0.927295218001612
The operations the type carries are the ones the real type carries, written
the same way: +, -, *, a modulus, an
argument, a conjugate, and the elementary functions. Division is
Complex-style rather than operator-style, for the reason the
fraction and the complex types already have one: it can refuse. A complex
power is exp(w ln z) on the principal branch, with a whole real
exponent taken by squaring instead so that an integer power stays exact — a
negative base under a fractional exponent goes through the logarithm, so its
real part comes out as a rounded cosine of half a turn rather than a zero, and
the page would rather say that than reach for a special case that hides it.
In and out of the other shapes
A double goes in and comes back out unchanged, since every finite double is a binary fraction already. A whole number rounds to the width, and an integer out of a value says which way to go.
two.sqrt(context=c).to_double()
@moonnum.BigFloat::parse("2.5", context=c).to_bigint()
two.sqrt(context=c).to_fixed(@moonnum.Fixed::bits_for_digits(20))
@moonnum.BigFloat::of_bigint(
@moonnum.parse_int("123456789012345678901234567890"), context=c,
)
1.4142135623730951
2
1.4142135623730950488
1.2345678901234567890123456789e+29
The last line is a thirty-digit integer at two hundred bits, which is
twenty-nine digits of it — the width, not the integer, is what decides. Text
is the other direction: parse reads what float()
reads and rounds once at the end, and to_string writes as many
digits as the width speaks for — fifteen at fifty-three bits, sixty at two
hundred — with the zeros at the end dropped so a whole number still reads as
one. to_significant and to_decimal take a count when
one is wanted.