as-big
Version:
AssemblyScript library for arbitrary-precision decimal arithmetic.
1,100 lines (912 loc) • 30.2 kB
text/typescript
/**
* Representation of big decimals.
*
* All operations are immutable.
*/
export default class Big {
/**
* {Big} instance with the value zero {0}.
*/
static readonly ZERO: Big = Big.zero();
/**
* {Big} instance with the value one {1}.
*/
static readonly ONE: Big = Big.one();
/**
* {Big} instance with the value two {2}.
*/
static readonly TWO: Big = Big.two();
/**
* {Big} instance with the value ten {10}.
*/
static readonly TEN: Big = Big.ten();
/**
* {Big} instance with the value one half {0.5}.
*/
static readonly HALF: Big = Big.half();
/**
* The positive exponent (PE) at and above which {toString} returns exponential notation.
* 1000000 is the maximum recommended exponent value of a Big, but this limit is not enforced.
*/
static PE: i32 = 21;// 0 to 1000000
/**
* The negative exponent (NE) at and beneath which {toString} returns exponential notation.
* -1000000 is the minimum recommended exponent value of a Big.
*/
static NE: i32 = -7; // 0 to -1000000
/*
* The maximum number of decimal places (DP) of the results of operations involving division:
* div and sqrt, and pow with negative exponents.
*/
static DP: i32 = 20; // 0 to MAX_DP
// The maximum value of DP and Big.DP.
static readonly MAX_DP: i32 = 1000000; // 0 to 1000000
// The maximum magnitude of the exponent argument to the pow method.
static readonly MAX_POWER: i32 = 1000000; // 1 to 1000000
/**
* The default rounding mode (RM) used when rounding to the above decimal places.
*
* 0 Towards zero (i.e. truncate, no rounding). (ROUND_DOWN)
* 1 To nearest neighbour. If equidistant, round up. (ROUND_HALF_UP)
* 2 To nearest neighbour. If equidistant, to even. (ROUND_HALF_EVEN)
* 3 Away from zero. (ROUND_UP)
*/
static RM: u8 = 1; // 0, 1, 2 or 3
/**
* Towards zero (i.e. truncate, no rounding).
*/
static readonly ROUND_DOWN: u8 = 0;
/**
* To nearest neighbour. If equidistant, round up.
*/
static readonly ROUND_HALF_UP: u8 = 1;
/**
* To nearest neighbour. If equidistant, to even.
*/
static readonly ROUND_HALF_EVEN: u8 = 2;
/**
* Away from zero.
*/
static readonly ROUND_UP: u8 = 3;
/**
* Default contructor.
* See {#of} for other options.
*
* @param s // sign
* @param e // decimal point
* @param c // array of digits
*/
constructor(private s: i8, private e: i32, private c: Array<u8>) {
}
/**
* Returns a new {Big} instance from generic type {T}.
*
* @param n the number as {Big}, {string}, or {number}
* @return the new {Big} instance
*/
static of<T>(n: T): Big {
if (n instanceof Big) return n;
if (n instanceof string) return new BigOfString(n);
if (n instanceof i8) return Big.of(n.toString());
if (n instanceof u8) return Big.of(n.toString());
if (n instanceof i16) return Big.of(n.toString());
if (n instanceof u16) return Big.of(n.toString());
if (n instanceof i32) return Big.of(n.toString());
if (n instanceof u32) return Big.of(n.toString());
if (n instanceof i64) return Big.of(n.toString());
if (n instanceof u64) return Big.of(n.toString());
if (n instanceof f32) return Big.of(n.toString());
if (n instanceof f64) return Big.of(n.toString());
throw new TypeError('Unsupported generic type ' + nameof<T>(n));
}
/**
* Creates a new instance of {Big} from {Big} {x}.
*
* @param x the {Big} instance to make a copy from
*/
static copyOf(x: Big): Big {
return new Big(x.s, x.e, x.c.slice());
}
/**
* Creates a new instance of {Big} with the value {0} (zero).
*/
static zero(): Big {
const arr = new Array<u8>(1);
unchecked(arr[0] = 0);
return new Big(1, 0, arr);
}
/**
* Creates a new instance of {Big} with the value {1} (one).
*/
static one(): Big {
const arr = new Array<u8>(1);
unchecked(arr[0] = 1);
return new Big(1, 0, arr);
}
/**
* Creates a new instance of {Big} with the value {2} (two).
*/
static two(): Big {
const arr = new Array<u8>(1);
unchecked(arr[0] = 2);
return new Big(1, 0, arr);
}
/**
* Creates a new instance of {Big} with the value {10} (ten).
*/
static ten(): Big {
const arr = new Array<u8>(1);
unchecked(arr[0] = 1);
return new Big(1, 1, arr);
}
/**
* Creates a new instance of {Big} with the value {0.5} (half).
*/
static half(): Big {
const arr = new Array<u8>(1);
unchecked(arr[0] = 5);
return new Big(1, -1, arr);
}
/**
* Return a new instance of {Big} with the negative value of this Big.
*/
.prefix('-')
neg(): Big {
return new Big(-this.s, this.e, this.c.slice());
}
/**
* Return a new instance of {Big} with the value of this {Big}.
*/
.prefix('+')
pos(): Big {
return new Big(this.s, this.e, this.c.slice());
}
/**
* Return true if the value of this {Big} is equal to the value of {Big} {y}, otherwise return false.
* @param y the {y}
*/
//@operator('==')
eq<T>(y: T): boolean {
return this.cmp(y) == 0;
}
eqBig(y: Big): boolean {
return this.eq(y);
}
/**
* Return true if the value of this {Big} is not equal to the value of {Big} {y}, otherwise return false.
* @param y the {y}
*/
//@operator('!=')
neq<T>(y: T): boolean {
return this.cmp(y) != 0;
}
neqBig(y: Big): boolean {
return this.neq(y);
}
/**
* Returns true if the value of this {Big} is greater than the value of {Big} {y}, otherwise false.
* @param y the {y}
*/
//@operator('>')
gt<T>(y: T): boolean {
return this.cmp(y) > 0;
}
gtBig(y: Big): boolean {
return this.gt(y);
}
/**
* Returns true if the value of this {Big} is greater than or equal to the value of {Big} {y}, otherwise false.
* @param y the {y}
*/
//@operator('>=')
gte<T>(y: T): boolean {
return this.cmp(y) > -1;
}
gteBig(y: Big): boolean {
return this.gte(y);
}
/**
* Returns true if the value of this {Big} is less than the value of {Big} {y}, otherwise false.
* @param y the {y}
*/
//@operator('<')
lt<T>(y: T): boolean {
return this.cmp(y) < 0;
}
ltBig(y: Big): boolean {
return this.lt(y);
}
/**
* Returns true if the value of this {Big} is less than or equal to the value of {Big} {y}, otherwise false.
* @param y the {y}
*/
//@operator('<=')
lte<T>(y: T): boolean {
return this.cmp(y) < 1;
}
lteBig(y: Big): boolean {
return this.lte(y);
}
/**
* Returns 1 if the value of this {Big} is greater than the value of {Big} {y},
* -1 if the value of this {Big} is less than the value of {Big} {y}, or
* 0 if they have the same value.
* @param y the {y}
*/
cmp<T>(y: T): i8 {
const yb = y instanceof Big ? y : Big.of(y);
let xc = this.c,
yc = yb.c,
xs = this.s,
ys = yb.s,
xe = this.e,
ye = yb.e;
// either zero?
if (unchecked(!xc[0] || !yc[0])) return unchecked(!xc[0] ? !yc[0] ? 0 : -ys : xs);
// signs differ?
if (xs != ys) return xs;
const isneg = xs < 0;
// compare exponents
if (xe != ye) return xe > ye ^ isneg ? 1 : -1;
const e = (xe = xc.length) < (ye = yc.length) ? xe : ye;
// compare digit by digit
for (let i = -1; ++i < e;) {
if (unchecked(xc[i] != yc[i])) return unchecked(xc[i] > yc[i] ^ isneg ? 1 : -1);
}
// compare lengths
return xe == ye ? 0 : xe > ye ^ isneg ? 1 : -1;
};
cmpBig(y: Big): i8 {
return this.cmp(y);
}
/**
* Returns a new {Big} whose value is the absolute value of this {Big}.
*/
abs(): Big {
return new Big(1, this.e, this.c.slice());
}
/**
* Returns a new {Big} whose value is the value of this {Big} plus the value of {Big} {y}.
* @param y the {y}
*/
//@operator('+')
plus<T>(y: T): Big {
let yb = y instanceof Big ? Big.copyOf(y) : Big.of(y);
let e: i32, k: i32, t: Array<u8>,
x = this;
// signs differ?
if (x.s != yb.s) {
yb.s = -yb.s;
return x.minus(yb);
}
let xe = x.e,
xc = x.c,
ye = yb.e,
yc = yb.c;
// either zero?
if (unchecked(!xc[0] || !yc[0])) {
if (unchecked(!yc[0])) {
if (unchecked(xc[0])) {
yb = Big.copyOf(x);
} else {
yb.s = x.s;
}
}
return yb;
}
xc = xc.slice();
// prepend zeros to equalise exponents
// note: reverse faster than unshifts
if (e = xe - ye) {
if (e > 0) {
ye = xe;
t = yc;
} else {
e = -e;
t = xc;
}
t.reverse();
for (; e--;) t.push(0);
t.reverse();
}
// point xc to the longer array
if (xc.length - yc.length < 0) {
t = yc;
yc = xc;
xc = t;
}
e = yc.length;
let m: u8;
// only start adding at yc.length - 1 as the further digits of xc can be left as they are
for (m = 0; e; unchecked(xc[e] %= 10)) m = unchecked((xc[--e] = <u8>(xc[e] + yc[e] + m)) / 10 | 0);
// no need to check for zero, as +x + +y != 0 && -x + -y != 0
if (m) {
xc.unshift(m);
++ye;
}
// remove trailing zeros
for (e = xc.length; unchecked(xc[--e] === 0);) xc.pop();
yb.c = xc;
yb.e = ye;
return yb;
}
plusBig(y: Big): Big {
return this.plus(y);
}
/**
* Returns a new {Big} whose value is the value of this {Big} minus the value of {Big} {y}.
* @param y the {y}
*/
//@operator('-')
minus<T>(y: T): Big {
let yb = y instanceof Big ? Big.copyOf(y) : Big.of(y);
if (this.eq(yb)) return Big.ZERO;
let i: i32, j: i32, t: Array<u8>, xlty: i32,
x = this,
xs = x.s,
ys = yb.s;
// signs differ?
if (xs != ys) {
yb.s = -ys;
return x.plus(yb);
}
let xc = x.c.slice(),
xe = x.e,
yc = yb.c,
ye = yb.e;
// either zero?
if (unchecked(!xc[0] || !yc[0])) {
if (unchecked(yc[0])) {
yb.s = -ys;
} else if (unchecked(xc[0])) {
yb = Big.copyOf(x);
} else {
yb.s = 1;
}
return yb;
}
let a: i32, b: i32;
// determine which is the bigger number - prepend zeros to equalise exponents
if (a = xe - ye) {
if (xlty = a < 0) {
a = -a;
t = xc;
} else {
ye = xe;
t = yc;
}
t.reverse();
for (b = a; b--;) t.push(0);
t.reverse();
} else {
// exponents equal - check digit by digit
j = ((xlty = xc.length < yc.length) ? xc : yc).length;
for (a = b = 0; b < j; b++) {
if (unchecked(xc[b] != yc[b])) {
xlty = unchecked(xc[b] < yc[b]);
break;
}
}
}
// x < y? point xc to the array of the bigger number
if (xlty) {
t = xc;
xc = yc;
yc = t;
yb.s = -yb.s;
}
// append zeros to xc if shorter - no need to add zeros to yc if shorter as subtraction only needs to start at yc.length
if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0;
// subtract yc from xc
for (b = i; j > a;) {
if (unchecked(xc[--j] < yc[j])) {
for (i = j; i && unchecked(!xc[--i]);) unchecked(xc[i] = 9);
unchecked(--xc[i]);
unchecked(xc[j] += 10);
}
unchecked(xc[j] -= yc[j]);
}
// remove trailing zeros
for (; unchecked(xc[--b] === 0);) xc.pop();
// remove leading zeros and adjust exponent accordingly
for (; unchecked(xc[0] === 0);) {
xc.shift();
--ye;
}
if (unchecked(!xc[0])) {
// n - n = +0
yb.s = 1;
// result must be zero
xc = new Array<u8>(1);
unchecked(xc[0] = 0);
ye = 0;
}
yb.c = xc;
yb.e = ye;
return yb;
}
minusBig(y: Big): Big {
return this.minus(y);
}
/**
* Returns a new {Big} whose value is the value of this {Big} times the value of {Big} {y}.
* @param y the {y}
*/
//@operator('*')
times<T>(y: T): Big {
let yb = y instanceof Big ? Big.copyOf(y) : Big.of(y);
let c: Array<u8>,
x = this,
xc = x.c.slice(),
yc = yb.c,
a = xc.length,
b = yc.length,
i = x.e,
j = yb.e;
// determine sign of result
yb.s = x.s == yb.s ? 1 : -1;
// return signed 0 if either 0
if (unchecked(!xc[0] || !yc[0])) {
yb.c = new Array<u8>(1);
unchecked(yb.c[0] = 0);
yb.e = 0;
return yb;
}
// initialise exponent of result as x.e + y.e
yb.e = i + j;
// if array xc has fewer digits than yc, swap xc and yc, and lengths
if (a < b) {
c = xc;
xc = yc;
yc = c;
j = a;
a = b;
b = j;
}
// initialise coefficient array of result with zeros
for (c = new Array<u8>(j = a + b); j--;) unchecked(c[j] = 0);
// Multiply
// i is initially xc.length
for (i = b; i--;) {
b = 0;
// a is yc.length
for (j = a + i; j > i;) {
// current sum of products at this digit position, plus carry
b = unchecked(c[j] + yc[i] * xc[j - i - 1] + b);
unchecked(c[j--] = u8(b % 10));
// carry
b = b / 10 | 0;
}
unchecked(c[j] = b as u8);
}
// increment result exponent if there is a final carry, otherwise remove leading zero
if (b) ++yb.e;
else c.shift();
// remove trailing zeros
for (i = c.length; unchecked(!c[--i]);) c.pop();
yb.c = c;
return yb;
}
timesBig(y: Big): Big {
return this.times(y);
}
/**
* Returns a new {Big} whose value is the value of this {Big} divided by the value of {Big} {y}, rounded,
* if necessary, to a maximum of {Big.DP} decimal places using rounding mode {Big.RM}.
* @param y the {y}
*/
//@operator('/')
div<T>(y: T): Big {
let yb = y instanceof Big ? Big.copyOf(y) : Big.of(y);
var x = this,
a = x.c, // dividend
b = yb.c, // divisor
k: i8 = x.s == yb.s ? 1 : -1,
dp = Big.DP;
// divisor is zero?
if (unchecked(!b[0])) {
throw new Error('Division by zero');
}
// dividend is 0? return +-0
if (unchecked(!a[0])) {
yb.s = k;
yb.c = new Array<u8>(1);
unchecked(yb.c[0] = 0);
yb.e = 0
return yb;
}
let bl: i32, cmp: i32, ri: i32,
bz = b.slice(),
ai = bl = b.length,
al = a.length,
r = a.slice(0, bl), // remainder
rl = r.length,
q = yb, // quotient
qc = q.c = [],
qi = 0,
p = dp + (q.e = x.e - yb.e) + 1; // precision of the result
q.s = k;
let m = p < 0 ? 0 : p;
// create version of divisor with leading zero
bz.unshift(0);
// add zeros to make remainder as long as divisor
for (; rl++ < bl;) r.push(0);
cmp = 0;
let n: u8;
do {
// n is how many times the divisor goes into current remainder
for (n = 0; n < 10; n++) {
// compare divisor and remainder
if (bl != (rl = r.length)) {
cmp = bl > rl ? 1 : -1;
} else {
for (ri = -1, cmp = 0; ++ri < bl;) {
if (unchecked(b[ri] != r[ri])) {
cmp = unchecked(b[ri] > r[ri]) ? 1 : -1;
break;
}
}
}
// if divisor < remainder, subtract divisor from remainder
if (cmp < 0) {
// remainder can't be more than 1 digit longer than divisor
// equalise lengths using divisor with extra leading zero?
for (let ct = rl == bl ? b : bz; rl;) {
if (unchecked(r[--rl] < ct[rl])) {
ri = rl;
for (; ri && unchecked(!r[--ri]);) unchecked(r[ri] = 9);
unchecked(--r[ri]);
unchecked(r[rl] += 10);
}
unchecked(r[rl] -= ct[rl]);
}
for (; unchecked(!r[0]);) r.shift();
} else {
break;
}
}
// add the digit n to the result array
qc[qi++] = cmp ? n : ++n;
// update the remainder.
if (unchecked(r[0]) && cmp) r[rl] = a.length > ai ? unchecked(a[ai]) : 0;
else {
r = new Array<u8>(1);
unchecked(r[0] = a.length > ai ? a[ai] : 0);
}
} while ((ai++ < al || r.length >= 0) && m-- > 0);
// leading zero? Do not remove if result is simply zero (qi == 1)
if (unchecked(!qc[0]) && qi != 1) {
// there can't be more than one zero
qc.shift();
q.e--;
p--;
}
// round?
if (qi > p) {
return this.__round(q, p, Big.RM, r.length >= 0);
}
return q;
}
divBig(y: Big): Big {
return this.div(y);
}
/**
* Returns a new {Big} whose value is the value of this {Big} modulo the value of {Big} {y}.
* @param y the {y}
*/
//@operator('%')
mod<T>(y: T): Big {
let x = Big.copyOf(this),
yb = y instanceof Big ? Big.copyOf(y) : Big.of(y);
if (unchecked(!yb.c[0])) {
throw new Error('Division by zero');
}
const xs = x.s,
ys = yb.s;
x.s = yb.s = 1;
const ygtx = yb.cmp(x) == 1;
x.s = xs;
yb.s = ys;
if (ygtx) return x;
const a = Big.DP,
b = Big.RM;
Big.DP = Big.RM = 0;
x = x.div(yb);
Big.DP = a;
Big.RM = b;
return this.minus(x.times(yb));
}
modBig(y: Big): Big {
return this.mod(y);
}
/**
* Returns a {Big} whose value is the value of this {Big} raised to the power {n}.
* If {n} is negative, round to a maximum of {Big.DP} decimal places using rounding mode {Big.RM}.
*
* @param n {i32}, {-MAX_POWER} to {MAX_POWER} inclusive.
*/
pow(n: i32): Big {
let x = this,
one = Big.ONE,
y = one,
isneg = n < 0;
if (n !== ~~n || n < -Big.MAX_POWER || n > Big.MAX_POWER) {
throw new Error('Invalid exponent ' + n.toString());
}
if (isneg) n = -n;
for (; ;) {
if (n & 1) y = y.times(x);
n >>= 1;
if (!n) break;
x = x.times(x);
}
return isneg ? one.div(y) : y;
}
sqrt(): Big {
let x = this,
e = x.e;
// zero?
if (unchecked(!x.c[0])) return Big.ZERO;
// negative?
if (x.s < 0) {
throw new Error('No square root for negative numbers ' + this.toString());
}
// estimate // TODO doesn't work well with built-in Math.sqrt
// let f = F64.parseFloat(x.toString());
// if (this.__validF64(x, f)) {
// return Big.of(Math.sqrt(f)).round(Big.DP);
// }
let r = x,
t = r;
e = r.e + (Big.DP += 4);
// Newton-Raphson iteration
do {
t = r;
r = t.plus(x.div(t)).times(Big.HALF).round(Big.DP);
} while (t.c.slice(0, e).join('') != r.c.slice(0, e).join(''));
return this.__round(Big.copyOf(r), (Big.DP -= 4) + r.e + 1);
}
/**
* Returns a new {Big} whose value is the value of this {Big} rounded to a maximum precision of {sd}
* significant digits using rounding mode {rm}, or {Big.RM} if {rm} is not specified.
*
* @param sd {i32} Significant digits: {1} to {MAX_DP} inclusive.
* @param rm? {u8} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
*/
prec(sd: i32, rm: u8 = Big.RM): Big {
if (sd !== ~~sd || sd < 1 || sd > Big.MAX_DP) {
throw new Error('Invalid precision ' + sd.toString());
}
return this.__round(Big.copyOf(this), sd, rm);
}
/**
* Returns a new {Big} whose value is the value of this {Big} rounded to a maximum of {dp} decimal places
* using rounding mode {rm}, or {Big.RM} if rm is not specified.
* If {dp} is negative, round to an integer which is a multiple of {10**-dp}.
* If {dp} is not specified, round to {0} decimal places.
*
* @param dp? {i32}, {-MAX_DP} to {MAX_DP} inclusive
* @param rm? {u8} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up)
*/
round(dp: i32 = 0, rm: u8 = Big.RM): Big {
if (dp !== ~~dp || dp < -Big.MAX_DP || dp > Big.MAX_DP) {
throw new Error('Invalid decimal places ' + dp.toString());
}
return this.__round(Big.copyOf(this), dp + this.e + 1, rm);
}
/**
* Return a string representing the value of this {Big} in exponential notation rounded to {dp} fixed
* decimal places using rounding mode {rm}, or {Big.RM} if rm is not specified.
*
* @param dp? {i32}, {-MAX_DP} to {MAX_DP} inclusive
* @param rm? {u8} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up)
*/
toExponential(dp: i32 = 0, rm: u8 = Big.RM): string {
let x = this,
n = unchecked(x.c[0]);
if (dp !== ~~dp || dp < 0 || dp > Big.MAX_DP) {
throw new Error('Invalid decimal places ' + dp.toString());
}
x = this.__round(Big.copyOf(x), ++dp, rm);
for (; x.c.length < dp;) x.c.push(0);
return x.__stringify(true, !!n);
}
/**
* Converts this {Big} instance to {f64}.
*/
toF64(): f64 {
// check if conversion is possible
const s = this.toString();
const n = F64.parseFloat(s);
if (!this.__validF64(this, n)) {
throw new RangeError('Out of float64 range: ' + s);
}
return n;
}
/**
* See {this.toF64}.
*/
toNumber(): number {
return this.toF64();
}
/**
* Converts this {Big} instance to {string}.
*
* @param radix currently ignored here
*/
toString(radix: number = 10): string {
if (radix && radix != 10) {
throw new Error('Currently only radix 10 is supported: ' + radix.toString());
}
return this.__stringify(this.e <= Big.NE || this.e >= Big.PE, unchecked(!!this.c[0]));
}
// Mutates the instance {x}.
__round(x: Big, sd: i32 = 0, rm: u8 = Big.RM, more: boolean = false): Big {
let xc = x.c;
if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) {
throw new Error('Invalid rounding mode ' + rm.toString());
}
if (sd < 1) {
more = unchecked(
rm === 3 && (more || !!xc[0]) || sd === 0 && (
rm === 1 && xc[0] >= 5 ||
rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc.length > 1))
)
);
xc.length = 1;
if (more) {
// 1, 0.1, 0.01, 0.001, 0.0001 etc.
x.e = x.e - sd + 1;
unchecked(xc[0] = 1);
} else {
// zero
unchecked(xc[0] = 0);
x.e = 0;
}
} else if (sd < xc.length) {
// xc[sd] is the digit after the digit that may be rounded up.
more = unchecked(
rm === 1 && xc[sd] >= 5 ||
rm === 2 && (xc[sd] > 5 || xc[sd] === 5 &&
(more || xc.length > sd + 1 || xc[sd - 1] & 1)) ||
rm === 3 && (more || !!xc[0])
);
// remove any digits after the required precision
xc.length = sd--;
// round up?
if (more) {
// rounding up may mean the previous digit has to be rounded up
for (; sd >= 0 && unchecked(++xc[sd] > 9);) {
unchecked(xc[sd] = 0);
if (!sd--) {
++x.e;
xc.unshift(1);
}
}
}
// remove trailing zeros
for (sd = xc.length; --sd >= 0 && unchecked(!xc[sd]);) xc.pop();
}
return x;
}
__stringify(doExponential: boolean, isNonzero: boolean): string {
let e = this.e;
let str = this.c.join(''),
len = str.length;
if (doExponential) {
str = str.charAt(0) + (len > 1 ? '.' + str.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e.toString();
} else if (e < 0) {
for (; ++e;) str = '0' + str;
str = '0.' + str;
} else if (e > 0) {
if (++e > len) {
for (e -= len; e--;) str += '0';
} else if (e < len) {
str = str.slice(0, e) + '.' + str.slice(e);
}
} else if (len > 1) {
str = str.charAt(0) + '.' + str.slice(1);
}
return this.s < 0 && isNonzero ? '-' + str : str;
}
__validF64(x: Big, n: f64): boolean {
return x == Big.of(n);
}
}
/**
* String decorator for creating a new {Big} from a {string}.
*/
class BigOfString extends Big {
/**
* Constructs from a {string}.
* @param n the value as {string}
*/
constructor(n: string) {
let xs: i8;
let xe: i32;
let xc: Array<u8>;
let i: i32 = 0, e: i32 = 0;
n = n.toLowerCase();
// valid number?
BigOfString.validate(n);
// determine sign
if (n.charAt(0) == '+') n = n.slice(1);
xs = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;
// decimal point?
if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');
// exponential form?
if ((i = n.indexOf('e')) > 0) {
if (e < 0) e = i;
e += I32.parseInt(n.slice(i + 1));
n = n.substring(0, i);
} else if (e < 0) {
e = n.length;
}
let len = n.length;
// determine leading zeros
for (i = 0; i < len && n.charAt(i) == '0';) ++i;
// zero
if (i === len) {
xc = new Array<u8>(1);
unchecked(xc[0] = 0);
xe = 0;
xs = 1;
} else {
// determine trailing zeros
for (; len > 0 && n.charAt(--len) == '0';);
xe = e - i - 1;
xc = new Array<u8>(len - i + 1);
// convert string to array of digits without leading/trailing zeros.
for (e = 0; i <= len;) unchecked(xc[e++] = U8.parseInt(n.charAt(i++)));
}
super(xs, xe, xc);
}
/**
* Validates the {string} to be a valid number format.
*
* @param n the number candidate as {string}
* @throws {TypeError} when not valid
*/
static validate(n: string): void {
let e = false, enow = false, nums = false, point = false;
for (let i = 0; i < n.length; i++) {
const c = n.charAt(i);
if ((c == '-' || c == '+') && (i == 0 || enow)) {
continue;
}
if (c == '.' && !point && !e) {
point = true;
continue;
}
if (c == 'e' && nums && !e) {
e = true;
enow = true;
continue;
}
const x = U8.parseInt(c);
if (x == 0 && c != '0' || !Number.isFinite(x)) {
throw new TypeError('Invalid value `' + c + '` in ' + n);
}
enow = false;
nums = true;
}
}
}