@wzo/calc 0.0.1

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.
@@ -0,0 +1,442 @@
1
+ // Precision math: internally represents decimal numbers using BigInt
2
+ // A number = sign * digits / 10^exp (exp >= 0 denotes the number of decimal places)
3
+
4
+ /** Internal decimal representation: a number = `sign * digits / 10^exp` */
5
+ export interface IDecimal {
6
+ /** Sign: `1` for positive, `-1` for negative. */
7
+ sign: 1 | -1
8
+ /** Significant digits as a non-negative integer (decimal point removed). */
9
+ digits: bigint
10
+ /** Number of decimal places (negative exponent of `10`). */
11
+ exp: number
12
+ }
13
+
14
+ const TEN = 10n
15
+
16
+ // Cache powers of 10 to avoid repeatedly constructing large BigInts like 10^precision in division
17
+ // (inspired by decimal.js's "precompute constants, no recomputation" approach; grows on demand, reused across calls)
18
+ const POW10: bigint[] = [1n]
19
+ const pow10 = (n: number): bigint => {
20
+ for (let i = POW10.length; i <= n; i++) POW10[i] = POW10[i - 1]! * TEN
21
+ return POW10[n]!
22
+ }
23
+
24
+ // ───── Module-level constant regexes ─────
25
+ const RE_SCI = /^([+-]?\d+(?:\.\d+)?)e([+-]?\d+)$/i
26
+ const RE_NUM = /^\d+(?:\.\d+)?$/
27
+ const RE_TRAIL_ZERO = /0+$/
28
+
29
+ /** Shifts the decimal point of `"1.23"` left or right by `n` positions → string. */
30
+ const shiftDecimalPoint = (s: string, n: number): string => {
31
+ let sign = ''
32
+ if (s.startsWith('-')) {
33
+ sign = '-'
34
+ s = s.slice(1)
35
+ } else if (s.startsWith('+')) {
36
+ s = s.slice(1)
37
+ }
38
+ const dot = s.indexOf('.')
39
+ const allDigits = dot === -1 ? s : s.slice(0, dot) + s.slice(dot + 1)
40
+ const dotPos = (dot === -1 ? s.length : dot) + n
41
+ if (dotPos <= 0) return `${sign}0.${'0'.repeat(-dotPos)}${allDigits}`
42
+ if (dotPos >= allDigits.length) return sign + allDigits + '0'.repeat(dotPos - allDigits.length)
43
+ return `${sign}${allDigits.slice(0, dotPos)}.${allDigits.slice(dotPos)}`
44
+ }
45
+
46
+ /**
47
+ * Parses a string, number, or bigint into the internal {@link IDecimal} representation.
48
+ *
49
+ * Supports standard decimals (`"1.23"`, `-0.5`) and scientific notation (`"1.2e-3"`).
50
+ *
51
+ * @param input Value to parse, e.g. `"3.14"`, `42`, `-1n`, `"1e10"`
52
+ * @returns Parsed {@link IDecimal} (`digits` is always >= 0; sign is carried by `sign`)
53
+ * @throws When the string is not a valid number
54
+ * @example
55
+ * parse('1.23') // { sign: 1, digits: 123n, exp: 2 }
56
+ * parse('-5') // { sign: -1, digits: 5n, exp: 0 }
57
+ */
58
+ export const parse = (input: string | number | bigint): IDecimal => {
59
+ if (typeof input === 'bigint') return { sign: input < 0n ? -1 : 1, digits: input < 0n ? -input : input, exp: 0 }
60
+ let s = String(input).trim()
61
+ if (s === '' || s === '-' || s === '+') throw new Error(`Cannot parse number: "${input}"`)
62
+ const expMatch = RE_SCI.exec(s)
63
+ if (expMatch) {
64
+ const base = expMatch[1]!
65
+ const e = Number.parseInt(expMatch[2]!, 10)
66
+ s = shiftDecimalPoint(base, e)
67
+ }
68
+ let sign: 1 | -1 = 1
69
+ if (s.startsWith('+')) {
70
+ s = s.slice(1)
71
+ } else if (s.startsWith('-')) {
72
+ sign = -1
73
+ s = s.slice(1)
74
+ }
75
+ if (!RE_NUM.test(s)) throw new Error(`Cannot parse number: "${input}"`)
76
+ const dot = s.indexOf('.')
77
+ const digitStr = dot === -1 ? s : s.slice(0, dot) + s.slice(dot + 1)
78
+ const exp = dot === -1 ? 0 : s.length - dot - 1
79
+ const digits = BigInt(digitStr || '0')
80
+ if (digits === 0n) return { sign: 1, digits: 0n, exp: 0 }
81
+ return { sign, digits, exp }
82
+ }
83
+
84
+ /**
85
+ * Converts an {@link IDecimal} back to a canonical decimal string (trailing zeros are stripped automatically).
86
+ *
87
+ * @param d The {@link IDecimal} to format
88
+ * @returns Canonical string; zero is always returned as `"0"`
89
+ * @example
90
+ * format({ sign: 1, digits: 1230n, exp: 3 }) // '1.23'
91
+ */
92
+ export const format = (d: IDecimal): string => {
93
+ if (d.digits === 0n) return '0'
94
+ let raw = d.digits.toString()
95
+ if (d.exp > 0) {
96
+ if (raw.length <= d.exp) raw = '0'.repeat(d.exp - raw.length + 1) + raw
97
+ const intPart = raw.slice(0, raw.length - d.exp)
98
+ const fracPart = raw.slice(raw.length - d.exp).replace(RE_TRAIL_ZERO, '')
99
+ raw = fracPart ? `${intPart}.${fracPart}` : intPart
100
+ }
101
+ return (d.sign < 0 ? '-' : '') + raw
102
+ }
103
+
104
+ /** Aligns the digits of two numbers to the same exp (using the larger of the two). */
105
+ const align = (a: IDecimal, b: IDecimal): { aN: bigint, bN: bigint, exp: number } => {
106
+ if (a.exp === b.exp) return { aN: a.digits, bN: b.digits, exp: a.exp }
107
+ if (a.exp > b.exp) return { aN: a.digits, bN: b.digits * TEN ** BigInt(a.exp - b.exp), exp: a.exp }
108
+ return { aN: a.digits * TEN ** BigInt(b.exp - a.exp), bN: b.digits, exp: b.exp }
109
+ }
110
+
111
+ const fromBig = (n: bigint, exp: number): IDecimal => {
112
+ if (n === 0n) return { sign: 1, digits: 0n, exp: 0 }
113
+ return { sign: n < 0n ? -1 : 1, digits: n < 0n ? -n : n, exp }
114
+ }
115
+
116
+ // ───── Fast path using integer-scaled numbers ─────
117
+ // When both operands can be represented as safe integer mantissas (value within 2^53, decimal places <= 15),
118
+ // use integer arithmetic on `number` for near-native speed; otherwise return null and fall back to the BigInt implementation below.
119
+ // Critical: scaling is done only as "integer × 10^k" — never multiply by a floating-point decimal (e.g. 0.29 * 100 = 28.999… would be wrong).
120
+ const MAX_SAFE = Number.MAX_SAFE_INTEGER
121
+ const POW10F = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15]
122
+
123
+ interface IFastNum {
124
+ /** Safe integer mantissa (with sign). */
125
+ int: number
126
+ /** Number of decimal places; actual value = int / 10^scale. */
127
+ scale: number
128
+ }
129
+
130
+ /** Parses input into `int / 10^scale` (int is a safe integer); returns `null` when safe representation is not possible, signalling the caller to fall back. */
131
+ const parseFast = (input: string | number | bigint): IFastNum | null => {
132
+ const s = typeof input === 'string' ? input : String(input)
133
+ let i = 0
134
+ let neg = false
135
+ const c0 = s.charCodeAt(0)
136
+ if (c0 === 45) { // '-'
137
+ neg = true
138
+ i = 1
139
+ } else if (c0 === 43) { // '+'
140
+ i = 1
141
+ }
142
+ let hasDot = false
143
+ let scale = 0
144
+ let digits = ''
145
+ for (; i < s.length; i++) {
146
+ const c = s.charCodeAt(i)
147
+ if (c === 46) { // '.'
148
+ if (hasDot || digits === '') return null // multiple dots or no integer part (".5") → fall back to strict parse
149
+ hasDot = true
150
+ } else if (c >= 48 && c <= 57) { // '0'..'9'
151
+ digits += s[i]
152
+ if (hasDot) scale++
153
+ } else {
154
+ return null // 'e' (scientific notation), whitespace, or illegal character → fall back
155
+ }
156
+ }
157
+ if (digits === '' || (hasDot && scale === 0)) return null // empty or trailing dot ("1.") → fall back
158
+ if (digits.length > 15 || scale >= POW10F.length) return null // exceeds safe integer range or too many decimal places → fall back
159
+ const int = Number(digits)
160
+ return { int: neg ? -int : int, scale }
161
+ }
162
+
163
+ /** Formats `int / 10^scale` as a canonical string, output identical to {@link format} (trailing zeros stripped, zero returns `'0'`). */
164
+ const fmtIntFast = (int: number, scale: number): string => {
165
+ if (int === 0) return '0'
166
+ const neg = int < 0
167
+ let s = (neg ? -int : int).toString()
168
+ if (scale === 0) return neg ? `-${s}` : s
169
+ if (s.length <= scale) s = '0'.repeat(scale - s.length + 1) + s
170
+ const cut = s.length - scale
171
+ let end = s.length
172
+ while (end > cut && s.charCodeAt(end - 1) === 48) end-- // strip trailing zeros
173
+ const out = end === cut ? s.slice(0, cut) : `${s.slice(0, cut)}.${s.slice(cut, end)}`
174
+ return neg ? `-${out}` : out
175
+ }
176
+
177
+ /** Fast path for addition/subtraction: `op` is `1` (add) or `-1` (subtract); returns `null` when unavailable. */
178
+ const fastAddSub = (a: string | number | bigint, b: string | number | bigint, op: 1 | -1): string | null => {
179
+ const pa = parseFast(a)
180
+ if (pa === null) return null
181
+ const pb = parseFast(b)
182
+ if (pb === null) return null
183
+ const scale = pa.scale > pb.scale ? pa.scale : pb.scale
184
+ const ia = pa.int * POW10F[scale - pa.scale]!
185
+ const ib = pb.int * POW10F[scale - pb.scale]!
186
+ if (ia > MAX_SAFE || ia < -MAX_SAFE || ib > MAX_SAFE || ib < -MAX_SAFE) return null
187
+ const r = ia + op * ib
188
+ if (r > MAX_SAFE || r < -MAX_SAFE) return null
189
+ return fmtIntFast(r, scale)
190
+ }
191
+
192
+ /** Fast path for multiplication; returns `null` when unavailable. */
193
+ const fastMul = (a: string | number | bigint, b: string | number | bigint): string | null => {
194
+ const pa = parseFast(a)
195
+ if (pa === null) return null
196
+ const pb = parseFast(b)
197
+ if (pb === null) return null
198
+ const r = pa.int * pb.int
199
+ if (r > MAX_SAFE || r < -MAX_SAFE) return null
200
+ return fmtIntFast(r, pa.scale + pb.scale)
201
+ }
202
+
203
+ // ───── Public arithmetic ─────
204
+ /**
205
+ * High-precision addition `a + b`, computed entirely with BigInt — no floating-point errors.
206
+ *
207
+ * @param a Augend
208
+ * @param b Addend
209
+ * @returns Canonical string of the sum
210
+ * @example
211
+ * add('0.1', '0.2') // '0.3'
212
+ */
213
+ export const add = (a: string | number | bigint, b: string | number | bigint): string => {
214
+ const fast = fastAddSub(a, b, 1)
215
+ if (fast !== null) return fast
216
+ const da = parse(a)
217
+ const db = parse(b)
218
+ const { aN, bN, exp } = align(da, db)
219
+ const r = BigInt(da.sign) * aN + BigInt(db.sign) * bN
220
+ return format(fromBig(r, exp))
221
+ }
222
+
223
+ /**
224
+ * High-precision subtraction `a - b`.
225
+ *
226
+ * @param a Minuend
227
+ * @param b Subtrahend
228
+ * @returns Canonical string of the difference
229
+ * @example
230
+ * sub('0.3', '0.1') // '0.2'
231
+ */
232
+ export const sub = (a: string | number | bigint, b: string | number | bigint): string => {
233
+ const fast = fastAddSub(a, b, -1)
234
+ if (fast !== null) return fast
235
+ const da = parse(a)
236
+ const db = parse(b)
237
+ const { aN, bN, exp } = align(da, db)
238
+ const r = BigInt(da.sign) * aN - BigInt(db.sign) * bN
239
+ return format(fromBig(r, exp))
240
+ }
241
+
242
+ /**
243
+ * High-precision multiplication `a * b`.
244
+ *
245
+ * @param a Factor
246
+ * @param b Factor
247
+ * @returns Canonical string of the product
248
+ * @example
249
+ * mul('0.1', '0.2') // '0.02'
250
+ */
251
+ export const mul = (a: string | number | bigint, b: string | number | bigint): string => {
252
+ const fast = fastMul(a, b)
253
+ if (fast !== null) return fast
254
+ const da = parse(a)
255
+ const db = parse(b)
256
+ const sign = (da.sign * db.sign) as 1 | -1
257
+ const digits = da.digits * db.digits
258
+ const exp = da.exp + db.exp
259
+ if (digits === 0n) return '0'
260
+ return format({ sign, digits, exp })
261
+ }
262
+
263
+ /**
264
+ * High-precision division `a / b`, result rounded to `precision` decimal places (half-up).
265
+ *
266
+ * @param a Dividend
267
+ * @param b Divisor
268
+ * @param precision Maximum decimal places to retain, defaults to `50`
269
+ * @returns Canonical string of the quotient
270
+ * @throws When the divisor is zero
271
+ * @example
272
+ * div('1', '3') // '0.333...' (up to 50 places)
273
+ * div('1', '3', 4) // '0.3333'
274
+ */
275
+ export const div = (a: string | number | bigint, b: string | number | bigint, precision: number = 50): string => {
276
+ const da = parse(a)
277
+ const db = parse(b)
278
+ if (db.digits === 0n) throw new Error('Division by zero')
279
+ if (da.digits === 0n) return '0'
280
+ const sign = (da.sign * db.sign) as 1 | -1
281
+ // Let result = a / b = (da.digits * 10^-da.exp) / (db.digits * 10^-db.exp)
282
+ // = (da.digits / db.digits) * 10^(db.exp - da.exp)
283
+ // Fast path: if the division is exact the result is a finite decimal; return it directly
284
+ // without scaling to precision+1 digits and doing large-integer division + rounding
285
+ // (analogous to decimal.js "early exit when remainder is zero")
286
+ if (da.digits % db.digits === 0n) {
287
+ const q = da.digits / db.digits
288
+ const e = da.exp - db.exp // result = q * 10^(-e)
289
+ const d: IDecimal = e >= 0 ? { sign, digits: q, exp: e } : { sign, digits: q * pow10(-e), exp: 0 }
290
+ return format(d)
291
+ }
292
+ // We want q = result * 10^(precision + 1) ⇒ shift = precision + 1 + db.exp - da.exp
293
+ const shift = precision + 1 + db.exp - da.exp
294
+ let num = da.digits
295
+ let den = db.digits
296
+ if (shift >= 0) num = num * pow10(shift)
297
+ else den = den * pow10(-shift)
298
+ const q = num / den
299
+ // q now represents result * 10^(precision+1); inspect the last digit to apply rounding
300
+ const rounded = q % TEN >= 5n ? q / TEN + 1n : q / TEN
301
+ return format(fromBig(sign === 1 ? rounded : -rounded, precision))
302
+ }
303
+
304
+ // ───── Utilities ─────
305
+ /**
306
+ * Compares two numbers numerically.
307
+ *
308
+ * @param a Left operand
309
+ * @param b Right operand
310
+ * @returns `1` if `a > b`, `-1` if `a < b`, `0` if equal
311
+ * @example
312
+ * cmp('0.1', '0.2') // -1
313
+ * cmp('2', '2.0') // 0
314
+ */
315
+ export const cmp = (a: string | number | bigint, b: string | number | bigint): number => {
316
+ const da = parse(a)
317
+ const db = parse(b)
318
+ if (da.digits === 0n && db.digits === 0n) return 0
319
+ if (da.sign !== db.sign) return da.sign < db.sign ? -1 : 1
320
+ const { aN, bN } = align(da, db)
321
+ if (aN === bN) return 0
322
+ const signed = da.sign === 1 ? (aN > bN ? 1 : -1) : (aN > bN ? -1 : 1)
323
+ return signed
324
+ }
325
+
326
+ /**
327
+ * Returns the negation `-a`.
328
+ *
329
+ * @param a Input value
330
+ * @returns Canonical string of the negation (`0` always returns `'0'`)
331
+ * @example
332
+ * neg('1.5') // '-1.5'
333
+ * neg('-2') // '2'
334
+ */
335
+ export const neg = (a: string | number | bigint): string => {
336
+ const d = parse(a)
337
+ if (d.digits === 0n) return '0'
338
+ return format({ ...d, sign: -d.sign as 1 | -1 })
339
+ }
340
+
341
+ /**
342
+ * Returns the absolute value `|a|`.
343
+ *
344
+ * @param a Input value
345
+ * @returns Canonical string of the absolute value
346
+ * @example
347
+ * abs('-3.14') // '3.14'
348
+ */
349
+ export const abs = (a: string | number | bigint): string => {
350
+ const d = parse(a)
351
+ return format({ ...d, sign: 1 })
352
+ }
353
+
354
+ /**
355
+ * Truncates to N decimal places by discarding excess digits — **no rounding**.
356
+ *
357
+ * @param a Input value
358
+ * @param decimals Number of decimal places to keep
359
+ * @returns Canonical string after truncation
360
+ * @example
361
+ * truncate('1.2349', 2) // '1.23'
362
+ * truncate('1.99', 0) // '1'
363
+ */
364
+ export const truncate = (a: string | number | bigint, decimals: number): string => {
365
+ const d = parse(a)
366
+ if (d.exp <= decimals) return format(d)
367
+ const drop = d.exp - decimals
368
+ const newDigits = d.digits / TEN ** BigInt(drop)
369
+ if (newDigits === 0n) return '0'
370
+ return format({ sign: d.sign, digits: newDigits, exp: decimals })
371
+ }
372
+
373
+ /**
374
+ * Rounds to N decimal places using half-up rounding (rounds up when digit `>= 0.5`).
375
+ *
376
+ * @param a Input value
377
+ * @param decimals Number of decimal places to keep
378
+ * @returns Canonical string after rounding
379
+ * @example
380
+ * roundHalfUp('1.235', 2) // '1.24'
381
+ * roundHalfUp('1.234', 2) // '1.23'
382
+ */
383
+ export const roundHalfUp = (a: string | number | bigint, decimals: number): string => {
384
+ const d = parse(a)
385
+ if (d.exp <= decimals) return format(d)
386
+ const drop = d.exp - decimals
387
+ const divisor = TEN ** BigInt(drop)
388
+ const halved = TEN ** BigInt(drop - 1) * 5n
389
+ const remainder = d.digits % divisor
390
+ let q = d.digits / divisor
391
+ if (remainder >= halved) q += 1n
392
+ if (q === 0n) return '0'
393
+ return format({ sign: d.sign, digits: q, exp: decimals })
394
+ }
395
+
396
+ /**
397
+ * Rounds away from zero to N decimal places (rounds up whenever there is any remainder).
398
+ *
399
+ * @param a Input value
400
+ * @param decimals Number of decimal places to keep
401
+ * @returns Canonical string after rounding
402
+ * @example
403
+ * roundCeil('1.231', 2) // '1.24'
404
+ * roundCeil('-1.231', 2) // '-1.24'
405
+ */
406
+ export const roundCeil = (a: string | number | bigint, decimals: number): string => {
407
+ const d = parse(a)
408
+ if (d.exp <= decimals) return format(d)
409
+ const drop = d.exp - decimals
410
+ const divisor = TEN ** BigInt(drop)
411
+ const remainder = d.digits % divisor
412
+ let q = d.digits / divisor
413
+ if (remainder !== 0n) q += 1n
414
+ if (q === 0n) return '0'
415
+ return format({ sign: d.sign, digits: q, exp: decimals })
416
+ }
417
+
418
+ /**
419
+ * Banker's rounding (round half to even): when the discarded portion is exactly `0.5`,
420
+ * rounds to the nearest even digit.
421
+ *
422
+ * @param a Input value
423
+ * @param decimals Number of decimal places to keep
424
+ * @returns Canonical string after rounding
425
+ * @example
426
+ * roundBanker('0.5', 0) // '0'
427
+ * roundBanker('1.5', 0) // '2'
428
+ * roundBanker('2.5', 0) // '2'
429
+ */
430
+ export const roundBanker = (a: string | number | bigint, decimals: number): string => {
431
+ const d = parse(a)
432
+ if (d.exp <= decimals) return format(d)
433
+ const drop = d.exp - decimals
434
+ const divisor = TEN ** BigInt(drop)
435
+ const halved = TEN ** BigInt(drop - 1) * 5n
436
+ const remainder = d.digits % divisor
437
+ let q = d.digits / divisor
438
+ if (remainder > halved) q += 1n
439
+ else if (remainder === halved && q % 2n === 1n) q += 1n
440
+ if (q === 0n) return '0'
441
+ return format({ sign: d.sign, digits: q, exp: decimals })
442
+ }
@@ -0,0 +1,126 @@
1
+ // Standalone arithmetic functions:
2
+ // add/sub/mul/div → return number (convenient for interop with native JS numbers)
3
+ // addStr/subStr/mulStr/divStr → return string (preserves full precision)
4
+ // Errors (invalid arguments, etc.) are thrown directly; use the calc expression form if you need a fallback
5
+
6
+ import type { IGlobalConfig, IPrecisionOption } from './config'
7
+ import { configWithPrecision } from './config'
8
+ import * as precision from './precision'
9
+
10
+ type Val = string | number | bigint
11
+
12
+ const reduce = (op: (a: string, b: Val) => string, args: Val[]): string => {
13
+ if (args.length === 0) return '0'
14
+ let r = String(args[0])
15
+ for (let i = 1; i < args.length; i++) r = op(r, args[i]!)
16
+ return r
17
+ }
18
+
19
+ /** Extracts a per-call precision option from the end of a variadic argument list (last element is an object ⇒ treated as {@link IPrecisionOption}; Val is always string/number/bigint so there is no ambiguity). */
20
+ const splitPrecision = (args: Array<Val | IPrecisionOption>): [Val[], IPrecisionOption?] => {
21
+ const last = args.at(-1)
22
+ if (last != null && typeof last === 'object') {
23
+ return [args.slice(0, -1) as Val[], last as IPrecisionOption]
24
+ }
25
+ return [args as Val[], undefined]
26
+ }
27
+
28
+ /** Division using the precision from the given config (shared by the default {@link div} export and per-call precision entry points). */
29
+ export const divWith = (cfg: IGlobalConfig, ...args: Val[]): number =>
30
+ Number(reduce((a, b) => precision.div(a, b, cfg._precision), args))
31
+ /** Division using the given config precision, string-returning variant (shared implementation). */
32
+ export const divStrWith = (cfg: IGlobalConfig, ...args: Val[]): string =>
33
+ reduce((a, b) => precision.div(a, b, cfg._precision), args)
34
+
35
+ /**
36
+ * High-precision addition, result converted to `number` (convenient for interop with native numbers).
37
+ *
38
+ * @param args Any number of addends, accumulated left to right
39
+ * @returns Sum (`number`)
40
+ * @example
41
+ * add(0.1, 0.2) // 0.3
42
+ * add(1, 2, 3, 4) // 10
43
+ */
44
+ export const add = (...args: Val[]): number => Number(reduce(precision.add, args))
45
+ /**
46
+ * High-precision subtraction, result as `number`: `args[0] - args[1] - args[2] ...`.
47
+ *
48
+ * @param args Minuend followed by subtrahends
49
+ * @returns Difference (`number`)
50
+ * @example
51
+ * sub(0.3, 0.1) // 0.2
52
+ */
53
+ export const sub = (...args: Val[]): number => Number(reduce(precision.sub, args))
54
+ /**
55
+ * High-precision multiplication, result as `number`, factors multiplied left to right.
56
+ *
57
+ * @param args Any number of factors
58
+ * @returns Product (`number`)
59
+ * @example
60
+ * mul(0.1, 0.2) // 0.02
61
+ */
62
+ export const mul = (...args: Val[]): number => Number(reduce(precision.mul, args))
63
+ /**
64
+ * High-precision division, result as `number`: `args[0] / args[1] / args[2] ...`.
65
+ *
66
+ * Precision defaults to the global `_precision`; pass `{ _precision }` as the **last** argument
67
+ * to override it for this call only (does not affect the global config).
68
+ *
69
+ * @param args Dividend and divisors, with an optional {@link IPrecisionOption} at the end
70
+ * @returns Quotient (`number`)
71
+ * @example
72
+ * div(1, 3) // 0.333... (global precision)
73
+ * div(100, 3, { _precision: 5 }) // 33.33333
74
+ */
75
+ export function div(...args: Val[]): number
76
+ export function div(...args: [...Val[], IPrecisionOption]): number
77
+ export function div(...args: Array<Val | IPrecisionOption>): number {
78
+ const [values, opt] = splitPrecision(args)
79
+ return divWith(configWithPrecision(opt), ...values)
80
+ }
81
+
82
+ /**
83
+ * High-precision addition, result returned as `string` (no precision loss; suitable for monetary values).
84
+ *
85
+ * @param args Any number of addends
86
+ * @returns Sum (`string`)
87
+ * @example
88
+ * addStr('0.1', '0.2') // '0.3'
89
+ */
90
+ export const addStr = (...args: Val[]): string => reduce(precision.add, args)
91
+ /**
92
+ * High-precision subtraction, result as `string`: `args[0] - args[1] - ...`.
93
+ *
94
+ * @param args Minuend followed by subtrahends
95
+ * @returns Difference (`string`)
96
+ * @example
97
+ * subStr('0.3', '0.1') // '0.2'
98
+ */
99
+ export const subStr = (...args: Val[]): string => reduce(precision.sub, args)
100
+ /**
101
+ * High-precision multiplication, result as `string`, factors multiplied left to right.
102
+ *
103
+ * @param args Any number of factors
104
+ * @returns Product (`string`)
105
+ * @example
106
+ * mulStr('0.1', '0.2') // '0.02'
107
+ */
108
+ export const mulStr = (...args: Val[]): string => reduce(precision.mul, args)
109
+ /**
110
+ * High-precision division, result as `string`: `args[0] / args[1] / ...`.
111
+ *
112
+ * Precision defaults to the global `_precision`; pass `{ _precision }` as the **last** argument
113
+ * to override it for this call only (does not affect the global config).
114
+ *
115
+ * @param args Dividend and divisors, with an optional {@link IPrecisionOption} at the end
116
+ * @returns Quotient (`string`)
117
+ * @example
118
+ * divStr('1', '3') // '0.333...' (global precision)
119
+ * divStr('100', '3', { _precision: 5 }) // '33.33333'
120
+ */
121
+ export function divStr(...args: Val[]): string
122
+ export function divStr(...args: [...Val[], IPrecisionOption]): string
123
+ export function divStr(...args: Array<Val | IPrecisionOption>): string {
124
+ const [values, opt] = splitPrecision(args)
125
+ return divStrWith(configWithPrecision(opt), ...values)
126
+ }