break_infinity.js
Version:
Performance-oriented decimal.js replacement for incremental games.
1,556 lines (1,208 loc) • 43.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Decimal = factory());
}(this, (function () { 'use strict';
var padEnd = function (string, maxLength, fillString) {
if (string == null || maxLength == null) {
return string;
}
var result = String(string);
var targetLen = typeof maxLength === 'number'
? maxLength
: parseInt(maxLength, 10);
if (isNaN(targetLen) || !isFinite(targetLen)) {
return result;
}
var length = result.length;
if (length >= targetLen) {
return result;
}
var filled = fillString == null ? '' : String(fillString);
if (filled === '') {
filled = ' ';
}
var fillLen = targetLen - length;
while (filled.length < fillLen) {
filled += filled;
}
var truncated = filled.length > fillLen ? filled.substr(0, fillLen) : filled;
return result + truncated;
};
// consider adding them together pointless, just return the larger one
var MAX_SIGNIFICANT_DIGITS = 17; // Highest value you can safely put here is Number.MAX_SAFE_INTEGER-MAX_SIGNIFICANT_DIGITS
var EXP_LIMIT = 9e15; // The largest exponent that can appear in a Number, though not all mantissas are valid here.
var NUMBER_EXP_MAX = 308; // The smallest exponent that can appear in a Number, though not all mantissas are valid here.
var NUMBER_EXP_MIN = -324; // Tolerance which is used for Number conversion to compensate floating-point error.
var ROUND_TOLERANCE = 1e-10;
var powerOf10 = function () {
// We need this lookup table because Math.pow(10, exponent)
// when exponent's absolute value is large is slightly inaccurate.
// You can fix it with the power of math... or just make a lookup table.
// Faster AND simpler
var powersOf10 = [];
for (var i = NUMBER_EXP_MIN + 1; i <= NUMBER_EXP_MAX; i++) {
powersOf10.push(Number("1e" + i));
}
var indexOf0InPowersOf10 = 323;
return function (power) {
return powersOf10[power + indexOf0InPowersOf10];
};
}();
var D = function D(value) {
return value instanceof Decimal ? value : new Decimal(value);
};
var ME = function ME(mantissa, exponent) {
return new Decimal().fromMantissaExponent(mantissa, exponent);
};
var ME_NN = function ME_NN(mantissa, exponent) {
return new Decimal().fromMantissaExponent_noNormalize(mantissa, exponent);
};
function affordGeometricSeries(resourcesAvailable, priceStart, priceRatio, currentOwned) {
var actualStart = priceStart.mul(priceRatio.pow(currentOwned));
return Decimal.floor(resourcesAvailable.div(actualStart).mul(priceRatio.sub(1)).add(1).log10() / priceRatio.log10());
}
function sumGeometricSeries(numItems, priceStart, priceRatio, currentOwned) {
return priceStart.mul(priceRatio.pow(currentOwned)).mul(Decimal.sub(1, priceRatio.pow(numItems))).div(Decimal.sub(1, priceRatio));
}
function affordArithmeticSeries(resourcesAvailable, priceStart, priceAdd, currentOwned) {
// n = (-(a-d/2) + sqrt((a-d/2)^2+2dS))/d
// where a is actualStart, d is priceAdd and S is resourcesAvailable
// then floor it and you're done!
var actualStart = priceStart.add(currentOwned.mul(priceAdd));
var b = actualStart.sub(priceAdd.div(2));
var b2 = b.pow(2);
return b.neg().add(b2.add(priceAdd.mul(resourcesAvailable).mul(2)).sqrt()).div(priceAdd).floor();
}
function sumArithmeticSeries(numItems, priceStart, priceAdd, currentOwned) {
var actualStart = priceStart.add(currentOwned.mul(priceAdd)); // (n/2)*(2*a+(n-1)*d)
return numItems.div(2).mul(actualStart.mul(2).plus(numItems.sub(1).mul(priceAdd)));
}
function efficiencyOfPurchase(cost, currentRpS, deltaRpS) {
return cost.div(currentRpS).add(cost.div(deltaRpS));
}
/**
* The Decimal's value is simply mantissa * 10^exponent.
*/
var Decimal =
/** @class */
function () {
function Decimal(value) {
/**
* A number (double) with absolute value between [1, 10) OR exactly 0.
* If mantissa is ever 10 or greater, it should be normalized
* (divide by 10 and add 1 to exponent until it is less than 10,
* or multiply by 10 and subtract 1 from exponent until it is 1 or greater).
* Infinity/-Infinity/NaN will cause bad things to happen.
*/
this.mantissa = NaN;
/**
* A number (integer) between -EXP_LIMIT and EXP_LIMIT.
* Non-integral/out of bounds will cause bad things to happen.
*/
this.exponent = NaN;
if (value === undefined) {
this.m = 0;
this.e = 0;
} else if (value instanceof Decimal) {
this.fromDecimal(value);
} else if (typeof value === "number") {
this.fromNumber(value);
} else {
this.fromString(value);
}
}
Object.defineProperty(Decimal.prototype, "m", {
get: function get() {
return this.mantissa;
},
set: function set(value) {
this.mantissa = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Decimal.prototype, "e", {
get: function get() {
return this.exponent;
},
set: function set(value) {
this.exponent = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Decimal.prototype, "s", {
get: function get() {
return this.sign();
},
set: function set(value) {
if (value === 0) {
this.e = 0;
this.m = 0;
return;
}
if (this.sgn() !== value) {
this.m = -this.m;
}
},
enumerable: false,
configurable: true
});
Decimal.fromMantissaExponent = function (mantissa, exponent) {
return new Decimal().fromMantissaExponent(mantissa, exponent);
};
Decimal.fromMantissaExponent_noNormalize = function (mantissa, exponent) {
return new Decimal().fromMantissaExponent_noNormalize(mantissa, exponent);
};
Decimal.fromDecimal = function (value) {
return new Decimal().fromDecimal(value);
};
Decimal.fromNumber = function (value) {
return new Decimal().fromNumber(value);
};
Decimal.fromString = function (value) {
return new Decimal().fromString(value);
};
Decimal.fromValue = function (value) {
return new Decimal().fromValue(value);
};
Decimal.fromValue_noAlloc = function (value) {
return value instanceof Decimal ? value : new Decimal(value);
};
Decimal.abs = function (value) {
return D(value).abs();
};
Decimal.neg = function (value) {
return D(value).neg();
};
Decimal.negate = function (value) {
return D(value).neg();
};
Decimal.negated = function (value) {
return D(value).neg();
};
Decimal.sign = function (value) {
return D(value).sign();
};
Decimal.sgn = function (value) {
return D(value).sign();
};
Decimal.round = function (value) {
return D(value).round();
};
Decimal.floor = function (value) {
return D(value).floor();
};
Decimal.ceil = function (value) {
return D(value).ceil();
};
Decimal.trunc = function (value) {
return D(value).trunc();
};
Decimal.add = function (value, other) {
return D(value).add(other);
};
Decimal.plus = function (value, other) {
return D(value).add(other);
};
Decimal.sub = function (value, other) {
return D(value).sub(other);
};
Decimal.subtract = function (value, other) {
return D(value).sub(other);
};
Decimal.minus = function (value, other) {
return D(value).sub(other);
};
Decimal.mul = function (value, other) {
return D(value).mul(other);
};
Decimal.multiply = function (value, other) {
return D(value).mul(other);
};
Decimal.times = function (value, other) {
return D(value).mul(other);
};
Decimal.div = function (value, other) {
return D(value).div(other);
};
Decimal.divide = function (value, other) {
return D(value).div(other);
};
Decimal.recip = function (value) {
return D(value).recip();
};
Decimal.reciprocal = function (value) {
return D(value).recip();
};
Decimal.reciprocate = function (value) {
return D(value).reciprocate();
};
Decimal.cmp = function (value, other) {
return D(value).cmp(other);
};
Decimal.compare = function (value, other) {
return D(value).cmp(other);
};
Decimal.eq = function (value, other) {
return D(value).eq(other);
};
Decimal.equals = function (value, other) {
return D(value).eq(other);
};
Decimal.neq = function (value, other) {
return D(value).neq(other);
};
Decimal.notEquals = function (value, other) {
return D(value).notEquals(other);
};
Decimal.lt = function (value, other) {
return D(value).lt(other);
};
Decimal.lte = function (value, other) {
return D(value).lte(other);
};
Decimal.gt = function (value, other) {
return D(value).gt(other);
};
Decimal.gte = function (value, other) {
return D(value).gte(other);
};
Decimal.max = function (value, other) {
return D(value).max(other);
};
Decimal.min = function (value, other) {
return D(value).min(other);
};
Decimal.clamp = function (value, min, max) {
return D(value).clamp(min, max);
};
Decimal.clampMin = function (value, min) {
return D(value).clampMin(min);
};
Decimal.clampMax = function (value, max) {
return D(value).clampMax(max);
};
Decimal.cmp_tolerance = function (value, other, tolerance) {
return D(value).cmp_tolerance(other, tolerance);
};
Decimal.compare_tolerance = function (value, other, tolerance) {
return D(value).cmp_tolerance(other, tolerance);
};
Decimal.eq_tolerance = function (value, other, tolerance) {
return D(value).eq_tolerance(other, tolerance);
};
Decimal.equals_tolerance = function (value, other, tolerance) {
return D(value).eq_tolerance(other, tolerance);
};
Decimal.neq_tolerance = function (value, other, tolerance) {
return D(value).neq_tolerance(other, tolerance);
};
Decimal.notEquals_tolerance = function (value, other, tolerance) {
return D(value).notEquals_tolerance(other, tolerance);
};
Decimal.lt_tolerance = function (value, other, tolerance) {
return D(value).lt_tolerance(other, tolerance);
};
Decimal.lte_tolerance = function (value, other, tolerance) {
return D(value).lte_tolerance(other, tolerance);
};
Decimal.gt_tolerance = function (value, other, tolerance) {
return D(value).gt_tolerance(other, tolerance);
};
Decimal.gte_tolerance = function (value, other, tolerance) {
return D(value).gte_tolerance(other, tolerance);
};
Decimal.log10 = function (value) {
return D(value).log10();
};
Decimal.absLog10 = function (value) {
return D(value).absLog10();
};
Decimal.pLog10 = function (value) {
return D(value).pLog10();
};
Decimal.log = function (value, base) {
return D(value).log(base);
};
Decimal.log2 = function (value) {
return D(value).log2();
};
Decimal.ln = function (value) {
return D(value).ln();
};
Decimal.logarithm = function (value, base) {
return D(value).logarithm(base);
};
Decimal.pow10 = function (value) {
if (Number.isInteger(value)) {
return ME_NN(1, value);
}
return ME(Math.pow(10, value % 1), Math.trunc(value));
};
Decimal.pow = function (value, other) {
// Fast track: 10^integer
if (typeof value === "number" && value === 10 && typeof other === "number" && Number.isInteger(other)) {
return ME_NN(1, other);
}
return D(value).pow(other);
};
Decimal.exp = function (value) {
return D(value).exp();
};
Decimal.sqr = function (value) {
return D(value).sqr();
};
Decimal.sqrt = function (value) {
return D(value).sqrt();
};
Decimal.cube = function (value) {
return D(value).cube();
};
Decimal.cbrt = function (value) {
return D(value).cbrt();
};
Decimal.dp = function (value) {
return D(value).dp();
};
Decimal.decimalPlaces = function (value) {
return D(value).dp();
};
/**
* If you're willing to spend 'resourcesAvailable' and want to buy something
* with exponentially increasing cost each purchase (start at priceStart,
* multiply by priceRatio, already own currentOwned), how much of it can you buy?
* Adapted from Trimps source code.
*/
Decimal.affordGeometricSeries = function (resourcesAvailable, priceStart, priceRatio, currentOwned) {
return affordGeometricSeries(D(resourcesAvailable), D(priceStart), D(priceRatio), currentOwned);
};
/**
* How much resource would it cost to buy (numItems) items if you already have currentOwned,
* the initial price is priceStart and it multiplies by priceRatio each purchase?
*/
Decimal.sumGeometricSeries = function (numItems, priceStart, priceRatio, currentOwned) {
return sumGeometricSeries(numItems, D(priceStart), D(priceRatio), currentOwned);
};
/**
* If you're willing to spend 'resourcesAvailable' and want to buy something with additively
* increasing cost each purchase (start at priceStart, add by priceAdd, already own currentOwned),
* how much of it can you buy?
*/
Decimal.affordArithmeticSeries = function (resourcesAvailable, priceStart, priceAdd, currentOwned) {
return affordArithmeticSeries(D(resourcesAvailable), D(priceStart), D(priceAdd), D(currentOwned));
};
/**
* How much resource would it cost to buy (numItems) items if you already have currentOwned,
* the initial price is priceStart and it adds priceAdd each purchase?
* Adapted from http://www.mathwords.com/a/arithmetic_series.htm
*/
Decimal.sumArithmeticSeries = function (numItems, priceStart, priceAdd, currentOwned) {
return sumArithmeticSeries(D(numItems), D(priceStart), D(priceAdd), D(currentOwned));
};
/**
* When comparing two purchases that cost (resource) and increase your resource/sec by (deltaRpS),
* the lowest efficiency score is the better one to purchase.
* From Frozen Cookies:
* http://cookieclicker.wikia.com/wiki/Frozen_Cookies_(JavaScript_Add-on)#Efficiency.3F_What.27s_that.3F
*/
Decimal.efficiencyOfPurchase = function (cost, currentRpS, deltaRpS) {
return efficiencyOfPurchase(D(cost), D(currentRpS), D(deltaRpS));
};
Decimal.randomDecimalForTesting = function (absMaxExponent) {
// NOTE: This doesn't follow any kind of sane random distribution, so use this for testing purposes only.
// 5% of the time, have a mantissa of 0
if (Math.random() * 20 < 1) {
return ME_NN(0, 0);
}
var mantissa = Math.random() * 10; // 10% of the time, have a simple mantissa
if (Math.random() * 10 < 1) {
mantissa = Math.round(mantissa);
}
mantissa *= Math.sign(Math.random() * 2 - 1);
var exponent = Math.floor(Math.random() * absMaxExponent * 2) - absMaxExponent;
return ME(mantissa, exponent);
/*
Examples:
randomly test pow:
var a = Decimal.randomDecimalForTesting(1000);
var pow = Math.random()*20-10;
if (Math.random()*2 < 1) { pow = Math.round(pow); }
var result = Decimal.pow(a, pow);
["(" + a.toString() + ")^" + pow.toString(), result.toString()]
randomly test add:
var a = Decimal.randomDecimalForTesting(1000);
var b = Decimal.randomDecimalForTesting(17);
var c = a.mul(b);
var result = a.add(c);
[a.toString() + "+" + c.toString(), result.toString()]
*/
};
/**
* When mantissa is very denormalized, use this to normalize much faster.
*/
Decimal.prototype.normalize = function () {
if (this.m >= 1 && this.m < 10) {
return this;
} // TODO: I'm worried about mantissa being negative 0 here which is why I set it again, but it may never matter
if (this.m === 0) {
this.m = 0;
this.e = 0;
return this;
}
var tempExponent = Math.floor(Math.log10(Math.abs(this.m)));
this.m = tempExponent === NUMBER_EXP_MIN ? this.m * 10 / 1e-323 : this.m / powerOf10(tempExponent);
this.e += tempExponent;
return this;
};
Decimal.prototype.fromMantissaExponent = function (mantissa, exponent) {
// SAFETY: don't let in non-numbers
if (!isFinite(mantissa) || !isFinite(exponent)) {
mantissa = Number.NaN;
exponent = Number.NaN;
return this;
}
this.m = mantissa;
this.e = exponent; // Non-normalized mantissas can easily get here, so this is mandatory.
this.normalize();
return this;
};
/**
* Well, you know what you're doing!
*/
Decimal.prototype.fromMantissaExponent_noNormalize = function (mantissa, exponent) {
this.m = mantissa;
this.e = exponent;
return this;
};
Decimal.prototype.fromDecimal = function (value) {
this.m = value.m;
this.e = value.e;
return this;
};
Decimal.prototype.fromNumber = function (value) {
// SAFETY: Handle Infinity and NaN in a somewhat meaningful way.
if (isNaN(value)) {
this.m = Number.NaN;
this.e = Number.NaN;
} else if (value === Number.POSITIVE_INFINITY) {
this.m = 1;
this.e = EXP_LIMIT;
} else if (value === Number.NEGATIVE_INFINITY) {
this.m = -1;
this.e = EXP_LIMIT;
} else if (value === 0) {
this.m = 0;
this.e = 0;
} else {
this.e = Math.floor(Math.log10(Math.abs(value))); // SAFETY: handle 5e-324, -5e-324 separately
this.m = this.e === NUMBER_EXP_MIN ? value * 10 / 1e-323 : value / powerOf10(this.e); // SAFETY: Prevent weirdness.
this.normalize();
}
return this;
};
Decimal.prototype.fromString = function (value) {
if (value.indexOf("e") !== -1) {
var parts = value.split("e");
this.m = parseFloat(parts[0]);
this.e = parseFloat(parts[1]); // Non-normalized mantissas can easily get here, so this is mandatory.
this.normalize();
} else if (value === "NaN") {
this.m = Number.NaN;
this.e = Number.NaN;
} else {
this.fromNumber(parseFloat(value));
if (isNaN(this.m)) {
throw Error("[DecimalError] Invalid argument: " + value);
}
}
return this;
};
Decimal.prototype.fromValue = function (value) {
if (value instanceof Decimal) {
return this.fromDecimal(value);
}
if (typeof value === "number") {
return this.fromNumber(value);
}
if (typeof value === "string") {
return this.fromString(value);
}
this.m = 0;
this.e = 0;
return this;
};
Decimal.prototype.toNumber = function () {
// Problem: new Decimal(116).toNumber() returns 115.99999999999999.
// TODO: How to fix in general case? It's clear that if toNumber() is
// VERY close to an integer, we want exactly the integer.
// But it's not clear how to specifically write that.
// So I'll just settle with 'exponent >= 0 and difference between rounded
// and not rounded < 1e-9' as a quick fix.
// UN-SAFETY: It still eventually fails.
// Since there's no way to know for sure we started with an integer,
// all we can do is decide what tradeoff we want between 'yeah I think
// this used to be an integer' and 'pfft, who needs THAT many decimal
// places tracked' by changing ROUND_TOLERANCE.
// https://github.com/Patashu/break_infinity.js/issues/52
// Currently starts failing at 800002. Workaround is to do .Round()
// AFTER toNumber() if you are confident you started with an integer.
// var result = this.m*Math.pow(10, this.e);
if (!isFinite(this.e)) {
return Number.NaN;
}
if (this.e > NUMBER_EXP_MAX) {
return this.m > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
}
if (this.e < NUMBER_EXP_MIN) {
return 0;
} // SAFETY: again, handle 5e-324, -5e-324 separately
if (this.e === NUMBER_EXP_MIN) {
return this.m > 0 ? 5e-324 : -5e-324;
}
var result = this.m * powerOf10(this.e);
if (!isFinite(result) || this.e < 0) {
return result;
}
var resultRounded = Math.round(result);
if (Math.abs(resultRounded - result) < ROUND_TOLERANCE) {
return resultRounded;
}
return result;
};
Decimal.prototype.mantissaWithDecimalPlaces = function (places) {
// https://stackoverflow.com/a/37425022
if (isNaN(this.m) || isNaN(this.e)) {
return Number.NaN;
}
if (this.m === 0) {
return 0;
}
var len = places + 1;
var numDigits = Math.ceil(Math.log10(Math.abs(this.m)));
var rounded = Math.round(this.m * Math.pow(10, len - numDigits)) * Math.pow(10, numDigits - len);
return parseFloat(rounded.toFixed(Math.max(len - numDigits, 0)));
};
Decimal.prototype.toString = function () {
if (isNaN(this.m) || isNaN(this.e)) {
return "NaN";
}
if (this.e >= EXP_LIMIT) {
return this.m > 0 ? "Infinity" : "-Infinity";
}
if (this.e <= -EXP_LIMIT || this.m === 0) {
return "0";
}
if (this.e < 21 && this.e > -7) {
return this.toNumber().toString();
}
return this.m + "e" + (this.e >= 0 ? "+" : "") + this.e;
};
Decimal.prototype.toExponential = function (places) {
// https://stackoverflow.com/a/37425022
// TODO: Some unfixed cases:
// new Decimal("1.2345e-999").toExponential()
// "1.23450000000000015e-999"
// new Decimal("1e-999").toExponential()
// "1.000000000000000000e-999"
// TBH I'm tempted to just say it's a feature.
// If you're doing pretty formatting then why don't you know how many decimal places you want...?
if (isNaN(this.m) || isNaN(this.e)) {
return "NaN";
}
if (this.e >= EXP_LIMIT) {
return this.m > 0 ? "Infinity" : "-Infinity";
}
if (this.e <= -EXP_LIMIT || this.m === 0) {
return "0" + (places > 0 ? padEnd(".", places + 1, "0") : "") + "e+0";
} // two cases:
// 1) exponent is < 308 and > -324: use basic toFixed
// 2) everything else: we have to do it ourselves!
if (this.e > NUMBER_EXP_MIN && this.e < NUMBER_EXP_MAX) {
return this.toNumber().toExponential(places);
}
if (!isFinite(places)) {
places = MAX_SIGNIFICANT_DIGITS;
}
var len = places + 1;
var numDigits = Math.max(1, Math.ceil(Math.log10(Math.abs(this.m))));
var rounded = Math.round(this.m * Math.pow(10, len - numDigits)) * Math.pow(10, numDigits - len);
return rounded.toFixed(Math.max(len - numDigits, 0)) + "e" + (this.e >= 0 ? "+" : "") + this.e;
};
Decimal.prototype.toFixed = function (places) {
if (isNaN(this.m) || isNaN(this.e)) {
return "NaN";
}
if (this.e >= EXP_LIMIT) {
return this.m > 0 ? "Infinity" : "-Infinity";
}
if (this.e <= -EXP_LIMIT || this.m === 0) {
return "0" + (places > 0 ? padEnd(".", places + 1, "0") : "");
} // two cases:
// 1) exponent is 17 or greater: just print out mantissa with the appropriate number of zeroes after it
// 2) exponent is 16 or less: use basic toFixed
if (this.e >= MAX_SIGNIFICANT_DIGITS) {
return this.m.toString().replace(".", "").padEnd(this.e + 1, "0") + (places > 0 ? padEnd(".", places + 1, "0") : "");
}
return this.toNumber().toFixed(places);
};
Decimal.prototype.toPrecision = function (places) {
if (this.e <= -7) {
return this.toExponential(places - 1);
}
if (places > this.e) {
return this.toFixed(places - this.e - 1);
}
return this.toExponential(places - 1);
};
Decimal.prototype.valueOf = function () {
return this.toString();
};
Decimal.prototype.toJSON = function () {
return this.toString();
};
Decimal.prototype.toStringWithDecimalPlaces = function (places) {
return this.toExponential(places);
};
Decimal.prototype.abs = function () {
return ME_NN(Math.abs(this.m), this.e);
};
Decimal.prototype.neg = function () {
return ME_NN(-this.m, this.e);
};
Decimal.prototype.negate = function () {
return this.neg();
};
Decimal.prototype.negated = function () {
return this.neg();
};
Decimal.prototype.sign = function () {
return Math.sign(this.m);
};
Decimal.prototype.sgn = function () {
return this.sign();
};
Decimal.prototype.round = function () {
if (this.e < -1) {
return new Decimal(0);
}
if (this.e < MAX_SIGNIFICANT_DIGITS) {
return new Decimal(Math.round(this.toNumber()));
}
return this;
};
Decimal.prototype.floor = function () {
if (this.e < -1) {
return Math.sign(this.m) >= 0 ? new Decimal(0) : new Decimal(-1);
}
if (this.e < MAX_SIGNIFICANT_DIGITS) {
return new Decimal(Math.floor(this.toNumber()));
}
return this;
};
Decimal.prototype.ceil = function () {
if (this.e < -1) {
return Math.sign(this.m) > 0 ? new Decimal(1) : new Decimal(0);
}
if (this.e < MAX_SIGNIFICANT_DIGITS) {
return new Decimal(Math.ceil(this.toNumber()));
}
return this;
};
Decimal.prototype.trunc = function () {
if (this.e < 0) {
return new Decimal(0);
}
if (this.e < MAX_SIGNIFICANT_DIGITS) {
return new Decimal(Math.trunc(this.toNumber()));
}
return this;
};
Decimal.prototype.add = function (value) {
// figure out which is bigger, shrink the mantissa of the smaller
// by the difference in exponents, add mantissas, normalize and return
// TODO: Optimizations and simplification may be possible, see https://github.com/Patashu/break_infinity.js/issues/8
var decimal = D(value);
if (this.m === 0) {
return decimal;
}
if (decimal.m === 0) {
return this;
}
var biggerDecimal;
var smallerDecimal;
if (this.e >= decimal.e) {
biggerDecimal = this;
smallerDecimal = decimal;
} else {
biggerDecimal = decimal;
smallerDecimal = this;
}
if (biggerDecimal.e - smallerDecimal.e > MAX_SIGNIFICANT_DIGITS) {
return biggerDecimal;
} // Have to do this because adding numbers that were once integers but scaled down is imprecise.
// Example: 299 + 18
var mantissa = Math.round(1e14 * biggerDecimal.m + 1e14 * smallerDecimal.m * powerOf10(smallerDecimal.e - biggerDecimal.e));
return ME(mantissa, biggerDecimal.e - 14);
};
Decimal.prototype.plus = function (value) {
return this.add(value);
};
Decimal.prototype.sub = function (value) {
return this.add(D(value).neg());
};
Decimal.prototype.subtract = function (value) {
return this.sub(value);
};
Decimal.prototype.minus = function (value) {
return this.sub(value);
};
Decimal.prototype.mul = function (value) {
// This version avoids an extra conversion to Decimal, if possible. Since the
// mantissa is -10...10, any number short of MAX/10 can be safely multiplied in
if (typeof value === "number") {
if (value < 1e307 && value > -1e307) {
return ME(this.m * value, this.e);
} // If the value is larger than 1e307, we can divide that out of mantissa (since it's
// greater than 1, it won't underflow)
return ME(this.m * 1e-307 * value, this.e + 307);
}
var decimal = typeof value === "string" ? new Decimal(value) : value;
return ME(this.m * decimal.m, this.e + decimal.e);
};
Decimal.prototype.multiply = function (value) {
return this.mul(value);
};
Decimal.prototype.times = function (value) {
return this.mul(value);
};
Decimal.prototype.div = function (value) {
return this.mul(D(value).recip());
};
Decimal.prototype.divide = function (value) {
return this.div(value);
};
Decimal.prototype.divideBy = function (value) {
return this.div(value);
};
Decimal.prototype.dividedBy = function (value) {
return this.div(value);
};
Decimal.prototype.recip = function () {
return ME(1 / this.m, -this.e);
};
Decimal.prototype.reciprocal = function () {
return this.recip();
};
Decimal.prototype.reciprocate = function () {
return this.recip();
};
/**
* -1 for less than value, 0 for equals value, 1 for greater than value
*/
Decimal.prototype.cmp = function (value) {
var decimal = D(value); // TODO: sign(a-b) might be better? https://github.com/Patashu/break_infinity.js/issues/12
/*
from smallest to largest:
-3e100
-1e100
-3e99
-1e99
-3e0
-1e0
-3e-99
-1e-99
-3e-100
-1e-100
0
1e-100
3e-100
1e-99
3e-99
1e0
3e0
1e99
3e99
1e100
3e100
*/
if (this.m === 0) {
if (decimal.m === 0) {
return 0;
}
if (decimal.m < 0) {
return 1;
}
if (decimal.m > 0) {
return -1;
}
}
if (decimal.m === 0) {
if (this.m < 0) {
return -1;
}
if (this.m > 0) {
return 1;
}
}
if (this.m > 0) {
if (decimal.m < 0) {
return 1;
}
if (this.e > decimal.e) {
return 1;
}
if (this.e < decimal.e) {
return -1;
}
if (this.m > decimal.m) {
return 1;
}
if (this.m < decimal.m) {
return -1;
}
return 0;
}
if (this.m < 0) {
if (decimal.m > 0) {
return -1;
}
if (this.e > decimal.e) {
return -1;
}
if (this.e < decimal.e) {
return 1;
}
if (this.m > decimal.m) {
return 1;
}
if (this.m < decimal.m) {
return -1;
}
return 0;
}
throw Error("Unreachable code");
};
Decimal.prototype.compare = function (value) {
return this.cmp(value);
};
Decimal.prototype.eq = function (value) {
var decimal = D(value);
return this.e === decimal.e && this.m === decimal.m;
};
Decimal.prototype.equals = function (value) {
return this.eq(value);
};
Decimal.prototype.neq = function (value) {
return !this.eq(value);
};
Decimal.prototype.notEquals = function (value) {
return this.neq(value);
};
Decimal.prototype.lt = function (value) {
var decimal = D(value);
if (this.m === 0) {
return decimal.m > 0;
}
if (decimal.m === 0) {
return this.m <= 0;
}
if (this.e === decimal.e) {
return this.m < decimal.m;
}
if (this.m > 0) {
return decimal.m > 0 && this.e < decimal.e;
}
return decimal.m > 0 || this.e > decimal.e;
};
Decimal.prototype.lte = function (value) {
return !this.gt(value);
};
Decimal.prototype.gt = function (value) {
var decimal = D(value);
if (this.m === 0) {
return decimal.m < 0;
}
if (decimal.m === 0) {
return this.m > 0;
}
if (this.e === decimal.e) {
return this.m > decimal.m;
}
if (this.m > 0) {
return decimal.m < 0 || this.e > decimal.e;
}
return decimal.m < 0 && this.e < decimal.e;
};
Decimal.prototype.gte = function (value) {
return !this.lt(value);
};
Decimal.prototype.max = function (value) {
var decimal = D(value);
return this.lt(decimal) ? decimal : this;
};
Decimal.prototype.min = function (value) {
var decimal = D(value);
return this.gt(decimal) ? decimal : this;
};
Decimal.prototype.clamp = function (min, max) {
return this.max(min).min(max);
};
Decimal.prototype.clampMin = function (min) {
return this.max(min);
};
Decimal.prototype.clampMax = function (max) {
return this.min(max);
};
Decimal.prototype.cmp_tolerance = function (value, tolerance) {
var decimal = D(value);
return this.eq_tolerance(decimal, tolerance) ? 0 : this.cmp(decimal);
};
Decimal.prototype.compare_tolerance = function (value, tolerance) {
return this.cmp_tolerance(value, tolerance);
};
/**
* Tolerance is a relative tolerance, multiplied by the greater of the magnitudes of the two arguments.
* For example, if you put in 1e-9, then any number closer to the
* larger number than (larger number)*1e-9 will be considered equal.
*/
Decimal.prototype.eq_tolerance = function (value, tolerance) {
var decimal = D(value); // https://stackoverflow.com/a/33024979
// return abs(a-b) <= tolerance * max(abs(a), abs(b))
return Decimal.lte(this.sub(decimal).abs(), Decimal.max(this.abs(), decimal.abs()).mul(tolerance));
};
Decimal.prototype.equals_tolerance = function (value, tolerance) {
return this.eq_tolerance(value, tolerance);
};
Decimal.prototype.neq_tolerance = function (value, tolerance) {
return !this.eq_tolerance(value, tolerance);
};
Decimal.prototype.notEquals_tolerance = function (value, tolerance) {
return this.neq_tolerance(value, tolerance);
};
Decimal.prototype.lt_tolerance = function (value, tolerance) {
var decimal = D(value);
return !this.eq_tolerance(decimal, tolerance) && this.lt(decimal);
};
Decimal.prototype.lte_tolerance = function (value, tolerance) {
var decimal = D(value);
return this.eq_tolerance(decimal, tolerance) || this.lt(decimal);
};
Decimal.prototype.gt_tolerance = function (value, tolerance) {
var decimal = D(value);
return !this.eq_tolerance(decimal, tolerance) && this.gt(decimal);
};
Decimal.prototype.gte_tolerance = function (value, tolerance) {
var decimal = D(value);
return this.eq_tolerance(decimal, tolerance) || this.gt(decimal);
};
Decimal.prototype.log10 = function () {
return this.e + Math.log10(this.m);
};
Decimal.prototype.absLog10 = function () {
return this.e + Math.log10(Math.abs(this.m));
};
Decimal.prototype.pLog10 = function () {
return this.m <= 0 || this.e < 0 ? 0 : this.log10();
};
Decimal.prototype.log = function (base) {
// UN-SAFETY: Most incremental game cases are log(number := 1 or greater, base := 2 or greater).
// We assume this to be true and thus only need to return a number, not a Decimal,
// and don't do any other kind of error checking.
return Math.LN10 / Math.log(base) * this.log10();
};
Decimal.prototype.log2 = function () {
return 3.321928094887362 * this.log10();
};
Decimal.prototype.ln = function () {
return 2.302585092994045 * this.log10();
};
Decimal.prototype.logarithm = function (base) {
return this.log(base);
};
Decimal.prototype.pow = function (value) {
// UN-SAFETY: Accuracy not guaranteed beyond ~9~11 decimal places.
// TODO: Decimal.pow(new Decimal(0.5), 0); or Decimal.pow(new Decimal(1), -1);
// makes an exponent of -0! Is a negative zero ever a problem?
var numberValue = value instanceof Decimal ? value.toNumber() : value; // TODO: Fast track seems about neutral for performance.
// It might become faster if an integer pow is implemented,
// or it might not be worth doing (see https://github.com/Patashu/break_infinity.js/issues/4 )
// Fast track: If (this.e*value) is an integer and mantissa^value
// fits in a Number, we can do a very fast method.
var temp = this.e * numberValue;
var newMantissa;
if (Number.isSafeInteger(temp)) {
newMantissa = Math.pow(this.m, numberValue);
if (isFinite(newMantissa) && newMantissa !== 0) {
return ME(newMantissa, temp);
}
} // Same speed and usually more accurate.
var newExponent = Math.trunc(temp);
var residue = temp - newExponent;
newMantissa = Math.pow(10, numberValue * Math.log10(this.m) + residue);
if (isFinite(newMantissa) && newMantissa !== 0) {
return ME(newMantissa, newExponent);
} // return Decimal.exp(value*this.ln());
var result = Decimal.pow10(numberValue * this.absLog10()); // this is 2x faster and gives same values AFAIK
if (this.sign() === -1) {
if (Math.abs(numberValue % 2) === 1) {
return result.neg();
} else if (Math.abs(numberValue % 2) === 0) {
return result;
}
return new Decimal(Number.NaN);
}
return result;
};
Decimal.prototype.pow_base = function (value) {
return D(value).pow(this);
};
Decimal.prototype.factorial = function () {
// Using Stirling's Approximation.
// https://en.wikipedia.org/wiki/Stirling%27s_approximation#Versions_suitable_for_calculators
var n = this.toNumber() + 1;
return Decimal.pow(n / Math.E * Math.sqrt(n * Math.sinh(1 / n) + 1 / (810 * Math.pow(n, 6))), n).mul(Math.sqrt(2 * Math.PI / n));
};
Decimal.prototype.exp = function () {
var x = this.toNumber(); // Fast track: if -706 < this < 709, we can use regular exp.
if (-706 < x && x < 709) {
return Decimal.fromNumber(Math.exp(x));
}
return Decimal.pow(Math.E, x);
};
Decimal.prototype.sqr = function () {
return ME(Math.pow(this.m, 2), this.e * 2);
};
Decimal.prototype.sqrt = function () {
if (this.m < 0) {
return new Decimal(Number.NaN);
}
if (this.e % 2 !== 0) {
return ME(Math.sqrt(this.m) * 3.16227766016838, Math.floor(this.e / 2));
} // Mod of a negative number is negative, so != means '1 or -1'
return ME(Math.sqrt(this.m), Math.floor(this.e / 2));
};
Decimal.prototype.cube = function () {
return ME(Math.pow(this.m, 3), this.e * 3);
};
Decimal.prototype.cbrt = function () {
var sign = 1;
var mantissa = this.m;
if (mantissa < 0) {
sign = -1;
mantissa = -mantissa;
}
var newMantissa = sign * Math.pow(mantissa, 1 / 3);
var mod = this.e % 3;
if (mod === 1 || mod === -1) {
return ME(newMantissa * 2.154434690031883, Math.floor(this.e / 3));
}
if (mod !== 0) {
return ME(newMantissa * 4.641588833612778, Math.floor(this.e / 3));
} // mod != 0 at this point means 'mod == 2 || mod == -2'
return ME(newMantissa, Math.floor(this.e / 3));
}; // Some hyperbolic trig functions that happen to be easy
Decimal.prototype.sinh = function () {
return this.exp().sub(this.negate().exp()).div(2);
};
Decimal.prototype.cosh = function () {
return this.exp().add(this.negate().exp()).div(2);
};
Decimal.prototype.tanh = function () {
return this.sinh().div(this.cosh());
};
Decimal.prototype.asinh = function () {
return Decimal.ln(this.add(this.sqr().add(1).sqrt()));
};
Decimal.prototype.acosh = function () {
return Decimal.ln(this.add(this.sqr().sub(1).sqrt()));
};
Decimal.prototype.atanh = function () {
if (this.abs().gte(1)) {
return Number.NaN;
}
return Decimal.ln(this.add(1).div(new Decimal(1).sub(this))) / 2;
};
/**
* Joke function from Realm Grinder
*/
Decimal.prototype.ascensionPenalty = function (ascensions) {
if (ascensions === 0) {
return this;
}
return this.pow(Math.pow(10, -ascensions));
};
/**
* Joke function from Cookie Clicker. It's 'egg'
*/
Decimal.prototype.egg = function () {
return this.add(9);
};
Decimal.prototype.lessThanOrEqualTo = function (other) {
return this.cmp(other) < 1;
};
Decimal.prototype.lessThan = function (other) {
return this.cmp(other) < 0;
};
Decimal.prototype.greaterThanOrEqualTo = function (other) {
return this.cmp(other) > -1;
};
Decimal.prototype.greaterThan = function (other) {
return this.cmp(other) > 0;
};
Decimal.prototype.decimalPlaces = function () {
return this.dp();
};
Decimal.prototype.dp = function () {
if (!isFinite(this.mantissa)) {
return NaN;
}
if (this.exponent >= MAX_SIGNIFICANT_DIGITS) {
return 0;
}
var mantissa = this.mantissa;
var places = -this.exponent;
var e = 1;
while (Math.abs(Math.round(mantissa * e) / e - mantissa) > ROUND_TOLERANCE) {
e *= 10;
places++;
}
return places > 0 ? places : 0;
};
Object.defineProperty(Decimal, "MAX_VALUE", {
get: function get() {
return MAX_VALUE;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Decimal, "MIN_VALUE", {
get: function get() {
return MIN_VALUE;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Decimal, "NUMBER_MAX_VALUE", {
get: function get() {
return NUMBER_MAX_VALUE;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Decimal, "NUMBER_MIN_VALUE", {
get: function get() {
return NUMBER_MIN_VALUE;
},
enumerable: false,
configurable: true
});
return Decimal;
}();
var MAX_VALUE = ME_NN(1, EXP_LIMIT);
var MIN_VALUE = ME_NN(1, -EXP_LIMIT);
var NUMBER_MAX_VALUE = D(Number.MAX_VALUE);
var NUMBER_MIN_VALUE = D(Number.MIN_VALUE);
return Decimal;
})));