1/**
2 * Decimal numbers and arithmetic for cases where JavaScript `number` rounding
3 * is not precise enough. A `BigDecimal` stores digits as a `bigint` plus a
4 * decimal scale, which lets the module parse, compare, add, subtract, multiply,
5 * divide, round, and format decimal values such as money, quantities, and
6 * measurements.
7 *
8 * @since 2.0.0
9 */
10
11import * as Equal from "./Equal.ts"
12import * as Equ from "./Equivalence.ts"
13import { dual } from "./Function.ts"
14import * as Hash from "./Hash.ts"
15import { type Inspectable, NodeInspectSymbol } from "./Inspectable.ts"
16import * as Option from "./Option.ts"
17import * as order from "./Order.ts"
18import type { Ordering } from "./Ordering.ts"
19import { type Pipeable, pipeArguments } from "./Pipeable.ts"
20import { hasProperty } from "./Predicate.ts"
21
22const DEFAULT_PRECISION = 100
23const FINITE_INT_REGEXP = /^[+-]?\d+$/
24
25const TypeId = "~effect/BigDecimal"
26
27/**
28 * Represents an arbitrary precision decimal number.
29 *
30 * **When to use**
31 *
32 * Use when decimal arithmetic needs to avoid JavaScript floating point
33 * representation errors.
34 *
35 * **Example** (Inspecting BigDecimal storage)
36 *
37 * ```ts import.meta.vitest
38 * import { BigDecimal } from "effect"
39 *
40 * const d = BigDecimal.fromStringUnsafe("123.45")
41 *
42 * d.value // => 12345n
43 * d.scale // => 2
44 * ```
45 *
46 * @category models
47 * @since 2.0.0
48 */
49export interface BigDecimal extends Equal.Equal, Pipeable, Inspectable {
50 readonly [TypeId]: typeof TypeId
51 readonly value: bigint
52 readonly scale: number
53 /** @internal */
54 normalized?: BigDecimal
55}
56
57const BigDecimalProto: Omit<BigDecimal, "value" | "scale" | "normalized"> = {
58 [TypeId]: TypeId,
59 [Hash.symbol](this: BigDecimal): number {
60 const normalized = normalize(this)
61 return Hash.combine(Hash.string(String(normalized.value)), Hash.number(normalized.scale))
62 },
63 [Equal.symbol](this: BigDecimal, that: unknown): boolean {
64 return isBigDecimal(that) && compare(this, that) === 0
65 },
66 toString(this: BigDecimal) {
67 return `BigDecimal(${format(this)})`
68 },
69 toJSON(this: BigDecimal) {
70 return {
71 _id: "BigDecimal",
72 value: String(this.value),
73 scale: this.scale
74 }
75 },
76 [NodeInspectSymbol](this: BigDecimal) {
77 return this.toJSON()
78 },
79 pipe() {
80 return pipeArguments(this, arguments)
81 }
82} as const
83
84/**
85 * Checks whether a given value is a `BigDecimal`.
86 *
87 * **When to use**
88 *
89 * Use to validate unknown input and narrow it to `BigDecimal`.
90 *
91 * **Example** (Checking BigDecimal values)
92 *
93 * ```ts import.meta.vitest
94 * import { BigDecimal } from "effect"
95 *
96 * const decimal = BigDecimal.fromNumber(123.45)
97 * BigDecimal.isBigDecimal(decimal) // => false
98 * BigDecimal.isBigDecimal(BigDecimal.fromStringUnsafe("123.45")) // => true
99 * BigDecimal.isBigDecimal(123.45) // => false
100 * BigDecimal.isBigDecimal("123.45") // => false
101 * ```
102 *
103 * @category guards
104 * @since 2.0.0
105 */
106export const isBigDecimal = (u: unknown): u is BigDecimal => hasProperty(u, TypeId)
107
108/**
109 * Creates a `BigDecimal` from a `bigint` value and a safe integer scale.
110 *
111 * **When to use**
112 *
113 * Use to construct a decimal directly from its unscaled integer value and
114 * decimal scale.
115 *
116 * **Gotchas**
117 *
118 * Throws a `RangeError` if `scale` is not a safe integer.
119 *
120 * **Example** (Creating decimals from bigint and scale)
121 *
122 * ```ts import.meta.vitest
123 * import { BigDecimal } from "effect"
124 *
125 * // Create 123.45 (12345 with scale 2)
126 * const decimal = BigDecimal.make(12345n, 2)
127 * decimal // => BigDecimal.fromStringUnsafe("123.45")
128 *
129 * // Create 42 (42 with scale 0)
130 * const integer = BigDecimal.make(42n, 0)
131 * integer // => BigDecimal.fromBigInt(42n)
132 * ```
133 *
134 * @see {@link fromBigInt} for constructing an integer decimal from a `bigint`
135 *
136 * @category constructors
137 * @since 2.0.0
138 */
139export const make = (value: bigint, scale: number): BigDecimal => {
140 if (!Number.isSafeInteger(scale)) {
141 throw new RangeError(`Scale must be a safe integer, got ${scale}`)
142 }
143 const o = Object.create(BigDecimalProto)
144 o.value = value
145 o.scale = scale
146 return o
147}
148
149const makeNormalized = (value: bigint, scale: number): BigDecimal => {
150 const o = make(value, scale)
151 o.normalized = o
152 return o
153}
154
155/**
156 * Internal function used to create pre-normalized `BigDecimal`s.
157 *
158 * @internal
159 */
160export const makeNormalizedUnsafe = (value: bigint, scale: number): BigDecimal => {
161 if (value !== bigint0 && value % bigint10 === bigint0) {
162 throw new RangeError("Value must be normalized")
163 }
164
165 return makeNormalized(value, scale)
166}
167
168const bigint0 = BigInt(0)
169const bigint1 = BigInt(1)
170const bigint_1 = BigInt(-1)
171const bigint2 = BigInt(2)
172const bigint5 = BigInt(5)
173const bigint_5 = BigInt(-5)
174const bigint10 = BigInt(10)
175const zero = makeNormalized(bigint0, 0)
176const one = makeNormalizedUnsafe(bigint1, 0)
177
178/**
179 * Normalizes a given `BigDecimal` by removing trailing zeros.
180 *
181 * **When to use**
182 *
183 * Use to canonicalize decimals that have equivalent values but different
184 * internal scales.
185 *
186 * **Example** (Normalizing trailing zeros)
187 *
188 * ```ts import.meta.vitest
189 * import { BigDecimal } from "effect"
190 *
191 * const decimal = BigDecimal.normalize(BigDecimal.fromStringUnsafe("123.00000"))
192 * const decimalStorage = [decimal.value, decimal.scale] // => [123n, 0]
193 *
194 * const largeDecimal = BigDecimal.normalize(BigDecimal.fromStringUnsafe("12300000"))
195 * const largeDecimalStorage = [largeDecimal.value, largeDecimal.scale] // => [123n, -5]
196 * ```
197 *
198 * @see {@link format} for rendering normalized decimals as strings
199 *
200 * @category scaling
201 * @since 2.0.0
202 */
203export const normalize = (self: BigDecimal): BigDecimal => {
204 if (self.normalized === undefined) {
205 if (self.value === bigint0) {
206 self.normalized = zero
207 } else {
208 const digits = `${self.value}`
209 let end = digits.length
210 while (digits[end - 1] === "0") end--
211 self.normalized = makeNormalized(BigInt(digits.slice(0, end)), self.scale - (digits.length - end))
212 }
213 }
214
215 return self.normalized
216}
217
218/**
219 * Changes a `BigDecimal` to the specified scale.
220 *
221 * **When to use**
222 *
223 * Use to change how many decimal places are represented by a `BigDecimal`.
224 *
225 * **Details**
226 *
227 * Increasing the scale appends decimal zeros. Decreasing the scale discards
228 * digits beyond the target scale by `bigint` division, which truncates toward
229 * zero.
230 *
231 * **Example** (Scaling decimal precision)
232 *
233 * ```ts import.meta.vitest
234 * import { BigDecimal } from "effect"
235 *
236 * const decimal = BigDecimal.fromNumberUnsafe(123.45)
237 *
238 * // Increase scale (add more precision)
239 * const scaled = BigDecimal.scale(decimal, 4)
240 * const scaledStorage = [scaled.value, scaled.scale] // => [1234500n, 4]
241 *
242 * // Decrease scale (reduce precision, truncating toward zero)
243 * const reduced = BigDecimal.scale(decimal, 1)
244 * reduced // => BigDecimal.fromStringUnsafe("123.4")
245 * ```
246 *
247 * @see {@link round} for changing scale with configurable rounding
248 *
249 * @category scaling
250 * @since 2.0.0
251 */
252export const scale: {
253 (scale: number): (self: BigDecimal) => BigDecimal
254 (self: BigDecimal, scale: number): BigDecimal
255} = dual(2, (self: BigDecimal, scale: number): BigDecimal => {
256 if (scale > self.scale) {
257 return make(self.value * bigint10 ** BigInt(scale - self.scale), scale)
258 }
259
260 if (scale < self.scale) {
261 return make(self.value / bigint10 ** BigInt(self.scale - scale), scale)
262 }
263
264 return self
265})
266
267/**
268 * Provides an addition operation on `BigDecimal`s.
269 *
270 * **When to use**
271 *
272 * Use when you need a decimal addition function for piping or higher-order APIs
273 * while preserving decimal precision.
274 *
275 * **Example** (Adding decimals)
276 *
277 * ```ts import.meta.vitest
278 * import { BigDecimal } from "effect"
279 *
280 * const result = BigDecimal.sum(
281 * BigDecimal.fromStringUnsafe("2"),
282 * BigDecimal.fromStringUnsafe("3")
283 * ) // => BigDecimal.fromBigInt(5n)
284 * ```
285 *
286 * @see {@link sumAll} for summing an iterable of `BigDecimal` values
287 *
288 * @category math
289 * @since 2.0.0
290 */
291export const sum: {
292 (that: BigDecimal): (self: BigDecimal) => BigDecimal
293 (self: BigDecimal, that: BigDecimal): BigDecimal
294} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => {
295 if (that.value === bigint0) {
296 return self
297 }
298
299 if (self.value === bigint0) {
300 return that
301 }
302
303 if (self.scale > that.scale) {
304 return make(scale(that, self.scale).value + self.value, self.scale)
305 }
306
307 if (self.scale < that.scale) {
308 return make(scale(self, that.scale).value + that.value, that.scale)
309 }
310
311 return make(self.value + that.value, self.scale)
312})
313
314/**
315 * Takes an `Iterable` of `BigDecimal`s and returns their sum as a single `BigDecimal`.
316 *
317 * **When to use**
318 *
319 * Use when you need to aggregate decimal quantities with decimal precision
320 * instead of converting through JavaScript numbers.
321 *
322 * **Example** (Adding multiple decimals)
323 *
324 * ```ts import.meta.vitest
325 * import { BigDecimal } from "effect"
326 *
327 * const result = BigDecimal.sumAll([
328 * BigDecimal.fromStringUnsafe("2"),
329 * BigDecimal.fromStringUnsafe("3"),
330 * BigDecimal.fromStringUnsafe("4")
331 * ]) // => BigDecimal.fromBigInt(9n)
332 * ```
333 *
334 * @see {@link sum} for adding two `BigDecimal` values
335 *
336 * @category math
337 * @since 3.16.0
338 */
339export const sumAll = (collection: Iterable<BigDecimal>): BigDecimal => {
340 let out: BigDecimal = zero
341 for (const n of collection) {
342 out = sum(out, n)
343 }
344 return out
345}
346
347/**
348 * Provides a multiplication operation on `BigDecimal`s.
349 *
350 * **When to use**
351 *
352 * Use to multiply two `BigDecimal` values.
353 *
354 * **Example** (Multiplying decimals)
355 *
356 * ```ts import.meta.vitest
357 * import { BigDecimal } from "effect"
358 *
359 * const result = BigDecimal.multiply(
360 * BigDecimal.fromStringUnsafe("2"),
361 * BigDecimal.fromStringUnsafe("3")
362 * ) // => BigDecimal.fromBigInt(6n)
363 * ```
364 *
365 * @see {@link multiplyAll} for multiplying an iterable of `BigDecimal` values
366 *
367 * @category math
368 * @since 2.0.0
369 */
370export const multiply: {
371 (that: BigDecimal): (self: BigDecimal) => BigDecimal
372 (self: BigDecimal, that: BigDecimal): BigDecimal
373} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => {
374 if (that.value === bigint0 || self.value === bigint0) {
375 return zero
376 }
377
378 return make(self.value * that.value, self.scale + that.scale)
379})
380
381/**
382 * Takes an `Iterable` of `BigDecimal`s and returns their multiplication as a single `BigDecimal`.
383 *
384 * **When to use**
385 *
386 * Use to multiply all `BigDecimal` values in an iterable.
387 *
388 * **Example** (Multiplying multiple decimals)
389 *
390 * ```ts import.meta.vitest
391 * import { BigDecimal } from "effect"
392 *
393 * const result = BigDecimal.multiplyAll([
394 * BigDecimal.fromStringUnsafe("2"),
395 * BigDecimal.fromStringUnsafe("3"),
396 * BigDecimal.fromStringUnsafe("4")
397 * ]) // => BigDecimal.fromBigInt(24n)
398 * ```
399 *
400 * @see {@link multiply} for multiplying two `BigDecimal` values
401 *
402 * @category math
403 * @since 4.0.0
404 */
405export const multiplyAll = (collection: Iterable<BigDecimal>): BigDecimal => {
406 let out: BigDecimal = one
407 for (const n of collection) {
408 if (n.value === bigint0) {
409 return zero
410 }
411 out = multiply(out, n)
412 }
413 return out
414}
415
416/**
417 * Provides a subtraction operation on `BigDecimal`s.
418 *
419 * **When to use**
420 *
421 * Use to subtract one `BigDecimal` value from another.
422 *
423 * **Example** (Subtracting decimals)
424 *
425 * ```ts import.meta.vitest
426 * import { BigDecimal } from "effect"
427 *
428 * const result = BigDecimal.subtract(
429 * BigDecimal.fromStringUnsafe("2"),
430 * BigDecimal.fromStringUnsafe("3")
431 * ) // => BigDecimal.fromBigInt(-1n)
432 * ```
433 *
434 * @category math
435 * @since 2.0.0
436 */
437export const subtract: {
438 (that: BigDecimal): (self: BigDecimal) => BigDecimal
439 (self: BigDecimal, that: BigDecimal): BigDecimal
440} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => {
441 if (that.value === bigint0) {
442 return self
443 }
444
445 if (self.value === bigint0) {
446 return make(-that.value, that.scale)
447 }
448
449 if (self.scale > that.scale) {
450 return make(self.value - scale(that, self.scale).value, self.scale)
451 }
452
453 if (self.scale < that.scale) {
454 return make(scale(self, that.scale).value - that.value, that.scale)
455 }
456
457 return make(self.value - that.value, self.scale)
458})
459
460/**
461 * Internal function used for arbitrary precision division.
462 */
463const divideWithPrecision = (
464 num: bigint,
465 den: bigint,
466 scale: number,
467 precision: number
468): BigDecimal => {
469 const numNegative = num < bigint0
470 const denNegative = den < bigint0
471 const negateResult = numNegative !== denNegative
472
473 num = numNegative ? -num : num
474 den = denNegative ? -den : den
475
476 // Shift digits until numerator is larger than denominator (set scale appropriately).
477 while (num < den) {
478 num *= bigint10
479 scale++
480 }
481
482 // First division.
483 let quotient = num / den
484 let remainder = num % den
485
486 if (remainder === bigint0) {
487 // No remainder, return immediately.
488 return make(negateResult ? -quotient : quotient, scale)
489 }
490
491 // The quotient is guaranteed to be non-negative at this point. No need to consider sign.
492 let count = `${quotient}`.length
493
494 // Shift the remainder by 1 decimal; The quotient will be 1 digit upon next division.
495 remainder *= bigint10
496 while (remainder !== bigint0 && count < precision) {
497 const q = remainder / den
498 const r = remainder % den
499 quotient = quotient * bigint10 + q
500 remainder = r * bigint10
501
502 count++
503 scale++
504 }
505
506 if (remainder !== bigint0) {
507 // Round final number with remainder.
508 quotient += roundTerminal(remainder / den)
509 }
510
511 return make(negateResult ? -quotient : quotient, scale)
512}
513
514/**
515 * Internal function used for rounding.
516 *
517 * Returns 1 if the most significant digit is >= 5, otherwise 0.
518 *
519 * This is used after dividing a number by a power of ten and rounding the last digit.
520 *
521 * @internal
522 */
523export const roundTerminal = (n: bigint): bigint => {
524 const pos = n >= bigint0 ? 0 : 1
525 return Number(`${n}`[pos]) < 5 ? bigint0 : bigint1
526}
527
528/**
529 * Divides `BigDecimal`s safely.
530 *
531 * **When to use**
532 *
533 * Use to divide `BigDecimal` values while representing division by zero as
534 * `Option.none`.
535 *
536 * **Details**
537 *
538 * If the dividend is not a multiple of the divisor, the result will be a `BigDecimal` value
539 * with up to the default division precision. If the divisor is `0`, the result
540 * will be `Option.none()`.
541 *
542 * **Example** (Dividing decimals safely)
543 *
544 * ```ts import.meta.vitest
545 * import { BigDecimal, Option } from "effect"
546 *
547 * const six = BigDecimal.fromBigInt(6n)
548 *
549 * BigDecimal.divide(six, BigDecimal.fromBigInt(3n)) // => Option.some(BigDecimal.fromBigInt(2n))
550 * BigDecimal.divide(six, BigDecimal.fromBigInt(4n)) // => Option.some(BigDecimal.fromStringUnsafe("1.5"))
551 * BigDecimal.divide(six, BigDecimal.fromBigInt(0n)) // => Option.none()
552 * ```
553 *
554 * @see {@link divideUnsafe} for division that throws when the divisor is zero
555 * @see {@link remainder} for the decimal remainder operation
556 *
557 * @category math
558 * @since 2.0.0
559 */
560export const divide: {
561 (that: BigDecimal): (self: BigDecimal) => Option.Option<BigDecimal>
562 (self: BigDecimal, that: BigDecimal): Option.Option<BigDecimal>
563} = dual(2, (self: BigDecimal, that: BigDecimal): Option.Option<BigDecimal> => {
564 if (that.value === bigint0) {
565 return Option.none()
566 }
567
568 if (self.value === bigint0) {
569 return Option.some(zero)
570 }
571
572 const scale = self.scale - that.scale
573 if (self.value === that.value) {
574 return Option.some(make(bigint1, scale))
575 }
576
577 return Option.some(divideWithPrecision(self.value, that.value, scale, DEFAULT_PRECISION))
578})
579
580/**
581 * Provides an unsafe division operation on `BigDecimal`s.
582 *
583 * **When to use**
584 *
585 * Use when you need to divide `BigDecimal` values where the divisor is known
586 * to be non-zero, so division by zero should be a thrown exception.
587 *
588 * **Details**
589 *
590 * If the dividend is not a multiple of the divisor, the result will be a `BigDecimal` value
591 * with up to the default division precision.
592 *
593 * **Gotchas**
594 *
595 * Throws a `RangeError` if the divisor is `0`.
596 *
597 * **Example** (Dividing decimals unsafely)
598 *
599 * ```ts import.meta.vitest
600 * import { BigDecimal } from "effect"
601 *
602 * BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(2n)
603 * BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("4")) // => BigDecimal.fromStringUnsafe("1.5")
604 * ```
605 *
606 * @see {@link divide} for division that returns `Option.none` when the divisor is zero
607 *
608 * @category math
609 * @since 4.0.0
610 */
611export const divideUnsafe: {
612 (that: BigDecimal): (self: BigDecimal) => BigDecimal
613 (self: BigDecimal, that: BigDecimal): BigDecimal
614} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => {
615 if (that.value === bigint0) {
616 throw new RangeError("Division by zero")
617 }
618
619 if (self.value === bigint0) {
620 return zero
621 }
622
623 const scale = self.scale - that.scale
624 if (self.value === that.value) {
625 return make(bigint1, scale)
626 }
627 return divideWithPrecision(self.value, that.value, scale, DEFAULT_PRECISION)
628})
629
630const MAX_COMPARISON_SCALE_ALIGNMENT = 100
631const comparisonPowersOfTen: Array<bigint | undefined> = [bigint1]
632
633const compareBigInt = (self: bigint, that: bigint): Ordering => self === that ? 0 : self < that ? -1 : 1
634
635const compareMagnitude = (self: BigDecimal, that: BigDecimal): Ordering => {
636 const selfDigits = `${self.value < bigint0 ? -self.value : self.value}`
637 const thatDigits = `${that.value < bigint0 ? -that.value : that.value}`
638 const exponentDifference = BigInt(selfDigits.length - thatDigits.length) - BigInt(self.scale) + BigInt(that.scale)
639 if (exponentDifference !== bigint0) return exponentDifference < bigint0 ? -1 : 1
640
641 const length = Math.max(selfDigits.length, thatDigits.length)
642 return order.String(selfDigits.padEnd(length, "0"), thatDigits.padEnd(length, "0"))
643}
644
645const compare = (self: BigDecimal, that: BigDecimal): Ordering => {
646 if (self.scale === that.scale) return compareBigInt(self.value, that.value)
647
648 const selfSign = sign(self)
649 const thatSign = sign(that)
650 if (selfSign !== thatSign) return selfSign < thatSign ? -1 : 1
651 if (selfSign === 0) return 0
652
653 const scaleDifference = self.scale - that.scale
654 const absoluteScaleDifference = Math.abs(scaleDifference)
655 if (absoluteScaleDifference > MAX_COMPARISON_SCALE_ALIGNMENT) {
656 return selfSign === -1 ? compareMagnitude(that, self) : compareMagnitude(self, that)
657 }
658
659 const powerOfTen = comparisonPowersOfTen[absoluteScaleDifference] ??= bigint10 ** BigInt(absoluteScaleDifference)
660 return scaleDifference > 0
661 ? compareBigInt(self.value, that.value * powerOfTen)
662 : compareBigInt(self.value * powerOfTen, that.value)
663}
664
665/**
666 * Provides an `Order` instance for `BigDecimal` that allows comparing and sorting BigDecimal values.
667 *
668 * **When to use**
669 *
670 * Use when you need to sort or compare decimal values through APIs that accept
671 * an ordering instance.
672 *
673 * **Example** (Comparing decimals)
674 *
675 * ```ts import.meta.vitest
676 * import { BigDecimal } from "effect"
677 *
678 * const a = BigDecimal.fromNumberUnsafe(1.5)
679 * const b = BigDecimal.fromNumberUnsafe(2.3)
680 * const c = BigDecimal.fromNumberUnsafe(1.5)
681 *
682 * BigDecimal.Order(a, b) // => -1
683 * BigDecimal.Order(b, a) // => 1
684 * BigDecimal.Order(a, c) // => 0
685 * ```
686 *
687 * @category instances
688 * @since 2.0.0
689 */
690export const Order: order.Order<BigDecimal> = order.make(compare)
691
692/**
693 * Returns `true` if the first argument is less than the second, otherwise `false`.
694 *
695 * **When to use**
696 *
697 * Use to test whether one `BigDecimal` is strictly less than another.
698 *
699 * **Example** (Checking less-than comparisons)
700 *
701 * ```ts import.meta.vitest
702 * import { BigDecimal } from "effect"
703 *
704 * const two = BigDecimal.fromStringUnsafe("2")
705 * const three = BigDecimal.fromStringUnsafe("3")
706 * const four = BigDecimal.fromStringUnsafe("4")
707 *
708 * BigDecimal.isLessThan(two, three) // => true
709 * BigDecimal.isLessThan(three, three) // => false
710 * BigDecimal.isLessThan(four, three) // => false
711 * ```
712 *
713 * @category predicates
714 * @since 4.0.0
715 */
716export const isLessThan: {
717 (that: BigDecimal): (self: BigDecimal) => boolean
718 (self: BigDecimal, that: BigDecimal): boolean
719} = order.isLessThan(Order)
720
721/**
722 * Checks whether a given `BigDecimal` is less than or equal to the provided one.
723 *
724 * **When to use**
725 *
726 * Use to test whether one `BigDecimal` is less than or equal to another.
727 *
728 * **Example** (Checking less-than-or-equal comparisons)
729 *
730 * ```ts import.meta.vitest
731 * import { BigDecimal } from "effect"
732 *
733 * const two = BigDecimal.fromStringUnsafe("2")
734 * const three = BigDecimal.fromStringUnsafe("3")
735 * const four = BigDecimal.fromStringUnsafe("4")
736 *
737 * BigDecimal.isLessThanOrEqualTo(two, three) // => true
738 * BigDecimal.isLessThanOrEqualTo(three, three) // => true
739 * BigDecimal.isLessThanOrEqualTo(four, three) // => false
740 * ```
741 *
742 * @category predicates
743 * @since 4.0.0
744 */
745export const isLessThanOrEqualTo: {
746 (that: BigDecimal): (self: BigDecimal) => boolean
747 (self: BigDecimal, that: BigDecimal): boolean
748} = order.isLessThanOrEqualTo(Order)
749
750/**
751 * Returns `true` if the first argument is greater than the second, otherwise `false`.
752 *
753 * **When to use**
754 *
755 * Use to test whether one `BigDecimal` is strictly greater than another.
756 *
757 * **Example** (Checking greater-than comparisons)
758 *
759 * ```ts import.meta.vitest
760 * import { BigDecimal } from "effect"
761 *
762 * const two = BigDecimal.fromStringUnsafe("2")
763 * const three = BigDecimal.fromStringUnsafe("3")
764 * const four = BigDecimal.fromStringUnsafe("4")
765 *
766 * BigDecimal.isGreaterThan(two, three) // => false
767 * BigDecimal.isGreaterThan(three, three) // => false
768 * BigDecimal.isGreaterThan(four, three) // => true
769 * ```
770 *
771 * @category predicates
772 * @since 4.0.0
773 */
774export const isGreaterThan: {
775 (that: BigDecimal): (self: BigDecimal) => boolean
776 (self: BigDecimal, that: BigDecimal): boolean
777} = order.isGreaterThan(Order)
778
779/**
780 * Checks whether a given `BigDecimal` is greater than or equal to the provided one.
781 *
782 * **When to use**
783 *
784 * Use to test whether one `BigDecimal` is greater than or equal to another.
785 *
786 * **Example** (Checking greater-than-or-equal comparisons)
787 *
788 * ```ts import.meta.vitest
789 * import { BigDecimal } from "effect"
790 *
791 * const two = BigDecimal.fromStringUnsafe("2")
792 * const three = BigDecimal.fromStringUnsafe("3")
793 * const four = BigDecimal.fromStringUnsafe("4")
794 *
795 * BigDecimal.isGreaterThanOrEqualTo(two, three) // => false
796 * BigDecimal.isGreaterThanOrEqualTo(three, three) // => true
797 * BigDecimal.isGreaterThanOrEqualTo(four, three) // => true
798 * ```
799 *
800 * @category predicates
801 * @since 4.0.0
802 */
803export const isGreaterThanOrEqualTo: {
804 (that: BigDecimal): (self: BigDecimal) => boolean
805 (self: BigDecimal, that: BigDecimal): boolean
806} = order.isGreaterThanOrEqualTo(Order)
807
808/**
809 * Checks whether a `BigDecimal` is between a `minimum` and `maximum` value (inclusive).
810 *
811 * **When to use**
812 *
813 * Use to test whether a `BigDecimal` falls inside an inclusive range.
814 *
815 * **Example** (Checking decimal ranges)
816 *
817 * ```ts import.meta.vitest
818 * import { BigDecimal } from "effect"
819 *
820 * const between = BigDecimal.between({
821 * minimum: BigDecimal.fromStringUnsafe("1"),
822 * maximum: BigDecimal.fromStringUnsafe("5")
823 * })
824 *
825 * between(BigDecimal.fromStringUnsafe("3")) // => true
826 * between(BigDecimal.fromStringUnsafe("0")) // => false
827 * between(BigDecimal.fromStringUnsafe("6")) // => false
828 * ```
829 *
830 * @see {@link clamp} for forcing a `BigDecimal` into an inclusive range
831 *
832 * @category predicates
833 * @since 2.0.0
834 */
835export const between: {
836 (options: {
837 minimum: BigDecimal
838 maximum: BigDecimal
839 }): (self: BigDecimal) => boolean
840 (self: BigDecimal, options: {
841 minimum: BigDecimal
842 maximum: BigDecimal
843 }): boolean
844} = order.isBetween(Order)
845
846/**
847 * Restricts the given `BigDecimal` to be within the range specified by the `minimum` and `maximum` values.
848 *
849 * **When to use**
850 *
851 * Use to force a `BigDecimal` into an inclusive range.
852 *
853 * **Details**
854 *
855 * If the `BigDecimal` is less than the `minimum` value, the function returns
856 * the `minimum` value. If it is greater than the `maximum` value, the function
857 * returns the `maximum` value. Otherwise, it returns the original `BigDecimal`.
858 *
859 * **Example** (Clamping decimals to a range)
860 *
861 * ```ts import.meta.vitest
862 * import { BigDecimal } from "effect"
863 *
864 * const clamp = BigDecimal.clamp({
865 * minimum: BigDecimal.fromStringUnsafe("1"),
866 * maximum: BigDecimal.fromStringUnsafe("5")
867 * })
868 *
869 * clamp(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(3n)
870 * clamp(BigDecimal.fromStringUnsafe("0")) // => BigDecimal.fromBigInt(1n)
871 * clamp(BigDecimal.fromStringUnsafe("6")) // => BigDecimal.fromBigInt(5n)
872 * ```
873 *
874 * @see {@link between} for checking whether a `BigDecimal` is already inside a range
875 *
876 * @category math
877 * @since 2.0.0
878 */
879export const clamp: {
880 (options: {
881 minimum: BigDecimal
882 maximum: BigDecimal
883 }): (self: BigDecimal) => BigDecimal
884 (self: BigDecimal, options: {
885 minimum: BigDecimal
886 maximum: BigDecimal
887 }): BigDecimal
888} = order.clamp(Order)
889
890/**
891 * Returns the minimum between two `BigDecimal`s.
892 *
893 * **When to use**
894 *
895 * Use to select the smaller of two `BigDecimal` values.
896 *
897 * **Example** (Selecting the smaller decimal)
898 *
899 * ```ts import.meta.vitest
900 * import { BigDecimal } from "effect"
901 *
902 * const result = BigDecimal.min(
903 * BigDecimal.fromStringUnsafe("2"),
904 * BigDecimal.fromStringUnsafe("3")
905 * ) // => BigDecimal.fromBigInt(2n)
906 * ```
907 *
908 * @see {@link max} for selecting the larger value
909 *
910 * @category math
911 * @since 2.0.0
912 */
913export const min: {
914 (that: BigDecimal): (self: BigDecimal) => BigDecimal
915 (self: BigDecimal, that: BigDecimal): BigDecimal
916} = order.min(Order)
917
918/**
919 * Returns the maximum between two `BigDecimal`s.
920 *
921 * **When to use**
922 *
923 * Use to select the larger of two `BigDecimal` values.
924 *
925 * **Example** (Selecting the larger decimal)
926 *
927 * ```ts import.meta.vitest
928 * import { BigDecimal } from "effect"
929 *
930 * const result = BigDecimal.max(
931 * BigDecimal.fromStringUnsafe("2"),
932 * BigDecimal.fromStringUnsafe("3")
933 * ) // => BigDecimal.fromBigInt(3n)
934 * ```
935 *
936 * @see {@link min} for selecting the smaller value
937 *
938 * @category math
939 * @since 2.0.0
940 */
941export const max: {
942 (that: BigDecimal): (self: BigDecimal) => BigDecimal
943 (self: BigDecimal, that: BigDecimal): BigDecimal
944} = order.max(Order)
945
946/**
947 * Determines the sign of a given `BigDecimal`.
948 *
949 * **When to use**
950 *
951 * Use to classify a `BigDecimal` as negative, zero, or positive.
952 *
953 * **Example** (Reading decimal signs)
954 *
955 * ```ts import.meta.vitest
956 * import { BigDecimal } from "effect"
957 *
958 * BigDecimal.sign(BigDecimal.fromStringUnsafe("-5")) // => -1
959 * BigDecimal.sign(BigDecimal.fromStringUnsafe("0")) // => 0
960 * BigDecimal.sign(BigDecimal.fromStringUnsafe("5")) // => 1
961 * ```
962 *
963 * @category math
964 * @since 2.0.0
965 */
966export const sign = (n: BigDecimal): Ordering => n.value === bigint0 ? 0 : n.value < bigint0 ? -1 : 1
967
968/**
969 * Determines the absolute value of a given `BigDecimal`.
970 *
971 * **When to use**
972 *
973 * Use to remove the sign from a `BigDecimal` while preserving its magnitude.
974 *
975 * **Example** (Calculating absolute values)
976 *
977 * ```ts import.meta.vitest
978 * import { BigDecimal } from "effect"
979 *
980 * BigDecimal.abs(BigDecimal.fromStringUnsafe("-5")) // => BigDecimal.fromBigInt(5n)
981 * BigDecimal.abs(BigDecimal.fromStringUnsafe("0")) // => BigDecimal.fromBigInt(0n)
982 * BigDecimal.abs(BigDecimal.fromStringUnsafe("5")) // => BigDecimal.fromBigInt(5n)
983 * ```
984 *
985 * @category math
986 * @since 2.0.0
987 */
988export const abs = (n: BigDecimal): BigDecimal => n.value < bigint0 ? make(-n.value, n.scale) : n
989
990/**
991 * Provides a negate operation on `BigDecimal`s.
992 *
993 * **When to use**
994 *
995 * Use to flip the sign of a `BigDecimal`.
996 *
997 * **Example** (Negating decimals)
998 *
999 * ```ts import.meta.vitest
1000 * import { BigDecimal } from "effect"
1001 *
1002 * BigDecimal.negate(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(-3n)
1003 * BigDecimal.negate(BigDecimal.fromStringUnsafe("-6")) // => BigDecimal.fromBigInt(6n)
1004 * ```
1005 *
1006 * @category math
1007 * @since 2.0.0
1008 */
1009export const negate = (n: BigDecimal): BigDecimal => make(-n.value, n.scale)
1010
1011/**
1012 * Computes the decimal remainder safely when one operand is divided by a second
1013 * operand.
1014 *
1015 * **When to use**
1016 *
1017 * Use to compute a decimal remainder while representing division by zero as
1018 * `Option.none`.
1019 *
1020 * **Details**
1021 *
1022 * If the divisor is `0`, the result will be `Option.none()`.
1023 *
1024 * **Example** (Computing remainders safely)
1025 *
1026 * ```ts import.meta.vitest
1027 * import { BigDecimal, Option } from "effect"
1028 *
1029 * const two = BigDecimal.fromStringUnsafe("2")
1030 * const three = BigDecimal.fromStringUnsafe("3")
1031 * const zero = BigDecimal.fromStringUnsafe("0")
1032 *
1033 * BigDecimal.remainder(three, two) // => Option.some(BigDecimal.fromBigInt(1n))
1034 * BigDecimal.remainder(two, zero) // => Option.none()
1035 * ```
1036 *
1037 * @see {@link remainderUnsafe} for remainder calculation that throws when the divisor is zero
1038 * @see {@link divide} for decimal quotient calculation
1039 *
1040 * @category math
1041 * @since 2.0.0
1042 */
1043export const remainder: {
1044 (divisor: BigDecimal): (self: BigDecimal) => Option.Option<BigDecimal>
1045 (self: BigDecimal, divisor: BigDecimal): Option.Option<BigDecimal>
1046} = dual(2, (self: BigDecimal, divisor: BigDecimal): Option.Option<BigDecimal> => {
1047 if (divisor.value === bigint0) {
1048 return Option.none()
1049 }
1050
1051 const max = Math.max(self.scale, divisor.scale)
1052 return Option.some(make(scale(self, max).value % scale(divisor, max).value, max))
1053})
1054
1055/**
1056 * Returns the decimal remainder left over when one operand is divided by a
1057 * non-zero second operand.
1058 *
1059 * **When to use**
1060 *
1061 * Use when you need to compute a `BigDecimal` remainder with a divisor known to
1062 * be non-zero and want a plain `BigDecimal` result instead of an `Option`.
1063 *
1064 * **Gotchas**
1065 *
1066 * Throws a `RangeError` if the divisor is `0`.
1067 *
1068 * **Example** (Computing remainders unsafely)
1069 *
1070 * ```ts import.meta.vitest
1071 * import { BigDecimal } from "effect"
1072 *
1073 * BigDecimal.remainderUnsafe(
1074 * BigDecimal.fromStringUnsafe("3"),
1075 * BigDecimal.fromStringUnsafe("2")
1076 * ) // => BigDecimal.fromBigInt(1n)
1077 * ```
1078 *
1079 * @see {@link remainder} for returning `Option.none` when the divisor is zero
1080 *
1081 * @category math
1082 * @since 4.0.0
1083 */
1084export const remainderUnsafe: {
1085 (divisor: BigDecimal): (self: BigDecimal) => BigDecimal
1086 (self: BigDecimal, divisor: BigDecimal): BigDecimal
1087} = dual(2, (self: BigDecimal, divisor: BigDecimal): BigDecimal => {
1088 if (divisor.value === bigint0) {
1089 throw new RangeError("Division by zero")
1090 }
1091
1092 const max = Math.max(self.scale, divisor.scale)
1093 return make(scale(self, max).value % scale(divisor, max).value, max)
1094})
1095
1096/**
1097 * Provides an `Equivalence` instance for `BigDecimal` that determines equality between BigDecimal values.
1098 *
1099 * **When to use**
1100 *
1101 * Use when comparing decimal values through APIs that accept an equivalence
1102 * relation.
1103 *
1104 * **Example** (Checking decimal equivalence)
1105 *
1106 * ```ts import.meta.vitest
1107 * import { BigDecimal } from "effect"
1108 *
1109 * const a = BigDecimal.fromStringUnsafe("1.50")
1110 * const b = BigDecimal.fromStringUnsafe("1.5")
1111 * const c = BigDecimal.fromStringUnsafe("2.0")
1112 *
1113 * BigDecimal.Equivalence(a, b) // => true
1114 * BigDecimal.Equivalence(a, c) // => false
1115 * ```
1116 *
1117 * @category instances
1118 * @since 2.0.0
1119 */
1120export const Equivalence: Equ.Equivalence<BigDecimal> = Equ.make((self, that) => compare(self, that) === 0)
1121
1122/**
1123 * Checks whether two `BigDecimal`s are equal.
1124 *
1125 * **When to use**
1126 *
1127 * Use to compare two `BigDecimal` values for numeric equality.
1128 *
1129 * **Example** (Checking decimal equality)
1130 *
1131 * ```ts import.meta.vitest
1132 * import { BigDecimal } from "effect"
1133 *
1134 * const a = BigDecimal.fromStringUnsafe("1.5")
1135 * const b = BigDecimal.fromStringUnsafe("1.50")
1136 * const c = BigDecimal.fromStringUnsafe("2.0")
1137 *
1138 * BigDecimal.equals(a, b) // => true
1139 * BigDecimal.equals(a, c) // => false
1140 * ```
1141 *
1142 * @see {@link Equivalence} for passing decimal equality to APIs that require an `Equivalence`
1143 *
1144 * @category predicates
1145 * @since 2.0.0
1146 */
1147export const equals: {
1148 (that: BigDecimal): (self: BigDecimal) => boolean
1149 (self: BigDecimal, that: BigDecimal): boolean
1150} = dual(2, (self: BigDecimal, that: BigDecimal): boolean => Equivalence(self, that))
1151
1152/**
1153 * Creates a `BigDecimal` from a `bigint` value.
1154 *
1155 * **When to use**
1156 *
1157 * Use to construct an integer `BigDecimal` from a `bigint`.
1158 *
1159 * **Example** (Creating decimals from bigint)
1160 *
1161 * ```ts import.meta.vitest
1162 * import { BigDecimal } from "effect"
1163 *
1164 * const decimal = BigDecimal.fromBigInt(123n)
1165 * decimal // => BigDecimal.fromStringUnsafe("123")
1166 *
1167 * const largeBigInt = BigDecimal.fromBigInt(9007199254740991n)
1168 * largeBigInt // => BigDecimal.fromStringUnsafe("9007199254740991")
1169 * ```
1170 *
1171 * @see {@link make} for constructing a decimal with an explicit scale
1172 *
1173 * @category constructors
1174 * @since 2.0.0
1175 */
1176export const fromBigInt = (n: bigint): BigDecimal => make(n, 0)
1177
1178/**
1179 * Creates a `BigDecimal` from a finite `number`.
1180 *
1181 * **When to use**
1182 *
1183 * Use when you need to convert a trusted finite JavaScript number to a
1184 * `BigDecimal` and want a plain result instead of an `Option`.
1185 *
1186 * **Gotchas**
1187 *
1188 * It is not recommended to convert a floating point number to a decimal
1189 * directly, as the floating point representation may be unexpected. Throws a
1190 * `RangeError` if the number is not finite (`NaN`, `+Infinity` or `-Infinity`).
1191 *
1192 * **Example** (Creating decimals from finite numbers)
1193 *
1194 * ```ts import.meta.vitest
1195 * import { BigDecimal } from "effect"
1196 *
1197 * BigDecimal.fromNumberUnsafe(123) // => BigDecimal.fromBigInt(123n)
1198 * BigDecimal.fromNumberUnsafe(123.456) // => BigDecimal.fromStringUnsafe("123.456")
1199 * ```
1200 *
1201 * @see {@link fromNumber} for returning `Option.none` when the number is not finite
1202 *
1203 * @category constructors
1204 * @since 4.0.0
1205 */
1206export const fromNumberUnsafe = (n: number): BigDecimal => {
1207 return Option.getOrThrowWith(fromNumber(n), () => new RangeError(`Number must be finite, got ${n}`))
1208}
1209
1210/**
1211 * Creates a `BigDecimal` safely from a finite `number`.
1212 *
1213 * **When to use**
1214 *
1215 * Use to convert a finite JavaScript number to a `BigDecimal` without throwing
1216 * on invalid input.
1217 *
1218 * **Details**
1219 *
1220 * Returns `Option.none()` for `NaN`, `+Infinity` or `-Infinity`.
1221 *
1222 * **Gotchas**
1223 *
1224 * It is not recommended to convert a floating point number to a decimal
1225 * directly, as the floating point representation may be unexpected.
1226 *
1227 * **Example** (Creating decimals from numbers safely)
1228 *
1229 * ```ts import.meta.vitest
1230 * import { BigDecimal, Option } from "effect"
1231 *
1232 * BigDecimal.fromNumber(123.456) // => Option.some(BigDecimal.fromStringUnsafe("123.456"))
1233 * BigDecimal.fromNumber(Infinity) // => Option.none()
1234 * ```
1235 *
1236 * @see {@link fromNumberUnsafe} for throwing when the number is not finite
1237 * @see {@link fromString} for parsing decimal strings directly
1238 *
1239 * @category constructors
1240 * @since 2.0.0
1241 */
1242export const fromNumber = (n: number): Option.Option<BigDecimal> => {
1243 if (!Number.isFinite(n)) {
1244 return Option.none()
1245 }
1246
1247 const string = `${n}`
1248 if (string.includes("e")) {
1249 return fromString(string)
1250 }
1251
1252 const [lead, trail = ""] = string.split(".")
1253 return Option.some(make(BigInt(`${lead}${trail}`), trail.length))
1254}
1255
1256/**
1257 * Parses a decimal string into a `BigDecimal` safely.
1258 *
1259 * **When to use**
1260 *
1261 * Use to parse external decimal text without throwing on invalid input.
1262 *
1263 * **Details**
1264 *
1265 * Returns `Option.some` for valid decimal or exponent notation and
1266 * `Option.none` when the string cannot be parsed or would produce an unsafe
1267 * scale. The empty string parses as zero.
1268 *
1269 * **Example** (Parsing decimal strings safely)
1270 *
1271 * ```ts import.meta.vitest
1272 * import { BigDecimal, Option } from "effect"
1273 *
1274 * BigDecimal.fromString("123.456") // => Option.some(BigDecimal.make(123456n, 3))
1275 * BigDecimal.fromString("123.abc") // => Option.none()
1276 * ```
1277 *
1278 * @see {@link fromStringUnsafe} for parsing that throws on invalid input
1279 * @see {@link fromNumber} for converting finite JavaScript numbers
1280 *
1281 * @category constructors
1282 * @since 2.0.0
1283 */
1284export const fromString = (s: string): Option.Option<BigDecimal> => {
1285 if (s === "") {
1286 return Option.some(zero)
1287 }
1288
1289 let base: string
1290 let exp: number
1291 const seperator = s.search(/[eE]/)
1292 if (seperator !== -1) {
1293 const trail = s.slice(seperator + 1)
1294 base = s.slice(0, seperator)
1295 exp = Number(trail)
1296 if (base === "" || !Number.isSafeInteger(exp) || !FINITE_INT_REGEXP.test(trail)) {
1297 return Option.none()
1298 }
1299 } else {
1300 base = s
1301 exp = 0
1302 }
1303
1304 let digits: string
1305 let offset: number
1306 const dot = base.search(/\./)
1307 if (dot !== -1) {
1308 const lead = base.slice(0, dot)
1309 const trail = base.slice(dot + 1)
1310 digits = `${lead}${trail}`
1311 offset = trail.length
1312 } else {
1313 digits = base
1314 offset = 0
1315 }
1316
1317 if (!FINITE_INT_REGEXP.test(digits)) {
1318 return Option.none()
1319 }
1320
1321 const scale = offset - exp
1322 if (!Number.isSafeInteger(scale)) {
1323 return Option.none()
1324 }
1325
1326 return Option.some(make(BigInt(digits), scale))
1327}
1328
1329/**
1330 * Parses a decimal string into a `BigDecimal`, throwing if the string is
1331 * invalid.
1332 *
1333 * **When to use**
1334 *
1335 * Use when you expect decimal text to be valid and want parse errors to throw.
1336 *
1337 * **Details**
1338 *
1339 * Accepts the same syntax as `fromString`. Use `fromString` when invalid input
1340 * should be represented as `Option.none` instead of throwing.
1341 *
1342 * **Example** (Parsing decimal strings unsafely)
1343 *
1344 * ```ts import.meta.vitest
1345 * import { BigDecimal } from "effect"
1346 *
1347 * BigDecimal.fromStringUnsafe("123") // => BigDecimal.fromBigInt(123n)
1348 * BigDecimal.fromStringUnsafe("123.456") // => BigDecimal.make(123456n, 3)
1349 * ```
1350 *
1351 * @see {@link fromString} for returning `Option.none` on invalid input
1352 *
1353 * @category constructors
1354 * @since 4.0.0
1355 */
1356export const fromStringUnsafe = (s: string): BigDecimal => {
1357 return Option.getOrThrowWith(fromString(s), () => new Error(`Invalid numerical string: ${s}`))
1358}
1359
1360/**
1361 * Formats a `BigDecimal` as a string.
1362 *
1363 * **When to use**
1364 *
1365 * Use to render a `BigDecimal` as plain decimal text when possible.
1366 *
1367 * **Details**
1368 *
1369 * The value is normalized before formatting. Scientific notation is used when
1370 * the absolute value of the normalized scale is at least `16`; otherwise plain
1371 * decimal notation is used.
1372 *
1373 * **Example** (Formatting decimals)
1374 *
1375 * ```ts import.meta.vitest
1376 * import { BigDecimal } from "effect"
1377 *
1378 * BigDecimal.format(BigDecimal.fromStringUnsafe("-5")) // => "-5"
1379 * BigDecimal.format(BigDecimal.fromStringUnsafe("123.456")) // => "123.456"
1380 * BigDecimal.format(BigDecimal.fromStringUnsafe("-0.00000123")) // => "-0.00000123"
1381 * ```
1382 *
1383 * @see {@link toExponential} for always rendering scientific notation
1384 *
1385 * @category converting
1386 * @since 2.0.0
1387 */
1388export const format = (n: BigDecimal): string => {
1389 const normalized = normalize(n)
1390 if (Math.abs(normalized.scale) >= 16) {
1391 return toExponential(normalized)
1392 }
1393
1394 const negative = normalized.value < bigint0
1395 const absolute = `${negative ? -normalized.value : normalized.value}`
1396 const digits = normalized.scale > 0
1397 ? absolute.padStart(normalized.scale + 1, "0")
1398 : absolute.padEnd(absolute.length - normalized.scale, "0")
1399 const point = digits.length - normalized.scale
1400 const complete = normalized.scale > 0 ? `${digits.slice(0, point)}.${digits.slice(point)}` : digits
1401 return negative ? `-${complete}` : complete
1402}
1403
1404/**
1405 * Formats a given `BigDecimal` as a `string` in scientific notation.
1406 *
1407 * **When to use**
1408 *
1409 * Use to render a `BigDecimal` in scientific notation.
1410 *
1411 * **Example** (Formatting decimals exponentially)
1412 *
1413 * ```ts import.meta.vitest
1414 * import { BigDecimal } from "effect"
1415 *
1416 * BigDecimal.toExponential(BigDecimal.make(123456n, -5)) // => "1.23456e+10"
1417 * ```
1418 *
1419 * @see {@link format} for plain decimal formatting when possible
1420 *
1421 * @category converting
1422 * @since 3.11.0
1423 */
1424export const toExponential = (n: BigDecimal): string => {
1425 if (isZero(n)) {
1426 return "0e+0"
1427 }
1428
1429 const normalized = normalize(n)
1430 const digits = `${normalized.value}`
1431 const point = normalized.value < bigint0 ? 2 : 1
1432 const head = digits.slice(0, point)
1433 const tail = digits.slice(point)
1434 const exp = tail.length - normalized.scale
1435 return `${head}${tail === "" ? "" : `.${tail}`}e${exp >= 0 ? "+" : ""}${exp}`
1436}
1437
1438/**
1439 * Converts a `BigDecimal` to a JavaScript `number`.
1440 *
1441 * **When to use**
1442 *
1443 * Use when you need a JavaScript number at an interop boundary where precision
1444 * loss is acceptable.
1445 *
1446 * **Gotchas**
1447 *
1448 * This conversion is unsafe because the result can lose integer or fractional
1449 * precision, round to a nearby representable value, or become `Infinity` when
1450 * the decimal cannot be represented as a finite JavaScript `number`.
1451 *
1452 * **Example** (Converting decimals to numbers)
1453 *
1454 * ```ts import.meta.vitest
1455 * import { BigDecimal } from "effect"
1456 *
1457 * BigDecimal.toNumberUnsafe(BigDecimal.fromStringUnsafe("123.456")) // => 123.456
1458 * ```
1459 *
1460 * @see {@link format} for preserving decimal precision as text
1461 *
1462 * @category converting
1463 * @since 4.0.0
1464 */
1465export const toNumberUnsafe = (n: BigDecimal): number => Number(format(n))
1466
1467/**
1468 * Checks whether a given `BigDecimal` is an integer.
1469 *
1470 * **When to use**
1471 *
1472 * Use to test whether a `BigDecimal` has no fractional decimal part.
1473 *
1474 * **Example** (Checking integer decimals)
1475 *
1476 * ```ts import.meta.vitest
1477 * import { BigDecimal } from "effect"
1478 *
1479 * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("0")) // => true
1480 * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1")) // => true
1481 * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1.1")) // => false
1482 * ```
1483 *
1484 * @category predicates
1485 * @since 2.0.0
1486 */
1487export const isInteger = (n: BigDecimal): boolean => normalize(n).scale <= 0
1488
1489/**
1490 * Checks whether a given `BigDecimal` is `0`.
1491 *
1492 * **When to use**
1493 *
1494 * Use to test whether a `BigDecimal` is exactly zero.
1495 *
1496 * **Example** (Checking zero decimals)
1497 *
1498 * ```ts import.meta.vitest
1499 * import { BigDecimal } from "effect"
1500 *
1501 * BigDecimal.isZero(BigDecimal.fromStringUnsafe("0")) // => true
1502 * BigDecimal.isZero(BigDecimal.fromStringUnsafe("1")) // => false
1503 * ```
1504 *
1505 * @category predicates
1506 * @since 2.0.0
1507 */
1508export const isZero = (n: BigDecimal): boolean => n.value === bigint0
1509
1510/**
1511 * Checks whether a given `BigDecimal` is negative.
1512 *
1513 * **When to use**
1514 *
1515 * Use to test whether a `BigDecimal` is less than zero.
1516 *
1517 * **Example** (Checking negative decimals)
1518 *
1519 * ```ts import.meta.vitest
1520 * import { BigDecimal } from "effect"
1521 *
1522 * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("-1")) // => true
1523 * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("0")) // => false
1524 * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("1")) // => false
1525 * ```
1526 *
1527 * @category predicates
1528 * @since 2.0.0
1529 */
1530export const isNegative = (n: BigDecimal): boolean => n.value < bigint0
1531
1532/**
1533 * Checks whether a given `BigDecimal` is positive.
1534 *
1535 * **When to use**
1536 *
1537 * Use to test whether a `BigDecimal` is greater than zero.
1538 *
1539 * **Example** (Checking positive decimals)
1540 *
1541 * ```ts import.meta.vitest
1542 * import { BigDecimal } from "effect"
1543 *
1544 * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("-1")) // => false
1545 * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("0")) // => false
1546 * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("1")) // => true
1547 * ```
1548 *
1549 * @category predicates
1550 * @since 2.0.0
1551 */
1552export const isPositive = (n: BigDecimal): boolean => n.value > bigint0
1553
1554const isBigDecimalArgs = (args: IArguments) => isBigDecimal(args[0])
1555
1556/**
1557 * Rounding modes for `BigDecimal`.
1558 *
1559 * **When to use**
1560 *
1561 * Use with `round` to choose how discarded digits affect a `BigDecimal`
1562 * rounded to a target scale.
1563 *
1564 * **Details**
1565 *
1566 * - `ceil`: round towards positive infinity
1567 * - `floor`: round towards negative infinity
1568 * - `to-zero`: round towards zero
1569 * - `from-zero`: round away from zero
1570 * - `half-ceil`: round to the nearest neighbor; if equidistant round towards positive infinity
1571 * - `half-floor`: round to the nearest neighbor; if equidistant round towards negative infinity
1572 * - `half-to-zero`: round to the nearest neighbor; if equidistant round towards zero
1573 * - `half-from-zero`: round to the nearest neighbor; if equidistant round away from zero
1574 * - `half-even`: round to the nearest neighbor; if equidistant round to the neighbor with an even digit
1575 * - `half-odd`: round to the nearest neighbor; if equidistant round to the neighbor with an odd digit
1576 *
1577 * @see {@link round} for configurable rounding with a `RoundingMode`
1578 * @see {@link ceil} for fixed rounding toward positive infinity
1579 * @see {@link floor} for fixed rounding toward negative infinity
1580 * @see {@link truncate} for fixed rounding toward zero
1581 *
1582 * @category math
1583 * @since 3.16.0
1584 */
1585export type RoundingMode =
1586 | "ceil"
1587 | "floor"
1588 | "to-zero"
1589 | "from-zero"
1590 | "half-ceil"
1591 | "half-floor"
1592 | "half-to-zero"
1593 | "half-from-zero"
1594 | "half-even"
1595 | "half-odd"
1596
1597/**
1598 * Computes a rounded `BigDecimal` at the given scale with the specified rounding mode.
1599 *
1600 * **When to use**
1601 *
1602 * Use to round a decimal at a requested scale with an explicit rounding mode.
1603 *
1604 * **Example** (Rounding decimals)
1605 *
1606 * ```ts import.meta.vitest
1607 * import { BigDecimal } from "effect"
1608 *
1609 * const positive = BigDecimal.round(BigDecimal.fromStringUnsafe("145"), { mode: "from-zero", scale: -1 })
1610 * positive // => BigDecimal.fromBigInt(150n)
1611 *
1612 * const negative = BigDecimal.round(BigDecimal.fromStringUnsafe("-14.5"))
1613 * negative // => BigDecimal.fromBigInt(-15n)
1614 * ```
1615 *
1616 * @see {@link ceil} for fixed rounding toward positive infinity
1617 * @see {@link floor} for fixed rounding toward negative infinity
1618 * @see {@link truncate} for fixed rounding toward zero
1619 *
1620 * @category math
1621 * @since 3.16.0
1622 */
1623export const round: {
1624 (options: { scale?: number; mode?: RoundingMode }): (self: BigDecimal) => BigDecimal
1625 (n: BigDecimal, options?: { scale?: number; mode?: RoundingMode }): BigDecimal
1626} = dual(isBigDecimalArgs, (self: BigDecimal, options?: { scale?: number; mode?: RoundingMode }): BigDecimal => {
1627 const mode = options?.mode ?? "half-from-zero"
1628 const scale = options?.scale ?? 0
1629
1630 switch (mode) {
1631 case "ceil":
1632 return ceil(self, scale)
1633
1634 case "floor":
1635 return floor(self, scale)
1636
1637 case "to-zero":
1638 return truncate(self, scale)
1639
1640 case "from-zero":
1641 return (isPositive(self) ? ceil(self, scale) : floor(self, scale))
1642
1643 case "half-ceil":
1644 return floor(sum(self, make(bigint5, scale + 1)), scale)
1645
1646 case "half-floor":
1647 return ceil(sum(self, make(bigint_5, scale + 1)), scale)
1648
1649 case "half-to-zero":
1650 return isNegative(self)
1651 ? floor(sum(self, make(bigint5, scale + 1)), scale)
1652 : ceil(sum(self, make(bigint_5, scale + 1)), scale)
1653
1654 case "half-from-zero":
1655 return isNegative(self)
1656 ? ceil(sum(self, make(bigint_5, scale + 1)), scale)
1657 : floor(sum(self, make(bigint5, scale + 1)), scale)
1658 }
1659
1660 const halfCeil = floor(sum(self, make(bigint5, scale + 1)), scale)
1661 const halfFloor = ceil(sum(self, make(bigint_5, scale + 1)), scale)
1662 const digit = digitAt(halfCeil, scale)
1663
1664 switch (mode) {
1665 case "half-even":
1666 return equals(halfCeil, halfFloor) ? halfCeil : (digit % bigint2 === bigint0) ? halfCeil : halfFloor
1667
1668 case "half-odd":
1669 return equals(halfCeil, halfFloor) ? halfCeil : (digit % bigint2 === bigint0) ? halfFloor : halfCeil
1670 }
1671})
1672
1673/**
1674 * Computes a truncated `BigDecimal` at the given scale. This removes fractional digits beyond the scale,
1675 * rounding toward zero.
1676 *
1677 * **When to use**
1678 *
1679 * Use when you need to discard fractional digits beyond a scale rather than
1680 * round half up, half down, or toward an infinity.
1681 *
1682 * **Example** (Truncating decimals)
1683 *
1684 * ```ts import.meta.vitest
1685 * import { BigDecimal } from "effect"
1686 *
1687 * BigDecimal.truncate(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(140n)
1688 * BigDecimal.truncate(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n)
1689 * ```
1690 *
1691 * @see {@link round} for configurable rounding modes
1692 * @see {@link ceil} for rounding toward positive infinity
1693 * @see {@link floor} for rounding toward negative infinity
1694 *
1695 * @category math
1696 * @since 3.16.0
1697 */
1698export const truncate: {
1699 (scale: number): (self: BigDecimal) => BigDecimal
1700 (self: BigDecimal, scale?: number): BigDecimal
1701} = dual(isBigDecimalArgs, (self: BigDecimal, scale: number = 0): BigDecimal => {
1702 if (self.scale <= scale) {
1703 return self
1704 }
1705
1706 // BigInt division truncates towards zero
1707 return make(self.value / (bigint10 ** BigInt(self.scale - scale)), scale)
1708})
1709
1710/**
1711 * Computes the ceiling of a `BigDecimal` at the given scale.
1712 *
1713 * **When to use**
1714 *
1715 * Use to round a decimal toward positive infinity at a requested scale.
1716 *
1717 * **Details**
1718 *
1719 * The default scale is `0`. Positive scales keep digits to the right of the
1720 * decimal point, and negative scales round positions to the left of the decimal
1721 * point.
1722 *
1723 * @see {@link floor} for rounding toward negative infinity
1724 * @see {@link truncate} for rounding toward zero
1725 * @see {@link round} for configurable rounding modes
1726 *
1727 * **Example** (Rounding decimals up)
1728 *
1729 * ```ts import.meta.vitest
1730 * import { BigDecimal } from "effect"
1731 *
1732 * BigDecimal.ceil(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(150n)
1733 * BigDecimal.ceil(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n)
1734 * ```
1735 *
1736 * @category math
1737 * @since 3.16.0
1738 */
1739export const ceil: {
1740 (scale: number): (self: BigDecimal) => BigDecimal
1741 (self: BigDecimal, scale?: number): BigDecimal
1742} = dual(isBigDecimalArgs, (self: BigDecimal, scale: number = 0): BigDecimal => {
1743 const truncated = truncate(self, scale)
1744
1745 if (isPositive(self) && isLessThan(truncated, self)) {
1746 return sum(truncated, make(bigint1, scale))
1747 }
1748
1749 return truncated
1750})
1751
1752/**
1753 * Internal function used by `round` for `half-even` and `half-odd` rounding modes.
1754 *
1755 * Returns the digit at the position of the given `scale` within the `BigDecimal`.
1756 *
1757 * @internal
1758 */
1759export const digitAt: {
1760 (scale: number): (self: BigDecimal) => bigint
1761 (self: BigDecimal, scale: number): bigint
1762} = dual(2, (self: BigDecimal, scale: number): bigint => {
1763 if (self.scale < scale) {
1764 return bigint0
1765 }
1766
1767 const scaled = self.value / (bigint10 ** BigInt(self.scale - scale))
1768 return scaled % bigint10
1769})
1770
1771/**
1772 * Computes the floor of a `BigDecimal` at the given scale.
1773 *
1774 * **When to use**
1775 *
1776 * Use to round a decimal toward negative infinity at a requested scale.
1777 *
1778 * **Example** (Rounding decimals down)
1779 *
1780 * ```ts import.meta.vitest
1781 * import { BigDecimal } from "effect"
1782 *
1783 * BigDecimal.floor(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(140n)
1784 * BigDecimal.floor(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-15n)
1785 * ```
1786 *
1787 * @see {@link ceil} for rounding toward positive infinity
1788 * @see {@link truncate} for rounding toward zero
1789 * @see {@link round} for configurable rounding modes
1790 *
1791 * @category math
1792 * @since 3.16.0
1793 */
1794export const floor: {
1795 (scale: number): (self: BigDecimal) => BigDecimal
1796 (self: BigDecimal, scale?: number): BigDecimal
1797} = dual(isBigDecimalArgs, (self: BigDecimal, scale: number = 0): BigDecimal => {
1798 const truncated = truncate(self, scale)
1799
1800 if (isNegative(self) && isGreaterThan(truncated, self)) {
1801 return sum(truncated, make(bigint_1, scale))
1802 }
1803
1804 return truncated
1805})