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