decimal128
Version:
Partial implementation of IEEE 754 Decimal128 decimal floating-point numbers
280 lines (276 loc) • 9.67 kB
JavaScript
'use strict';
var common = require('./common.cjs');
const zero = BigInt(0);
const one = BigInt(1);
const minusOne = BigInt(-1);
const ten = BigInt(10);
function gcd(a, b) {
while (b !== zero) {
let t = b;
b = a % b;
a = t;
}
return a;
}
function* nextDigitForDivision(x, y, n) {
let result = "";
let emittedDecimalPoint = false;
let done = false;
while (!done && common.countFractionalDigits(result) < n) {
if (x === zero) {
done = true;
}
else if (x < y) {
if (emittedDecimalPoint) {
x = x * ten;
if (x < y) {
// look ahead: are we still a power of 10 behind?
result = result + "0";
yield 0;
}
}
else {
emittedDecimalPoint = true;
result = (result === "" ? "0" : result) + ".";
x = x * ten;
yield -1;
if (x < y) {
// look ahead: are we still a power of 10 behind?
result = result + "0";
yield 0;
}
}
}
else {
let q = x / y;
x = x % y;
let qString = q.toString();
result = result + qString;
for (let i = 0; i < qString.length; i++) {
yield parseInt(qString.charAt(i));
}
}
}
return 0;
}
class Rational {
constructor(p, q) {
if (q === zero) {
throw new RangeError("Cannot construct rational whose denominator is zero");
}
let num = p;
let den = q;
let neg = false;
if (p < zero) {
if (q < zero) {
num = -p;
den = -q;
}
else {
num = -p;
neg = true;
}
}
else if (q < zero) {
den = -q;
neg = true;
}
let g = gcd(num, den);
this.numerator = num / g;
this.denominator = den / g;
this.isNegative = neg;
}
toString() {
return `${this.isNegative ? "-" : ""}${this.numerator}/${this.denominator}`;
}
static fromString(s) {
if (s.match(/^-/)) {
return Rational.fromString(s.substring(1)).negate();
}
if (s.match(/^[0-9]+$/)) {
return new Rational(BigInt(s), 1n);
}
if (s.match(/^[0-9]+[eE][+-]?[0-9]+$/)) {
let [num, exp] = s.split(/[eE]/);
let originalRat = new Rational(BigInt(num), 1n);
return originalRat.scale10(Number(exp));
}
if (s.match(/[.]/)) {
let [whole, decimal] = s.split(".");
if (decimal.match(/[eE]/)) {
let [dec, exp] = decimal.split(/[eE]/);
let originalRat = Rational.fromString(`${whole}.${dec}`);
return originalRat.scale10(Number(exp));
}
let numerator = BigInt(whole + decimal);
let denominator = ten ** BigInt(decimal.length);
return new Rational(numerator, denominator);
}
throw new SyntaxError(`Invalid rational number string: ${s}`);
}
scale10(n) {
if (this.isNegative) {
return this.negate().scale10(n).negate();
}
if (n === 0) {
return this;
}
if (n < 0) {
return new Rational(this.numerator, this.denominator * ten ** BigInt(0 - n));
}
return new Rational(this.numerator * ten ** BigInt(n), this.denominator);
}
negate() {
if (this.isNegative) {
return new Rational(this.numerator, this.denominator);
}
return new Rational(this.numerator * minusOne, this.denominator);
}
static _add(x, y) {
if (x.isNegative) {
return Rational._subtract(y, x.negate());
}
if (y.isNegative) {
return Rational._subtract(x, y.negate());
}
return new Rational(x.numerator * y.denominator + y.numerator * x.denominator, x.denominator * y.denominator);
}
static _subtract(x, y) {
if (x.isNegative) {
return Rational._add(x.negate(), y).negate();
}
return new Rational(x.numerator * y.denominator - y.numerator * x.denominator, x.denominator * y.denominator);
}
static _multiply(x, y) {
return new Rational(x.numerator * y.numerator, x.denominator * y.denominator);
}
static add(...theArgs) {
return theArgs.reduce((acc, cur) => Rational._add(acc, cur), new Rational(zero, one));
}
static subtract(x, ...theArgs) {
return theArgs.reduce((acc, cur) => Rational._subtract(acc, cur), x);
}
static multiply(...theArgs) {
return theArgs.reduce((acc, cur) => Rational._multiply(acc, cur), new Rational(one, one));
}
toFixed(n) {
if (n !== Infinity && !Number.isInteger(n)) {
throw new TypeError("Cannot enumerate a non-integer number of decimal places");
}
if (n < 0) {
throw new RangeError("Cannot enumerate a negative number of decimal places");
}
if (this.isNegative) {
return "-" + this.negate().toFixed(n);
}
if (this.numerator === zero) {
if (Infinity === n) {
throw new RangeError("Cannot enumerate infinite decimal places of zero");
}
return "0" + "." + "0".repeat(n);
}
let digitGenerator = nextDigitForDivision(this.numerator, this.denominator, n);
let digit = digitGenerator.next();
let result = "";
while (!digit.done) {
let v = digit.value;
if (-1 === v) {
result = ("" === result ? "0" : result) + ".";
}
else {
result = result + `${v}`;
}
digit = digitGenerator.next();
}
if (Infinity === n) {
return result;
}
let numFractionalDigits = common.countFractionalDigits(result);
if (numFractionalDigits >= n) {
return result;
}
let numZeroesNeeded = n - numFractionalDigits;
let zeroesNeeded = "0".repeat(numZeroesNeeded);
if (result.match(/[.]/)) {
return result + zeroesNeeded;
}
return result + "." + zeroesNeeded;
}
static roundHalfEven(initialPart, penultimateDigit, finalDigit, quantum) {
if (finalDigit < 5) {
return initialPart;
}
if (finalDigit > 5) {
return Rational.add(initialPart, initialPart.isNegative ? quantum.negate() : quantum);
}
if (penultimateDigit % 2 === 0) {
return initialPart;
}
return Rational.add(initialPart, initialPart.isNegative ? quantum.negate() : quantum);
}
static roundHalfExpand(initialPart, penultimateDigit, finalDigit, quantum) {
if (finalDigit < 5) {
return initialPart;
}
return Rational.add(initialPart, initialPart.isNegative ? quantum.negate() : quantum);
}
static roundCeil(initialPart, penultimateDigit, finalDigit, quantum) {
if (initialPart.isNegative) {
return initialPart;
}
if (finalDigit === 0) {
return initialPart;
}
return Rational.add(initialPart, quantum);
}
static roundFloor(initialPart, penultimateDigit, finalDigit, quantum) {
if (initialPart.isNegative) {
return Rational.subtract(initialPart, quantum);
}
return initialPart;
}
round(numFractionalDigits, mode) {
if (numFractionalDigits < 0) {
throw new RangeError("Cannot round to negative number of decimal places");
}
let s = this.toFixed(numFractionalDigits + 1);
let [integerPart, fractionalPart] = s.split(".");
let quantum = Rational.fromString(numFractionalDigits === 0
? "1"
: "0" + "." + "0".repeat(numFractionalDigits - 1) + "1");
let truncated = Rational.fromString(integerPart + "." + fractionalPart.substring(0, numFractionalDigits));
let penultimateDigit = parseInt(numFractionalDigits === 0
? integerPart.charAt(integerPart.length - 1)
: fractionalPart.charAt(numFractionalDigits - 1));
let finalDigit = parseInt(fractionalPart.charAt(numFractionalDigits));
if (mode === common.ROUNDING_MODE_TRUNCATE) {
return truncated;
}
if (mode === common.ROUNDING_MODE_HALF_EVEN) {
return Rational.roundHalfEven(truncated, penultimateDigit, finalDigit, quantum);
}
if (mode === common.ROUNDING_MODE_CEILING) {
return Rational.roundCeil(truncated, penultimateDigit, finalDigit, quantum);
}
if (mode === common.ROUNDING_MODE_FLOOR) {
return Rational.roundFloor(truncated, penultimateDigit, finalDigit, quantum);
}
return Rational.roundHalfExpand(truncated, penultimateDigit, finalDigit, quantum);
}
cmp(x) {
let a = (this.isNegative ? minusOne : one) * this.numerator * x.denominator;
let b = (x.isNegative ? minusOne : one) * x.numerator * this.denominator;
if (a < b) {
return -1;
}
if (b < a) {
return 1;
}
return 0;
}
isZero() {
return this.numerator === zero;
}
}
exports.Rational = Rational;
//# sourceMappingURL=Rational.cjs.map