bigdecimal-string 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/dist/index.d.mts +289 -0
- package/dist/index.d.ts +289 -0
- package/dist/index.js +601 -0
- package/dist/index.mjs +574 -0
- package/package.json +51 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var RoundingMode = /* @__PURE__ */ ((RoundingMode2) => {
|
|
3
|
+
RoundingMode2["CEILING"] = "CEILING";
|
|
4
|
+
RoundingMode2["FLOOR"] = "FLOOR";
|
|
5
|
+
RoundingMode2["DOWN"] = "DOWN";
|
|
6
|
+
RoundingMode2["UP"] = "UP";
|
|
7
|
+
RoundingMode2["HALF_EVEN"] = "HALF_EVEN";
|
|
8
|
+
RoundingMode2["HALF_UP"] = "HALF_UP";
|
|
9
|
+
RoundingMode2["HALF_DOWN"] = "HALF_DOWN";
|
|
10
|
+
return RoundingMode2;
|
|
11
|
+
})(RoundingMode || {});
|
|
12
|
+
var DEFAULT_PRECISION = 2;
|
|
13
|
+
var DEFAULT_ROUNDING_MODE = "HALF_UP" /* HALF_UP */;
|
|
14
|
+
var BigDecimal = class _BigDecimal {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new BigDecimal instance
|
|
17
|
+
* @param value - The value to create from (string, number, bigint, or another BigDecimal)
|
|
18
|
+
* @param precision - Number of decimal places (default: auto-detected or 2)
|
|
19
|
+
*/
|
|
20
|
+
constructor(value, precision) {
|
|
21
|
+
if (value === null || value === void 0 || value === "") {
|
|
22
|
+
this.unscaledValue = 0n;
|
|
23
|
+
this.scale = precision ?? DEFAULT_PRECISION;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (value instanceof _BigDecimal) {
|
|
27
|
+
if (precision !== void 0 && precision !== value.scale) {
|
|
28
|
+
const adjusted = value.setScale(precision);
|
|
29
|
+
this.unscaledValue = adjusted.unscaledValue;
|
|
30
|
+
this.scale = adjusted.scale;
|
|
31
|
+
} else {
|
|
32
|
+
this.unscaledValue = value.unscaledValue;
|
|
33
|
+
this.scale = value.scale;
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const parsed = _BigDecimal.parse(value, precision);
|
|
38
|
+
this.unscaledValue = parsed.unscaledValue;
|
|
39
|
+
this.scale = parsed.scale;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a value into unscaled bigint and scale
|
|
43
|
+
*/
|
|
44
|
+
static parse(value, precision) {
|
|
45
|
+
if (typeof value === "bigint") {
|
|
46
|
+
const scale = precision ?? DEFAULT_PRECISION;
|
|
47
|
+
return {
|
|
48
|
+
unscaledValue: value * _BigDecimal.powerOf10(scale),
|
|
49
|
+
scale
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
let str = typeof value === "number" ? value.toString() : value;
|
|
53
|
+
if (/[eE]/.test(str)) {
|
|
54
|
+
str = _BigDecimal.scientificToPlain(str);
|
|
55
|
+
}
|
|
56
|
+
const isNegative = str.startsWith("-");
|
|
57
|
+
if (isNegative || str.startsWith("+")) {
|
|
58
|
+
str = str.slice(1);
|
|
59
|
+
}
|
|
60
|
+
const [intPart, decPart = ""] = str.split(/[.,]/);
|
|
61
|
+
const detectedScale = decPart.length;
|
|
62
|
+
const targetScale = precision ?? Math.max(detectedScale, DEFAULT_PRECISION);
|
|
63
|
+
let cleanIntPart = intPart.replace(/^0+/, "") || "0";
|
|
64
|
+
let adjustedDecPart;
|
|
65
|
+
if (decPart.length < targetScale) {
|
|
66
|
+
adjustedDecPart = decPart.padEnd(targetScale, "0");
|
|
67
|
+
} else if (decPart.length > targetScale) {
|
|
68
|
+
adjustedDecPart = decPart.slice(0, targetScale);
|
|
69
|
+
const nextDigit = parseInt(decPart[targetScale] || "0", 10);
|
|
70
|
+
if (nextDigit >= 5) {
|
|
71
|
+
if (targetScale === 0) {
|
|
72
|
+
cleanIntPart = (BigInt(cleanIntPart) + 1n).toString();
|
|
73
|
+
} else {
|
|
74
|
+
const rounded = BigInt(adjustedDecPart || "0") + 1n;
|
|
75
|
+
const roundedStr = rounded.toString();
|
|
76
|
+
if (roundedStr.length > targetScale) {
|
|
77
|
+
cleanIntPart = (BigInt(cleanIntPart) + 1n).toString();
|
|
78
|
+
adjustedDecPart = "0".repeat(targetScale);
|
|
79
|
+
} else {
|
|
80
|
+
adjustedDecPart = roundedStr.padStart(targetScale, "0");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
adjustedDecPart = decPart;
|
|
86
|
+
}
|
|
87
|
+
const combined = targetScale > 0 ? cleanIntPart + adjustedDecPart : cleanIntPart;
|
|
88
|
+
let unscaledValue = BigInt(combined);
|
|
89
|
+
if (isNegative) {
|
|
90
|
+
unscaledValue = -unscaledValue;
|
|
91
|
+
}
|
|
92
|
+
return { unscaledValue, scale: targetScale };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Convert scientific notation to plain decimal string
|
|
96
|
+
*/
|
|
97
|
+
static scientificToPlain(sci) {
|
|
98
|
+
if (!/[eE]/.test(sci)) return sci;
|
|
99
|
+
const [coefficient, expPart] = sci.toLowerCase().split("e");
|
|
100
|
+
const exponent = parseInt(expPart, 10);
|
|
101
|
+
const isNegative = coefficient.startsWith("-");
|
|
102
|
+
const cleanCoef = coefficient.replace(/^[-+]/, "");
|
|
103
|
+
const [intPart, fracPart = ""] = cleanCoef.split(".");
|
|
104
|
+
const digits = intPart + fracPart;
|
|
105
|
+
const sign = isNegative ? "-" : "";
|
|
106
|
+
if (exponent >= 0) {
|
|
107
|
+
const totalIntDigits = intPart.length + exponent;
|
|
108
|
+
if (totalIntDigits >= digits.length) {
|
|
109
|
+
return sign + digits + "0".repeat(totalIntDigits - digits.length);
|
|
110
|
+
}
|
|
111
|
+
return sign + digits.slice(0, totalIntDigits) + "." + digits.slice(totalIntDigits);
|
|
112
|
+
}
|
|
113
|
+
const zerosNeeded = Math.abs(exponent) - intPart.length;
|
|
114
|
+
if (zerosNeeded >= 0) {
|
|
115
|
+
return sign + "0." + "0".repeat(zerosNeeded) + digits;
|
|
116
|
+
}
|
|
117
|
+
const splitPoint = intPart.length + exponent;
|
|
118
|
+
return sign + digits.slice(0, splitPoint) + "." + digits.slice(splitPoint);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Calculate 10^n as bigint
|
|
122
|
+
*/
|
|
123
|
+
static powerOf10(n) {
|
|
124
|
+
if (n < 0) throw new Error("Power must be non-negative");
|
|
125
|
+
return 10n ** BigInt(n);
|
|
126
|
+
}
|
|
127
|
+
// ============================================
|
|
128
|
+
// ARITHMETIC OPERATIONS (Chainable)
|
|
129
|
+
// ============================================
|
|
130
|
+
/**
|
|
131
|
+
* Add another value to this BigDecimal
|
|
132
|
+
* @returns A new BigDecimal with the result
|
|
133
|
+
*/
|
|
134
|
+
add(other) {
|
|
135
|
+
const otherBd = other instanceof _BigDecimal ? other : new _BigDecimal(other, this.scale);
|
|
136
|
+
const [a, b] = _BigDecimal.alignScales(this, otherBd);
|
|
137
|
+
return _BigDecimal.fromUnscaled(a.unscaledValue + b.unscaledValue, a.scale);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Subtract another value from this BigDecimal
|
|
141
|
+
* @returns A new BigDecimal with the result
|
|
142
|
+
*/
|
|
143
|
+
subtract(other) {
|
|
144
|
+
const otherBd = other instanceof _BigDecimal ? other : new _BigDecimal(other, this.scale);
|
|
145
|
+
const [a, b] = _BigDecimal.alignScales(this, otherBd);
|
|
146
|
+
return _BigDecimal.fromUnscaled(a.unscaledValue - b.unscaledValue, a.scale);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Alias for subtract
|
|
150
|
+
*/
|
|
151
|
+
minus(other) {
|
|
152
|
+
return this.subtract(other);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Alias for add
|
|
156
|
+
*/
|
|
157
|
+
plus(other) {
|
|
158
|
+
return this.add(other);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Multiply this BigDecimal by another value
|
|
162
|
+
* @returns A new BigDecimal with the result
|
|
163
|
+
*/
|
|
164
|
+
multiply(other) {
|
|
165
|
+
const otherBd = other instanceof _BigDecimal ? other : new _BigDecimal(other, this.scale);
|
|
166
|
+
const rawResult = this.unscaledValue * otherBd.unscaledValue;
|
|
167
|
+
const rawScale = this.scale + otherBd.scale;
|
|
168
|
+
const targetScale = Math.max(this.scale, otherBd.scale);
|
|
169
|
+
const scaleDiff = rawScale - targetScale;
|
|
170
|
+
const divisor = _BigDecimal.powerOf10(scaleDiff);
|
|
171
|
+
const rounded = _BigDecimal.roundDivision(rawResult, divisor, DEFAULT_ROUNDING_MODE);
|
|
172
|
+
return _BigDecimal.fromUnscaled(rounded, targetScale);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Alias for multiply
|
|
176
|
+
*/
|
|
177
|
+
times(other) {
|
|
178
|
+
return this.multiply(other);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Divide this BigDecimal by another value
|
|
182
|
+
* @param other - The divisor
|
|
183
|
+
* @param precision - Precision for the result (default: this.scale)
|
|
184
|
+
* @param roundingMode - How to round (default: HALF_UP)
|
|
185
|
+
* @returns A new BigDecimal with the result
|
|
186
|
+
*/
|
|
187
|
+
divide(other, precision, roundingMode = DEFAULT_ROUNDING_MODE) {
|
|
188
|
+
const otherBd = other instanceof _BigDecimal ? other : new _BigDecimal(other, this.scale);
|
|
189
|
+
if (otherBd.unscaledValue === 0n) {
|
|
190
|
+
throw new Error("Division by zero");
|
|
191
|
+
}
|
|
192
|
+
const targetScale = precision ?? this.scale;
|
|
193
|
+
const scaledDividend = this.unscaledValue * _BigDecimal.powerOf10(otherBd.scale + targetScale);
|
|
194
|
+
const divisor = otherBd.unscaledValue * _BigDecimal.powerOf10(this.scale);
|
|
195
|
+
const result = _BigDecimal.roundDivision(scaledDividend, divisor, roundingMode);
|
|
196
|
+
return _BigDecimal.fromUnscaled(result, targetScale);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Alias for divide
|
|
200
|
+
*/
|
|
201
|
+
dividedBy(other, precision, roundingMode) {
|
|
202
|
+
return this.divide(other, precision, roundingMode);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get the remainder of division
|
|
206
|
+
*/
|
|
207
|
+
mod(other) {
|
|
208
|
+
const otherBd = other instanceof _BigDecimal ? other : new _BigDecimal(other, this.scale);
|
|
209
|
+
const [a, b] = _BigDecimal.alignScales(this, otherBd);
|
|
210
|
+
if (b.unscaledValue === 0n) {
|
|
211
|
+
throw new Error("Division by zero");
|
|
212
|
+
}
|
|
213
|
+
const remainder = a.unscaledValue % b.unscaledValue;
|
|
214
|
+
return _BigDecimal.fromUnscaled(remainder, a.scale);
|
|
215
|
+
}
|
|
216
|
+
// ============================================
|
|
217
|
+
// COMPARISON OPERATIONS
|
|
218
|
+
// ============================================
|
|
219
|
+
/**
|
|
220
|
+
* Compare this BigDecimal to another
|
|
221
|
+
* @returns -1 if this < other, 0 if equal, 1 if this > other
|
|
222
|
+
*/
|
|
223
|
+
compareTo(other) {
|
|
224
|
+
const otherBd = other instanceof _BigDecimal ? other : new _BigDecimal(other, this.scale);
|
|
225
|
+
const [a, b] = _BigDecimal.alignScales(this, otherBd);
|
|
226
|
+
if (a.unscaledValue < b.unscaledValue) return -1;
|
|
227
|
+
if (a.unscaledValue > b.unscaledValue) return 1;
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Check if this BigDecimal equals another value
|
|
232
|
+
*/
|
|
233
|
+
equals(other) {
|
|
234
|
+
return this.compareTo(other) === 0;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Alias for equals
|
|
238
|
+
*/
|
|
239
|
+
eq(other) {
|
|
240
|
+
return this.equals(other);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if this BigDecimal is less than another
|
|
244
|
+
*/
|
|
245
|
+
lessThan(other) {
|
|
246
|
+
return this.compareTo(other) < 0;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Alias for lessThan
|
|
250
|
+
*/
|
|
251
|
+
lt(other) {
|
|
252
|
+
return this.lessThan(other);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Check if this BigDecimal is less than or equal to another
|
|
256
|
+
*/
|
|
257
|
+
lessThanOrEqual(other) {
|
|
258
|
+
return this.compareTo(other) <= 0;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Alias for lessThanOrEqual
|
|
262
|
+
*/
|
|
263
|
+
lte(other) {
|
|
264
|
+
return this.lessThanOrEqual(other);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Check if this BigDecimal is greater than another
|
|
268
|
+
*/
|
|
269
|
+
greaterThan(other) {
|
|
270
|
+
return this.compareTo(other) > 0;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Alias for greaterThan
|
|
274
|
+
*/
|
|
275
|
+
gt(other) {
|
|
276
|
+
return this.greaterThan(other);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Check if this BigDecimal is greater than or equal to another
|
|
280
|
+
*/
|
|
281
|
+
greaterThanOrEqual(other) {
|
|
282
|
+
return this.compareTo(other) >= 0;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Alias for greaterThanOrEqual
|
|
286
|
+
*/
|
|
287
|
+
gte(other) {
|
|
288
|
+
return this.greaterThanOrEqual(other);
|
|
289
|
+
}
|
|
290
|
+
// ============================================
|
|
291
|
+
// UTILITY METHODS
|
|
292
|
+
// ============================================
|
|
293
|
+
/**
|
|
294
|
+
* Check if this BigDecimal is zero
|
|
295
|
+
*/
|
|
296
|
+
isZero() {
|
|
297
|
+
return this.unscaledValue === 0n;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Check if this BigDecimal is positive (> 0)
|
|
301
|
+
*/
|
|
302
|
+
isPositive() {
|
|
303
|
+
return this.unscaledValue > 0n;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Check if this BigDecimal is negative (< 0)
|
|
307
|
+
*/
|
|
308
|
+
isNegative() {
|
|
309
|
+
return this.unscaledValue < 0n;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get the absolute value
|
|
313
|
+
* @returns A new BigDecimal with the absolute value
|
|
314
|
+
*/
|
|
315
|
+
abs() {
|
|
316
|
+
if (this.unscaledValue >= 0n) {
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
return _BigDecimal.fromUnscaled(-this.unscaledValue, this.scale);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Negate this BigDecimal
|
|
323
|
+
* @returns A new BigDecimal with the opposite sign
|
|
324
|
+
*/
|
|
325
|
+
negate() {
|
|
326
|
+
return _BigDecimal.fromUnscaled(-this.unscaledValue, this.scale);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get the sign of this BigDecimal
|
|
330
|
+
* @returns -1, 0, or 1
|
|
331
|
+
*/
|
|
332
|
+
sign() {
|
|
333
|
+
if (this.unscaledValue < 0n) return -1;
|
|
334
|
+
if (this.unscaledValue > 0n) return 1;
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Change the scale (number of decimal places)
|
|
339
|
+
*/
|
|
340
|
+
setScale(newScale, roundingMode = DEFAULT_ROUNDING_MODE) {
|
|
341
|
+
if (newScale === this.scale) {
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
if (newScale > this.scale) {
|
|
345
|
+
const multiplier = _BigDecimal.powerOf10(newScale - this.scale);
|
|
346
|
+
return _BigDecimal.fromUnscaled(this.unscaledValue * multiplier, newScale);
|
|
347
|
+
}
|
|
348
|
+
const divisor = _BigDecimal.powerOf10(this.scale - newScale);
|
|
349
|
+
const rounded = _BigDecimal.roundDivision(this.unscaledValue, divisor, roundingMode);
|
|
350
|
+
return _BigDecimal.fromUnscaled(rounded, newScale);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Get the precision (number of decimal places)
|
|
354
|
+
*/
|
|
355
|
+
getPrecision() {
|
|
356
|
+
return this.scale;
|
|
357
|
+
}
|
|
358
|
+
// ============================================
|
|
359
|
+
// CONVERSION METHODS
|
|
360
|
+
// ============================================
|
|
361
|
+
/**
|
|
362
|
+
* Convert to string representation
|
|
363
|
+
* @param options - Formatting options
|
|
364
|
+
* @param options.prettify - Add thousand separators (commas)
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* bd("1234567.89").toString() // "1234567.89"
|
|
369
|
+
* bd("1234567.89").toString({ prettify: true }) // "1,234,567.89"
|
|
370
|
+
* bd("1e15").toString() // "1000000000000000.00"
|
|
371
|
+
* bd("1e15").toString({ prettify: true }) // "1,000,000,000,000,000.00"
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
toString(options) {
|
|
375
|
+
const isNegative = this.unscaledValue < 0n;
|
|
376
|
+
const absValue = isNegative ? -this.unscaledValue : this.unscaledValue;
|
|
377
|
+
const sign = isNegative ? "-" : "";
|
|
378
|
+
let intPart;
|
|
379
|
+
let decPart;
|
|
380
|
+
if (this.scale === 0) {
|
|
381
|
+
intPart = absValue.toString();
|
|
382
|
+
decPart = "";
|
|
383
|
+
} else {
|
|
384
|
+
const str = absValue.toString().padStart(this.scale + 1, "0");
|
|
385
|
+
intPart = str.slice(0, -this.scale) || "0";
|
|
386
|
+
decPart = str.slice(-this.scale);
|
|
387
|
+
}
|
|
388
|
+
if (options?.prettify) {
|
|
389
|
+
intPart = _BigDecimal.addThousandSeparators(intPart);
|
|
390
|
+
}
|
|
391
|
+
if (this.scale === 0 || !decPart) {
|
|
392
|
+
return `${sign}${intPart}`;
|
|
393
|
+
}
|
|
394
|
+
return `${sign}${intPart}.${decPart}`;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Format as a display string with thousand separators
|
|
398
|
+
* Shorthand for toString({ prettify: true })
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```ts
|
|
402
|
+
* bd("1234567.89").toFormat() // "1,234,567.89"
|
|
403
|
+
* bd("1e12").toFormat() // "1,000,000,000,000.00"
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
toFormat() {
|
|
407
|
+
return this.toString({ prettify: true });
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Add thousand separators to an integer string
|
|
411
|
+
*/
|
|
412
|
+
static addThousandSeparators(intPart) {
|
|
413
|
+
return intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Convert to number (may lose precision for large values)
|
|
417
|
+
* @warning Use with caution - JavaScript numbers have limited precision
|
|
418
|
+
*/
|
|
419
|
+
toNumber() {
|
|
420
|
+
return parseFloat(this.toString());
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Format with fixed decimal places
|
|
424
|
+
* @param decimals - Number of decimal places
|
|
425
|
+
* @param options - Formatting options
|
|
426
|
+
*/
|
|
427
|
+
toFixed(decimals, options) {
|
|
428
|
+
return this.setScale(decimals).toString(options);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get the integer part only
|
|
432
|
+
*/
|
|
433
|
+
toInteger() {
|
|
434
|
+
const divisor = _BigDecimal.powerOf10(this.scale);
|
|
435
|
+
return this.unscaledValue / divisor;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* valueOf for implicit conversions
|
|
439
|
+
*/
|
|
440
|
+
valueOf() {
|
|
441
|
+
return this.toNumber();
|
|
442
|
+
}
|
|
443
|
+
// ============================================
|
|
444
|
+
// STATIC FACTORY METHODS
|
|
445
|
+
// ============================================
|
|
446
|
+
/**
|
|
447
|
+
* Create from unscaled value and scale
|
|
448
|
+
*/
|
|
449
|
+
static fromUnscaled(unscaledValue, scale) {
|
|
450
|
+
const bd2 = Object.create(_BigDecimal.prototype);
|
|
451
|
+
bd2.unscaledValue = unscaledValue;
|
|
452
|
+
bd2.scale = scale;
|
|
453
|
+
return bd2;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Create a BigDecimal with value zero
|
|
457
|
+
*/
|
|
458
|
+
static zero(precision = DEFAULT_PRECISION) {
|
|
459
|
+
return new _BigDecimal(0, precision);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Create a BigDecimal with value one
|
|
463
|
+
*/
|
|
464
|
+
static one(precision = DEFAULT_PRECISION) {
|
|
465
|
+
return new _BigDecimal(1, precision);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Sum multiple values
|
|
469
|
+
*/
|
|
470
|
+
static sum(...values) {
|
|
471
|
+
if (values.length === 0) {
|
|
472
|
+
return _BigDecimal.zero();
|
|
473
|
+
}
|
|
474
|
+
return values.reduce((acc, val) => {
|
|
475
|
+
return acc.add(val);
|
|
476
|
+
}, _BigDecimal.zero());
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get the maximum value
|
|
480
|
+
*/
|
|
481
|
+
static max(...values) {
|
|
482
|
+
if (values.length === 0) {
|
|
483
|
+
throw new Error("max requires at least one value");
|
|
484
|
+
}
|
|
485
|
+
return values.reduce((max, val) => {
|
|
486
|
+
const bd2 = val instanceof _BigDecimal ? val : new _BigDecimal(val);
|
|
487
|
+
return bd2.gt(max) ? bd2 : max;
|
|
488
|
+
}, new _BigDecimal(values[0]));
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Get the minimum value
|
|
492
|
+
*/
|
|
493
|
+
static min(...values) {
|
|
494
|
+
if (values.length === 0) {
|
|
495
|
+
throw new Error("min requires at least one value");
|
|
496
|
+
}
|
|
497
|
+
return values.reduce((min, val) => {
|
|
498
|
+
const bd2 = val instanceof _BigDecimal ? val : new _BigDecimal(val);
|
|
499
|
+
return bd2.lt(min) ? bd2 : min;
|
|
500
|
+
}, new _BigDecimal(values[0]));
|
|
501
|
+
}
|
|
502
|
+
// ============================================
|
|
503
|
+
// INTERNAL HELPERS
|
|
504
|
+
// ============================================
|
|
505
|
+
/**
|
|
506
|
+
* Align two BigDecimals to the same scale
|
|
507
|
+
*/
|
|
508
|
+
static alignScales(a, b) {
|
|
509
|
+
if (a.scale === b.scale) {
|
|
510
|
+
return [a, b];
|
|
511
|
+
}
|
|
512
|
+
const targetScale = Math.max(a.scale, b.scale);
|
|
513
|
+
return [a.setScale(targetScale), b.setScale(targetScale)];
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Perform division with rounding
|
|
517
|
+
*/
|
|
518
|
+
static roundDivision(dividend, divisor, mode) {
|
|
519
|
+
if (divisor === 0n) {
|
|
520
|
+
throw new Error("Division by zero");
|
|
521
|
+
}
|
|
522
|
+
const quotient = dividend / divisor;
|
|
523
|
+
const remainder = dividend % divisor;
|
|
524
|
+
if (remainder === 0n) {
|
|
525
|
+
return quotient;
|
|
526
|
+
}
|
|
527
|
+
const isNegative = dividend < 0n !== divisor < 0n;
|
|
528
|
+
const absRemainder = remainder < 0n ? -remainder : remainder;
|
|
529
|
+
const absDivisor = divisor < 0n ? -divisor : divisor;
|
|
530
|
+
const shouldRoundUp = (() => {
|
|
531
|
+
switch (mode) {
|
|
532
|
+
case "UP" /* UP */:
|
|
533
|
+
return true;
|
|
534
|
+
case "DOWN" /* DOWN */:
|
|
535
|
+
return false;
|
|
536
|
+
case "CEILING" /* CEILING */:
|
|
537
|
+
return !isNegative;
|
|
538
|
+
case "FLOOR" /* FLOOR */:
|
|
539
|
+
return isNegative;
|
|
540
|
+
case "HALF_UP" /* HALF_UP */: {
|
|
541
|
+
const doubled = absRemainder * 2n;
|
|
542
|
+
return doubled >= absDivisor;
|
|
543
|
+
}
|
|
544
|
+
case "HALF_DOWN" /* HALF_DOWN */: {
|
|
545
|
+
const doubled = absRemainder * 2n;
|
|
546
|
+
return doubled > absDivisor;
|
|
547
|
+
}
|
|
548
|
+
case "HALF_EVEN" /* HALF_EVEN */: {
|
|
549
|
+
const doubled = absRemainder * 2n;
|
|
550
|
+
if (doubled > absDivisor) return true;
|
|
551
|
+
if (doubled < absDivisor) return false;
|
|
552
|
+
const absQuotient = quotient < 0n ? -quotient : quotient;
|
|
553
|
+
return absQuotient % 2n !== 0n;
|
|
554
|
+
}
|
|
555
|
+
default:
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
})();
|
|
559
|
+
if (shouldRoundUp) {
|
|
560
|
+
return isNegative ? quotient - 1n : quotient + 1n;
|
|
561
|
+
}
|
|
562
|
+
return quotient;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
function bd(value, precision) {
|
|
566
|
+
return new BigDecimal(value, precision);
|
|
567
|
+
}
|
|
568
|
+
var index_default = BigDecimal;
|
|
569
|
+
export {
|
|
570
|
+
BigDecimal,
|
|
571
|
+
RoundingMode,
|
|
572
|
+
bd,
|
|
573
|
+
index_default as default
|
|
574
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bigdecimal-string",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Precise decimal arithmetic for JavaScript using BigInt. Avoids floating-point errors with a chainable, immutable API.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"bigdecimal",
|
|
26
|
+
"decimal",
|
|
27
|
+
"precision",
|
|
28
|
+
"arithmetic",
|
|
29
|
+
"bigint",
|
|
30
|
+
"financial",
|
|
31
|
+
"money",
|
|
32
|
+
"currency",
|
|
33
|
+
"floating-point",
|
|
34
|
+
"math"
|
|
35
|
+
],
|
|
36
|
+
"author": "ozJSey",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/ozJSey/big_decimal_string.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/ozJSey/big_decimal_string#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/ozJSey/big_decimal_string/issues"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vitest": "^2.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|