"use strict";
const Integer = require("./integer");
const utils = require("../utils");
/** @module types */
/**
* Constructs an immutable arbitrary-precision signed decimal number.
* A `BigDecimal` consists of an [arbitrary precision integer]{@link module:types~Integer}
* <i>unscaled value</i> and a 32-bit integer <i>scale</i>. 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>.
* @class
* @classdesc 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`.
* @param {Integer|Number} unscaledValue The integer part of the decimal.
* @param {Number} scale The scale of the decimal.
* @constructor
*/
function BigDecimal(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}
*/
BigDecimal.fromBuffer = function (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}
*/
BigDecimal.toBuffer = function (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}
*/
BigDecimal.fromString = function (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}
*/
BigDecimal.fromNumber = function (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}
*/
BigDecimal.prototype.equals = function (other) {
return other instanceof BigDecimal && this.compare(other) === 0;
};
BigDecimal.prototype.inspect = function () {
return this.constructor.name + ": " + this.toString();
};
/**
* @param {BigDecimal} other
* @returns {boolean}
*/
BigDecimal.prototype.notEquals = function (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.
*/
BigDecimal.prototype.compare = function (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.
*/
BigDecimal.prototype.subtract = function (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 `BigDecimal`.
* @param {BigDecimal} other The BigDecimal to sum to this.
* @return {!BigDecimal} The BigDecimal result.
*/
BigDecimal.prototype.add = function (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}
*/
BigDecimal.prototype.greaterThan = function (other) {
return this.compare(other) === 1;
};
/** @return {boolean} Whether this value is negative. */
BigDecimal.prototype.isNegative = function () {
return this._intVal.isNegative();
};
/** @return {boolean} Whether this value is zero. */
BigDecimal.prototype.isZero = function () {
return this._intVal.isZero();
};
/**
* Returns the string representation of this `BigDecimal`
* @returns {string}
*/
BigDecimal.prototype.toString = function () {
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}
*/
BigDecimal.prototype.toNumber = function () {
return parseFloat(this.toString());
};
/**
* Returns the string representation.
* Method used by the native JSON.stringify() to serialize this instance.
*/
BigDecimal.prototype.toJSON = function () {
return this.toString();
};
module.exports = BigDecimal;