break_eternity 0.2.1
break_eternity: ^0.2.1 copied to clipboard
Big numbers for idle and incremental games: a faithful Dart port of break_eternity.js, representing values up to 10^^1e308 with fast, constant-time arithmetic.
Changelog #
0.2.1 #
- Fixed the
LICENSEfile so pub.dev recognises it. The licence was always MIT, but the file also carried a paragraph explaining the relationship to break_eternity.js, and pub.dev's detector matches the file against the OSI templates and gives up as soon as unrelated prose appears. 0.2.0 therefore scored 0/10 for licensing and displayed "no license was recognized". The file now contains the MIT text and nothing else — both copyright lines are kept, which is fine — and the explanation moved to the README. Verified withpana: 150/160 before, 160/160 after.
0.2.0 #
-
Added
toInt(),toIntOrNull(),toIntClamped(),toBigInt(), theisIntegerpredicate and the~/operator. These have no counterpart in break_eternity.js, which targets a language with one numeric type; Dart code needs a realintfor list indices, RNG bounds,Durationand database columns. All additive — no existing behaviour changes.toInt()throws rather than approximating above layer 0 (about9e15), because that is wheremagbecomes a logarithm and no exact integer survives. This is deliberately unliketoDouble().toInt(), which saturates silently at the int64 boundary on the VM. -
Documented that
powis not exact on integer results, sofloor(pow(x, n))can be one low — the failure mode behind off-by-one cost tables and level curves. New README section "Do notfloorapow", plus caveats onpow,sqr,cube,cbrt,rootand/. No behaviour changed: the inexactness is inherited from break_eternity.js, which produces bit-identical answers, andtest/precision_test.dartnow pins it so it cannot be "fixed" by accident.
0.1.0 #
Initial release: the core Decimal value type.
sign/layer/magrepresentation covering values up to 10^^1e308, ported from break_eternity.js 2.1.3.- Arithmetic:
+,-,*,/,%, negation,abs,reciprocal, and the rounding family (floor,ceil,round,truncate). - Full comparison surface including tolerance-based variants.
- Conversions to and from
numandString. - Logarithms:
log10,absLog10,pLog10,log2,lnandlog(base). - Powers and roots:
pow,pow10,powBase,root,sqr,sqrt,cube,cbrtandexp. - The incremental-game series helpers, in closed form and constant time at any
scale:
affordGeometricSeries,sumGeometricSeries,affordArithmeticSeries,sumArithmeticSeriesandefficiencyOfPurchase. These are what let a game offer "buy max" when the player's balance has long since passed the point where a purchase loop could terminate. mod(other, floored: true)alongside%, which is the truncated-division modulo. The two agree on positive operands and differ in sign otherwise.- Tetration and its inverses:
tetrate,iteratedExp,iteratedLog,slog,layerAdd,layerAdd10andlambertW(both real branches), pluspentateandpentaLogone level above. Non-integer heights use the reference's analytic approximation for bases up to 10 and its linear one above that; passlinear: trueto force the linear approximation everywhere. Heights are a plainnum, as they are in the original — a tower taller than 1.8e308 is not representable regardless. - The full
parsegrammar:x^y,x^^y,x^^^y(each optionally carrying a payload after a semicolon), theXpY/X PT Y/XFYtetration shorthands, stacked exponents such as2e3e4,(e^N)Mwith a negative or fractionalN, exponents no double can hold such as1e400, and thousands separators. - Verified against the JavaScript reference implementation with generated
fixtures (44 operations, 32,561 cases) and a native-
doubleoracle suite, both run on the Dart VM and on dart2js. - Self-contained software
log10andlog2(no dependency on the host C library'slog), so results are identical on the Dart VM, dart2js and Wasm, and exact inputs give exact answers:1e30.dec.log10()is exactly 30, andlog2is exact on every power of two between 2^-52 and 2^52 (above that the reference is inexact too, and this port matches it).ln,expandpowkeep callingdart:math, because the reference callsMath.log,Math.expandMath.pow; they are the functions whose last bit is platform-dependent. ECMAScript leavesMath.pow's accuracy implementation-defined, and compiled to JavaScript it differs by architecture —7.dec.sqr()is48.99999999999999on macOS/arm64 and exactly49on Linux/x64 — so anything reached throughpowis portable to a tolerance, not to the bit.
Two deliberate divergences from the reference, both introduced with the tetration work and both in the same direction — refusing rather than guessing:
parserejects input JavaScript's lenientparseFloatwould accept. The reference reads5 applesas5,garbagee5as1e5, and a 400-digit integer as0; all three raise aFormatExceptionhere.parsestrips every thousands separator. The reference usesString.replacewith a string pattern, which removes only the first, so it reads1,000,000as1000.
Also worth knowing: <= and >= follow IEEE 754 here, while the reference
defines them as !gt and !lt and so reports NaN as satisfying both. The
places inside tetration where that changes an answer — slog of a NaN, mainly
— reproduce the reference's result explicitly, so the fixtures still match.
Note on the software log2: an earlier draft computed it as
log10(x) / log10(2), which returns -10.999999999999998 for 2^-11 where
JavaScript returns exactly -11. It is now a port of the same fdlibm
__ieee754_log2 that V8 ships, verified bit-identical to Math.log2 on a
39,000-value corpus on the VM and a 56,000-value corpus under dart2js.
Hardening against hostile save data, from a pre-release security review. Save
strings are attacker-controlled in a shipped game, so parse and tryParse
must always terminate and must never throw anything but FormatException:
(e^N)0no longer hangs. Normalising a zero magnitude walked the layer-down loop one step at a time — about 60 days forNof 1e15, and genuinely forever forNat or above 2^54, where subtracting 1 from the layer stops changing the double. The result is now computed directly; it is the same value the loop would have reached, so every generated fixture still matches.tryParserejects inputs longer than 4096 characters instead of raisingStackOverflowErrorout of the regex engine. Diverges from the reference, which would parse a multi-megabyte literal.(e^N)Maccepts anNin scientific notation, so values at or above layer 1e21 — includingDecimal.layerMaxandDecimal.layerMin— round-trip throughtoString/parseinstead of producing an unloadable save.toStringAsPrecisionclamps rather than throwingRangeErrorfor magnitudes below 1 at 16 or more digits. Costs at most five digits of double-rounding noise, which the reference emits.