This site describes solve-engine 2.44.0, the version npm installs.
Big integers
An ordinary number here is a double, the double-precision floating-point value a spreadsheet or a JavaScript program also uses. A double holds every whole number up to 9,007,199,254,740,991 exactly. That is 2^53 − 1, and the numbers up to it are often called the safe range. Past it a double can hold only some whole numbers, with gaps between them that widen as the numbers grow, so a result landing in a gap has to be rounded to a neighbour.
Whole numbers stay exact
Section titled “Whole numbers stay exact”When adding, subtracting, multiplying or raising whole numbers produces a result past the safe range, the engine works it out exactly instead, and every digit is shown. The answer is still an ordinary number: it can be stored in a variable, compared, divided into a fraction or multiplied by money like any other.
2^53 // 9,007,199,254,740,9922^53 + 1 // 9,007,199,254,740,9933^40 // 12,157,665,459,056,928,8012^64 // 18,446,744,073,709,551,616fact(25) // 15,511,210,043,330,985,984,000,000The operations that take a whole number and give one back read the exact value
too: the remainder (mod), the rounding functions, gcd and lcm,
permutation and combination, and writing a number in another base. So does
money, and a place count asked for with to N dp.
7^77 mod 13 // 113^40 mod 7 // 4floor(2^53 + 1) // 9,007,199,254,740,993lcm(2^40, 3^20) // 3,833,759,992,447,475,122,176combination(56, 23) // 3,167,295,784,216,200(2^53 + 1) as hex // 0x20000000000001(2^60 + 1) * $1 // $1,152,921,504,606,846,977.003^40 to 2 dp // 12,157,665,459,056,928,801.00Where exactness stops
Section titled “Where exactness stops”A whole number typed past the safe range in plain digits keeps the digits
typed. A double would round 9007199254740993 to the nearest number it can
hold, 9,007,199,254,740,992, and show that as if it were the number written, so
the digits are read as an exact integer instead, the same one 2^53 + 1 builds.
A number typed in scientific notation is different: 1e16 names a double, and a
double past the safe range may already be a rounding, so it keeps its double, and
so does arithmetic on it. The same number built from whole numbers within the
range is exact:
9007199254740993 // 9,007,199,254,740,99312345678901234567890 + 1 // 12,345,678,901,234,567,8911e16 + 1 - 1e16 // 010^16 + 1 - 10^16 // 1A number typed with a decimal point keeps its digits too. Past the safe range a
double holds no fraction at all, so 9007199254740993.5 would be the double
9,007,199,254,740,994, and the half the reader typed would be gone. The engine
reads a decimal written in plain digits exactly, and where a double is too
coarse to hold the places shown, the digits come from that exact reading, and
from exact arithmetic on it:
9007199254740993.5 // 9,007,199,254,740,993.509007199254740993.5 + 1 // 9,007,199,254,740,994.509007199254740993.5 / 2 // 4,503,599,627,370,496.759007199254740993.5 == 9007199254740994 // false2^60 + 0.5 // 1,152,921,504,606,846,976.50Converting such a number keeps the exact reading too. as int drops the
fraction the way int and trunc do, towards zero, so a negative number loses
its fraction upwards; and as percent, which writes a number as a share of a
hundred, moves the point of the exact decimal two places:
9007199254740993.5 as int // 9,007,199,254,740,993-9007199254740993.5 as int // -9,007,199,254,740,993floor(9007199254740993.5) // 9,007,199,254,740,993(2^53 + 1) as int // 9,007,199,254,740,9939007199254740993.5 as percent // 900,719,925,474,099,350.00%(2^53 + 1) as percent // 900,719,925,474,099,300.00%A fraction is kept exactly as well. 2^60 + 0.5 is not a decimal anyone typed
but a sum, held as the exact fraction 2,305,843,009,213,693,953 over 2, while
its double, which holds no fraction at that size, is 2^60 itself. The rounding
functions and as int read the exact fraction first, then an exact decimal, and
the double only when there is neither, so each one rounds the number written.
floor rounds down and ceil up, int, trunc and as int drop the fraction
towards zero, and round takes a half away from zero, as it does for a small
number (round(2.5) is 3 and round(-2.5) is -3):
floor(2^60 + 0.5) // 1,152,921,504,606,846,976ceil(2^60 + 0.5) // 1,152,921,504,606,846,977round(2^60 + 0.5) // 1,152,921,504,606,846,977(2^60 + 0.5) as int // 1,152,921,504,606,846,976floor(-(2^60 + 0.5)) // -1,152,921,504,606,846,977int(-(2^60 + 0.5)) // -1,152,921,504,606,846,976round(-(2^60 + 0.5)) // -1,152,921,504,606,846,977round(2^60 + 1/3) // 1,152,921,504,606,846,976The other limits:
- A result with no exact reading. A square root, a power with a fractional exponent or a logarithm has no exact decimal, so its result is a double, and so is arithmetic on it.
- Past the largest double. A double has no finite value beyond about 1.8 × 10^308, and the answer there is infinity, as it always was. A percentage is a hundred times its number, so one past that point is refused by name rather than written as an infinity (see percentages).
- A percentage typed with its sign. A percentage written with
%after a number that large reads the nearest double (900719925474099350%);as percentof the number is the exact form. - Some work on a large quantity. A quantity with a unit (a length, a mass, a
number of days) past 2^53 keeps the exact value it was written as, so
9007199254740993.5 mis 9,007,199,254,740,993.50 m, andfloor,ceil,round,trunc,intandas intof it round that value, not the nearest double. Adding or subtracting another amount in the same unit, and multiplying or dividing by a plain number, keep it exact too. Converting it into another unit (in km), multiplying two quantities together, andmodread the nearest double, since a conversion factor is itself a double. Money goes further: an amount of money keeps its exact decimal at any size, sofloor($9007199254740993.5)is $9,007,199,254,740,993.00, where the nearest double would round from $9,007,199,254,740,994.
sqrt(2^106) + 0.5 // 9,007,199,254,740,9922^1024 // ∞(2^53 + 1) kg // 9,007,199,254,740,993.00 kgceil((2^60 + 0.5) m) // 1,152,921,504,606,846,977.00 mround((2^60 + 0.5) m) // 1,152,921,504,606,846,977.00 m(2^60 + 0.5) m + 1 m // 1,152,921,504,606,846,977.50 mfloor($9007199254740993.5) // $9,007,199,254,740,993.00ceil($9007199254740993.5) // $9,007,199,254,740,994.00For a program embedding the engine, the value of such a result is still the
nearest double, so existing code reading it is unaffected. The exact integer is
the value’s rational (with a denominator of one), and the formatted result
shows its digits.
Typing a big integer: the n suffix
Section titled “Typing a big integer: the n suffix”Package:
BIGINT_PACKAGE. Registered bycreateEngine(); for a slimmer engine, register it explicitly (see choosing packages).
To type a whole number past the safe range and keep every digit, suffix it with
n. The result is a big integer, a separate kind of number that holds a whole
number of any size. It is also what bitwise work past the 32-bit range needs, and
its division is whole-number division, which drops the remainder. A big integer
prints its digits without grouping.
12345678901234567890n + 1 // 12345678901234567891123n * 2 // 2467n / 2n // 31n << 40 // 1099511627776A big integer and an exact result are the same number when their digits are,
so the two combine and compare on those digits: the n form of 3^40 is
3^40.
3^40 - 12157665459056928801n // 03^40 == 12157665459056928801n // true(2^53 + 1) & 1n // 1