"use strict";
const Integer = require("./integer");
const utils = require("../utils");
/** @module types */
/**
* The `BigDecimal` class provides operations for
* arithmetic, scale manipulation, rounding, comparison and
* format conversion. The {@link #toString} method provides a
* canonical representation of a `BigDecimal`.
*/
class BigDecimal {
/**
* Constructs an immutable arbitrary-precision signed decimal number.
* A `BigDecimal` consists of an [arbitrary precision integer]{@link module:types~Integer}
* *unscaled value* and a 32-bit integer *scale*. If zero
* or positive, the scale is the number of digits to the right of the
* decimal point. If negative, the unscaled value of the number is
* multiplied by ten to the power of the negation of the scale. The
* value of the number represented by the `BigDecimal` is
* therefore <tt>(unscaledValue × 10<sup>-scale</sup>)</tt>.
* * A `BigDecimal` consists of an arbitrary precision integer
* *unscaled value* and a 32-bit integer *scale*. If zero
* or positive, the scale is the number of digits to the right of the
* decimal point. If negative, the unscaled value of the number is
* multiplied by ten to the power of the negation of the scale. The
* value of the number represented by the `BigDecimal` is
* therefore (unscaledValue × 10^(-scale)).
*
* @param {Integer|Number} unscaledValue The integer part of the decimal.
* @param {Number} scale The scale of the decimal.
*/
constructor(unscaledValue, scale) {
if (typeof unscaledValue === "number") {
unscaledValue = Integer.fromNumber(unscaledValue);
}
/**
* @type {Integer}
* @private
*/
this._intVal = unscaledValue;
/**
* @type {Number}
* @private
*/
this._scale = scale;
}
/**
* Returns the BigDecimal representation of a buffer composed of the scale (int32BE) and the unsigned value (varint BE)
* @param {Buffer} buf
* @returns {BigDecimal}
*/
static fromBuffer(buf) {
const scale = buf.readInt32BE(0);
const unscaledValue = Integer.fromBuffer(buf.slice(4));
return new BigDecimal(unscaledValue, scale);
}
/**
* Returns a buffer representation composed of the scale as a BE int 32 and the unsigned value as a BE varint
* @param {BigDecimal} value
* @returns {Buffer}
*/
static toBuffer(value) {
const unscaledValueBuffer = Integer.toBuffer(value._intVal);
const scaleBuffer = utils.allocBufferUnsafe(4);
scaleBuffer.writeInt32BE(value._scale, 0);
return Buffer.concat(
[scaleBuffer, unscaledValueBuffer],
scaleBuffer.length + unscaledValueBuffer.length,
);
}
/**
* Returns a BigDecimal representation of the string
* @param {String} value
* @returns {BigDecimal}
*/
static fromString(value) {
if (!value) {
throw new TypeError("Invalid null or undefined value");
}
value = value.trim();
const scaleIndex = value.indexOf(".");
let scale = 0;
if (scaleIndex >= 0) {
scale = value.length - 1 - scaleIndex;
value = value.substr(0, scaleIndex) + value.substr(scaleIndex + 1);
}
return new BigDecimal(Integer.fromString(value), scale);
}
/**
* Returns a BigDecimal representation of the Number
* @param {Number} value
* @returns {BigDecimal}
*/
static fromNumber(value) {
if (isNaN(value)) {
return new BigDecimal(Integer.ZERO, 0);
}
let textValue = value.toString();
if (textValue.indexOf("e") >= 0) {
// get until scale 20
textValue = value.toFixed(20);
}
return BigDecimal.fromString(textValue);
}
/**
* Returns true if the value of the BigDecimal instance and other are the same
* @param {BigDecimal} other
* @returns {Boolean}
*/
equals(other) {
return other instanceof BigDecimal && this.compare(other) === 0;
}
inspect() {
return this.constructor.name + ": " + this.toString();
}
/**
* @param {BigDecimal} other
* @returns {boolean}
*/
notEquals(other) {
return !this.equals(other);
}
/**
* Compares this BigDecimal with the given one.
* @param {BigDecimal} other Integer to compare against.
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
* if the given one is greater.
*/
compare(other) {
const diff = this.subtract(other);
if (diff.isNegative()) {
return -1;
}
if (diff.isZero()) {
return 0;
}
return +1;
}
/**
* Returns the difference of this and the given BigDecimal.
* @param {BigDecimal} other The BigDecimal to subtract from this.
* @return {!BigDecimal} The BigDecimal result.
*/
subtract(other) {
const first = this;
if (first._scale === other._scale) {
return new BigDecimal(
first._intVal.subtract(other._intVal),
first._scale,
);
}
let diffScale;
let unscaledValue;
if (first._scale < other._scale) {
// The scale of this is lower
diffScale = other._scale - first._scale;
// multiple this unScaledValue to compare in the same scale
unscaledValue = first._intVal
.multiply(Integer.fromNumber(Math.pow(10, diffScale)))
.subtract(other._intVal);
return new BigDecimal(unscaledValue, other._scale);
}
// The scale of this is higher
diffScale = first._scale - other._scale;
// multiple this unScaledValue to compare in the same scale
unscaledValue = first._intVal.subtract(
other._intVal.multiply(Integer.fromNumber(Math.pow(10, diffScale))),
);
return new BigDecimal(unscaledValue, first._scale);
}
/**
* Returns the sum of this and the given <code>BigDecimal</code>.
* @param {BigDecimal} other The BigDecimal to sum to this.
* @return {!BigDecimal} The BigDecimal result.
*/
add(other) {
const first = this;
if (first._scale === other._scale) {
return new BigDecimal(
first._intVal.add(other._intVal),
first._scale,
);
}
let diffScale;
let unscaledValue;
if (first._scale < other._scale) {
// The scale of this is lower
diffScale = other._scale - first._scale;
// multiple this unScaledValue to compare in the same scale
unscaledValue = first._intVal
.multiply(Integer.fromNumber(Math.pow(10, diffScale)))
.add(other._intVal);
return new BigDecimal(unscaledValue, other._scale);
}
// The scale of this is higher
diffScale = first._scale - other._scale;
// multiple this unScaledValue to compare in the same scale
unscaledValue = first._intVal.add(
other._intVal.multiply(Integer.fromNumber(Math.pow(10, diffScale))),
);
return new BigDecimal(unscaledValue, first._scale);
}
/**
* Returns true if the current instance is greater than the other
* @param {BigDecimal} other
* @returns {boolean}
*/
greaterThan(other) {
return this.compare(other) === 1;
}
/** @return {boolean} Whether this value is negative. */
isNegative() {
return this._intVal.isNegative();
}
/** @return {boolean} Whether this value is zero. */
isZero() {
return this._intVal.isZero();
}
/**
* Returns the string representation of this <code>BigDecimal</code>
* @returns {string}
*/
toString() {
let intString = this._intVal.toString();
if (this._scale === 0) {
return intString;
}
let signSymbol = "";
if (intString.charAt(0) === "-") {
signSymbol = "-";
intString = intString.substr(1);
}
let separatorIndex = intString.length - this._scale;
if (separatorIndex <= 0) {
// add zeros at the beginning, plus an additional zero
intString =
utils.stringRepeat("0", -separatorIndex + 1) + intString;
separatorIndex = intString.length - this._scale;
}
return (
signSymbol +
intString.substr(0, separatorIndex) +
"." +
intString.substr(separatorIndex)
);
}
/**
* Returns a Number representation of this `BigDecimal`.
* @returns {Number}
*/
toNumber() {
return parseFloat(this.toString());
}
/**
* Returns the string representation.
* Method used by the native JSON.stringify() to serialize this instance.
*/
toJSON() {
return this.toString();
}
}
module.exports = BigDecimal;