@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.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/README.zh.md +115 -0
- package/dist/index.cjs +1354 -0
- package/dist/index.d.cts +449 -0
- package/dist/index.d.mts +449 -0
- package/dist/index.mjs +1324 -0
- package/package.json +67 -0
- package/src/index.ts +31 -0
- package/src/utils/aggregate.ts +109 -0
- package/src/utils/calc.ts +100 -0
- package/src/utils/chain.ts +127 -0
- package/src/utils/config.ts +59 -0
- package/src/utils/format.ts +366 -0
- package/src/utils/parser.ts +310 -0
- package/src/utils/precision.ts +442 -0
- package/src/utils/standalone.ts +126 -0
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wzo/calc",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "Precision math + number formatting — zero runtime dependencies, BigInt internally",
|
|
6
|
+
"author": "nowo",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://nowo.github.io/calc",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/nowo/calc.git",
|
|
12
|
+
"directory": "packages/main"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/nowo/calc/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"calc",
|
|
17
|
+
"precision",
|
|
18
|
+
"decimal",
|
|
19
|
+
"bignumber",
|
|
20
|
+
"format"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/index.d.cts",
|
|
34
|
+
"default": "./dist/index.cjs"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/index.cjs",
|
|
39
|
+
"module": "./dist/index.mjs",
|
|
40
|
+
"types": "./dist/index.d.mts",
|
|
41
|
+
"files": [
|
|
42
|
+
"README.zh.md",
|
|
43
|
+
"dist",
|
|
44
|
+
"src"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=16.6"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"a-calc": "^3.0.0",
|
|
51
|
+
"changelogen": "^0.6.2",
|
|
52
|
+
"decimal.js": "^10.6.0",
|
|
53
|
+
"mathjs": "^15.2.0",
|
|
54
|
+
"tsdown": "^0.18.0",
|
|
55
|
+
"typescript": "^5.9.3",
|
|
56
|
+
"vitest": "^3.2.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsdown",
|
|
60
|
+
"dev": "tsdown --watch",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest",
|
|
64
|
+
"bench": "vitest bench --run",
|
|
65
|
+
"release": "changelogen --release --push"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// @wzo/calc — precision math + number formatting
|
|
2
|
+
// Zero runtime dependencies; all precision arithmetic uses BigInt internally
|
|
3
|
+
|
|
4
|
+
export { calcAvg, calcMax, calcMin, calcSum } from './utils/aggregate'
|
|
5
|
+
export { calc, fmt } from './utils/calc'
|
|
6
|
+
|
|
7
|
+
export type { ICalcOptions, IDebugInfo } from './utils/calc'
|
|
8
|
+
export { chainAdd, chainDiv, chainMul, chainSub } from './utils/chain'
|
|
9
|
+
|
|
10
|
+
export type { IChain } from './utils/chain'
|
|
11
|
+
export { getConfig, resetConfig, setConfig } from './utils/config'
|
|
12
|
+
|
|
13
|
+
export type { IGlobalConfig, IPrecisionOption } from './utils/config'
|
|
14
|
+
|
|
15
|
+
export type { IFmtOptions, IFormat, Rounding } from './utils/format'
|
|
16
|
+
|
|
17
|
+
// Advanced usage: direct access to precision primitives
|
|
18
|
+
export {
|
|
19
|
+
abs,
|
|
20
|
+
cmp,
|
|
21
|
+
neg,
|
|
22
|
+
parse,
|
|
23
|
+
div as rawDiv, // division with explicit precision parameter (for add/sub/mul use addStr/subStr/mulStr)
|
|
24
|
+
roundBanker,
|
|
25
|
+
roundCeil,
|
|
26
|
+
roundHalfUp,
|
|
27
|
+
truncate,
|
|
28
|
+
} from './utils/precision'
|
|
29
|
+
|
|
30
|
+
export type { IDecimal } from './utils/precision'
|
|
31
|
+
export { add, addStr, div, divStr, mul, mulStr, sub, subStr } from './utils/standalone'
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Aggregation: calcSum / calcAvg / calcMax / calcMin
|
|
2
|
+
// Usage: calcSum("price", [{ price: 10 }, { price: 20 }]) → "30"
|
|
3
|
+
// calcSum([1, 2, 3]) → "6" (direct array form)
|
|
4
|
+
|
|
5
|
+
import type { IGlobalConfig, IPrecisionOption } from './config'
|
|
6
|
+
import { configWithPrecision } from './config'
|
|
7
|
+
import { add as addStr, cmp, div as divStr } from './precision'
|
|
8
|
+
|
|
9
|
+
type Val = string | number | bigint
|
|
10
|
+
// Values may be null / undefined: backends often return null/undefined values; pickValues skips them (the type reflects this design)
|
|
11
|
+
type Item = Record<string, Val | null | undefined>
|
|
12
|
+
|
|
13
|
+
const pickValues = (keyOrArr: string | Val[], list?: Item[]): string[] => {
|
|
14
|
+
let raw: Array<Val | null | undefined>
|
|
15
|
+
if (Array.isArray(keyOrArr)) {
|
|
16
|
+
raw = keyOrArr
|
|
17
|
+
} else {
|
|
18
|
+
if (!list) throw new Error('list is required when keyOrArr is a field name')
|
|
19
|
+
raw = list.map(item => item[keyOrArr])
|
|
20
|
+
}
|
|
21
|
+
// Skip null / undefined (backends often return empty values) to avoid parse errors from String(null)
|
|
22
|
+
return raw.filter(v => v != null).map(v => String(v))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Internal sum core — invalid values throw and propagate to the caller. */
|
|
26
|
+
const sumOf = (values: string[]): string => {
|
|
27
|
+
if (values.length === 0) return '0'
|
|
28
|
+
let sum = values[0]!
|
|
29
|
+
for (let i = 1; i < values.length; i++) sum = addStr(sum, values[i]!)
|
|
30
|
+
return sum
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Computes the sum. Accepts two call forms: a direct value array, or a field name with an array of objects.
|
|
35
|
+
*
|
|
36
|
+
* @param keyOrArr Value array (`[1, 2, 3]`) or the field name to sum (`'price'`)
|
|
37
|
+
* @param list Array of objects, required when the first argument is a field name
|
|
38
|
+
* @returns Total sum (`string`, high precision)
|
|
39
|
+
* @example
|
|
40
|
+
* calcSum([1, 2, 3]) // '6'
|
|
41
|
+
* calcSum('price', [{ price: 10 }, { price: 20 }]) // '30'
|
|
42
|
+
*/
|
|
43
|
+
export const calcSum = (keyOrArr: string | Val[], list?: Item[]): string => sumOf(pickValues(keyOrArr, list))
|
|
44
|
+
|
|
45
|
+
/** Computes the average with the given config precision (shared by the default {@link calcAvg} export and per-call precision entry points). */
|
|
46
|
+
export const calcAvgWith = (cfg: IGlobalConfig, keyOrArr: string | Val[], list?: Item[]): string => {
|
|
47
|
+
const values = pickValues(keyOrArr, list)
|
|
48
|
+
if (values.length === 0) return '0'
|
|
49
|
+
return divStr(sumOf(values), String(values.length), cfg._precision)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Computes the average (sum / count). Returns `'0'` for an empty collection.
|
|
54
|
+
*
|
|
55
|
+
* Precision defaults to the global `_precision`; pass `{ _precision }` as the **last** argument
|
|
56
|
+
* to override it for this call only (does not affect the global config).
|
|
57
|
+
*
|
|
58
|
+
* The first argument is either a value array (`[1,2,3]`) or a field name (`'price'`
|
|
59
|
+
* used together with an array of objects as the second argument).
|
|
60
|
+
*
|
|
61
|
+
* @returns Average value (`string`)
|
|
62
|
+
* @example
|
|
63
|
+
* calcAvg([1, 2, 3]) // '2'
|
|
64
|
+
* calcAvg('score', [{ score: 80 }, { score: 90 }]) // '85'
|
|
65
|
+
* calcAvg([10, 20, 25], { _precision: 2 }) // '18.33'
|
|
66
|
+
*/
|
|
67
|
+
export function calcAvg(arr: Val[], opt?: IPrecisionOption): string
|
|
68
|
+
export function calcAvg(key: string, list: Item[], opt?: IPrecisionOption): string
|
|
69
|
+
export function calcAvg(keyOrArr: string | Val[], listOrOpt?: Item[] | IPrecisionOption, opt?: IPrecisionOption): string {
|
|
70
|
+
// Array form: second argument is opt; field-name form: second argument is list, third is opt
|
|
71
|
+
const isFieldForm = Array.isArray(listOrOpt)
|
|
72
|
+
const list = isFieldForm ? listOrOpt : undefined
|
|
73
|
+
const o = isFieldForm ? opt : listOrOpt
|
|
74
|
+
return calcAvgWith(configWithPrecision(o), keyOrArr, list)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns the maximum value (numeric comparison, not lexicographic).
|
|
79
|
+
*
|
|
80
|
+
* @param keyOrArr Value array or field name
|
|
81
|
+
* @param list Array of objects, required when the first argument is a field name
|
|
82
|
+
* @returns Maximum value (`string`); returns `'0'` for an empty collection
|
|
83
|
+
* @example
|
|
84
|
+
* calcMax([3, 10, 2]) // '10'
|
|
85
|
+
*/
|
|
86
|
+
export const calcMax = (keyOrArr: string | Val[], list?: Item[]): string => {
|
|
87
|
+
const values = pickValues(keyOrArr, list)
|
|
88
|
+
if (values.length === 0) return '0'
|
|
89
|
+
let max = values[0]!
|
|
90
|
+
for (let i = 1; i < values.length; i++) if (cmp(values[i]!, max) > 0) max = values[i]!
|
|
91
|
+
return max
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the minimum value (numeric comparison).
|
|
96
|
+
*
|
|
97
|
+
* @param keyOrArr Value array or field name
|
|
98
|
+
* @param list Array of objects, required when the first argument is a field name
|
|
99
|
+
* @returns Minimum value (`string`); returns `'0'` for an empty collection
|
|
100
|
+
* @example
|
|
101
|
+
* calcMin([3, 10, 2]) // '2'
|
|
102
|
+
*/
|
|
103
|
+
export const calcMin = (keyOrArr: string | Val[], list?: Item[]): string => {
|
|
104
|
+
const values = pickValues(keyOrArr, list)
|
|
105
|
+
if (values.length === 0) return '0'
|
|
106
|
+
let min = values[0]!
|
|
107
|
+
for (let i = 1; i < values.length; i++) if (cmp(values[i]!, min) < 0) min = values[i]!
|
|
108
|
+
return min
|
|
109
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Entry point calc(): arithmetic expression evaluation → (optional) formatting
|
|
2
|
+
|
|
3
|
+
import type { IGlobalConfig } from './config'
|
|
4
|
+
import type { IFormat } from './format'
|
|
5
|
+
import type { IEvalContext } from './parser'
|
|
6
|
+
import { getConfig } from './config'
|
|
7
|
+
import { formatValue, normalizeFormat } from './format'
|
|
8
|
+
import { evaluate } from './parser'
|
|
9
|
+
|
|
10
|
+
/** Control options for {@link calc} (all prefixed with `_`; variables are not supported — embed values via template interpolation) */
|
|
11
|
+
export interface ICalcOptions {
|
|
12
|
+
/** true ⇒ enable unit mode (% is treated as a unit marker rather than division by 100; the result retains the % suffix) */
|
|
13
|
+
_unit?: boolean
|
|
14
|
+
/** Explicit format: an {@link IFormat} options object */
|
|
15
|
+
_fmt?: IFormat
|
|
16
|
+
/** Division precision (defaults to the global `_precision`) */
|
|
17
|
+
_precision?: number
|
|
18
|
+
/**
|
|
19
|
+
* Step-by-step evaluation debug:
|
|
20
|
+
* - `true` ⇒ prints to console
|
|
21
|
+
* - pass a function ⇒ receives {@link IDebugInfo} via callback, no console output
|
|
22
|
+
*/
|
|
23
|
+
_debug?: boolean | ((info: IDebugInfo) => void)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Evaluation info collected by `_debug` */
|
|
27
|
+
export interface IDebugInfo {
|
|
28
|
+
/** The original expression */
|
|
29
|
+
expr: string
|
|
30
|
+
/** Step-by-step evaluation trace (one entry per step) */
|
|
31
|
+
steps: string[]
|
|
32
|
+
/** Raw result before formatting */
|
|
33
|
+
value: string
|
|
34
|
+
/** Final returned value */
|
|
35
|
+
result: string | number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const RE_HAS_PERCENT = /%/
|
|
39
|
+
|
|
40
|
+
const emitDebug = (info: IDebugInfo, debug: true | ((info: IDebugInfo) => void)): void => {
|
|
41
|
+
if (typeof debug === 'function') {
|
|
42
|
+
debug(info)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.log(
|
|
47
|
+
`[calc] ${info.expr}\n${
|
|
48
|
+
info.steps.length ? `${info.steps.map(s => ` · ${s}`).join('\n')}\n` : ''
|
|
49
|
+
} = ${info.result}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Evaluate with an explicit config (the default export {@link calc} delegates to this). */
|
|
54
|
+
export function calcWith(cfg: IGlobalConfig, expr: string, options: ICalcOptions = {}): string | number {
|
|
55
|
+
const steps = options._debug ? [] as string[] : undefined
|
|
56
|
+
const ctx: IEvalContext = {
|
|
57
|
+
unit: !!options._unit,
|
|
58
|
+
precision: options._precision ?? cfg._precision,
|
|
59
|
+
trace: steps,
|
|
60
|
+
}
|
|
61
|
+
const value = evaluate(expr, ctx)
|
|
62
|
+
|
|
63
|
+
// Unit mode: if the expression contains %, append % to the result (only when no explicit format is set)
|
|
64
|
+
const exprHasPercent = options._unit && RE_HAS_PERCENT.test(expr)
|
|
65
|
+
|
|
66
|
+
const fmtSpec = options._fmt || cfg._fmt
|
|
67
|
+
const result = !fmtSpec
|
|
68
|
+
? (exprHasPercent ? `${value}%` : value)
|
|
69
|
+
: formatValue(value, normalizeFormat(fmtSpec))
|
|
70
|
+
|
|
71
|
+
if (options._debug) emitDebug({ expr, steps: steps!, value, result }, options._debug)
|
|
72
|
+
return result
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main entry point: evaluates an arithmetic expression string and optionally formats the output.
|
|
77
|
+
*
|
|
78
|
+
* Expressions are pure arithmetic: the four basic operations, parentheses, and math functions
|
|
79
|
+
* (`max`/`min`/`clamp`…). **Variables are not supported** — embed values directly in the
|
|
80
|
+
* expression via template interpolation: `` calc(`${price} * ${qty}`) ``.
|
|
81
|
+
* Formatting is specified via `options._fmt` (an {@link IFormat} object).
|
|
82
|
+
*
|
|
83
|
+
* `calc` is designed for **computation**: on error (invalid expression, etc.) it **throws
|
|
84
|
+
* directly** and the caller is responsible for handling it.
|
|
85
|
+
* For **display** scenarios that need a fallback on error, use {@link fmt} instead
|
|
86
|
+
* (same API and supports arithmetic, but returns `_error` on failure rather than throwing).
|
|
87
|
+
*
|
|
88
|
+
* @param expr Arithmetic expression, e.g. `'1 + 2 * 3'`, `'(1 + 2) / 3'`
|
|
89
|
+
* @param options Control options (see {@link ICalcOptions})
|
|
90
|
+
* @returns Computation result (`string`; `number` when `_fmt.output` is `'number'`)
|
|
91
|
+
* @throws Throws when the expression is invalid
|
|
92
|
+
* @example
|
|
93
|
+
* calc('1 + 2 * 3') // '7'
|
|
94
|
+
* calc(`${price} * ${qty}`) // use template interpolation instead of variables
|
|
95
|
+
* calc('1 + 2', { _fmt: { decimals: 2 } }) // '3.00'
|
|
96
|
+
*/
|
|
97
|
+
export const calc = (expr: string, options: ICalcOptions = {}): string | number => calcWith(getConfig(), expr, options)
|
|
98
|
+
|
|
99
|
+
/** Direct formatting */
|
|
100
|
+
export { fmt } from './format'
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Chaining API: chainAdd/chainSub/chainMul/chainDiv → returns an object supporting further .add().sub().mul().div() calls
|
|
2
|
+
// Terminate: call with () or (formatStr) to obtain the final string/number result
|
|
3
|
+
// Errors (invalid arguments, etc.) are thrown directly and must be handled by the caller
|
|
4
|
+
|
|
5
|
+
import type { IGlobalConfig, IPrecisionOption } from './config'
|
|
6
|
+
import type { IFormat } from './format'
|
|
7
|
+
import { configWithPrecision, getConfig } from './config'
|
|
8
|
+
import { formatValue, normalizeFormat } from './format'
|
|
9
|
+
import { add, div, mul, sub } from './precision'
|
|
10
|
+
|
|
11
|
+
type Val = string | number | bigint
|
|
12
|
+
|
|
13
|
+
/** Extracts a per-call precision option from the end of a variadic argument list (last element is an object ⇒ treated as {@link IPrecisionOption}). */
|
|
14
|
+
const splitPrecision = (args: Array<Val | IPrecisionOption>): [Val[], IPrecisionOption?] => {
|
|
15
|
+
const last = args.at(-1)
|
|
16
|
+
if (last != null && typeof last === 'object') {
|
|
17
|
+
return [args.slice(0, -1) as Val[], last as IPrecisionOption]
|
|
18
|
+
}
|
|
19
|
+
return [args as Val[], undefined]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A chaining computation object. Every arithmetic method returns itself for further chaining;
|
|
24
|
+
* calling it as a function terminates the chain and returns the result.
|
|
25
|
+
*/
|
|
26
|
+
export interface IChain {
|
|
27
|
+
/** Continues accumulating additions; returns itself for chaining. */
|
|
28
|
+
add: (...args: Val[]) => IChain
|
|
29
|
+
/** Continues accumulating subtractions; returns itself. */
|
|
30
|
+
sub: (...args: Val[]) => IChain
|
|
31
|
+
/** Continues accumulating multiplications; returns itself. */
|
|
32
|
+
mul: (...args: Val[]) => IChain
|
|
33
|
+
/** Continues accumulating divisions (uses the instance's `_precision`); returns itself. */
|
|
34
|
+
div: (...args: Val[]) => IChain
|
|
35
|
+
/**
|
|
36
|
+
* Terminates the chain and retrieves the result: no argument ⇒ returns the current value (`string`);
|
|
37
|
+
* pass an {@link IFormat} object ⇒ returns `string | number` according to the formatting rules.
|
|
38
|
+
*/
|
|
39
|
+
(format?: IFormat): string | number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const makeChain = (cfg: IGlobalConfig, initial: string): IChain => {
|
|
43
|
+
let value = initial
|
|
44
|
+
const fn = ((format?: IFormat): string | number => {
|
|
45
|
+
if (!format) return value
|
|
46
|
+
return formatValue(value, normalizeFormat(format))
|
|
47
|
+
}) as IChain
|
|
48
|
+
|
|
49
|
+
fn.add = (...args: Val[]) => {
|
|
50
|
+
for (const a of args) value = add(value, a)
|
|
51
|
+
return fn
|
|
52
|
+
}
|
|
53
|
+
fn.sub = (...args: Val[]) => {
|
|
54
|
+
for (const a of args) value = sub(value, a)
|
|
55
|
+
return fn
|
|
56
|
+
}
|
|
57
|
+
fn.mul = (...args: Val[]) => {
|
|
58
|
+
for (const a of args) value = mul(value, a)
|
|
59
|
+
return fn
|
|
60
|
+
}
|
|
61
|
+
fn.div = (...args: Val[]) => {
|
|
62
|
+
for (const a of args) value = div(value, a, cfg._precision)
|
|
63
|
+
return fn
|
|
64
|
+
}
|
|
65
|
+
return fn
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const reduceWith = (op: (a: string, b: Val) => string, args: Val[]): string => {
|
|
69
|
+
if (args.length === 0) return '0'
|
|
70
|
+
let r = String(args[0])
|
|
71
|
+
for (let i = 1; i < args.length; i++) r = op(r, args[i]!)
|
|
72
|
+
return r
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Internal implementations that accept a config (shared by default exports and per-call precision entry points) ──
|
|
76
|
+
export const chainAddWith = (cfg: IGlobalConfig, ...args: Val[]): IChain => makeChain(cfg, reduceWith(add, args))
|
|
77
|
+
export const chainSubWith = (cfg: IGlobalConfig, ...args: Val[]): IChain => makeChain(cfg, reduceWith(sub, args))
|
|
78
|
+
export const chainMulWith = (cfg: IGlobalConfig, ...args: Val[]): IChain => makeChain(cfg, reduceWith(mul, args))
|
|
79
|
+
export const chainDivWith = (cfg: IGlobalConfig, ...args: Val[]): IChain => makeChain(cfg, reduceWith((a, b) => div(a, b, cfg._precision), args))
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Starts a chaining computation with addition (`a + b + c ...`).
|
|
83
|
+
*
|
|
84
|
+
* @param args Initial values to accumulate via addition
|
|
85
|
+
* @returns An {@link IChain} supporting further `.add().sub().mul().div()` calls
|
|
86
|
+
* @example
|
|
87
|
+
* chainAdd(1, 2).mul(3)() // '9'
|
|
88
|
+
* chainAdd(1, 2).mul(3)({ decimals: 2 }) // '9.00'
|
|
89
|
+
*/
|
|
90
|
+
export const chainAdd = (...args: Val[]): IChain => chainAddWith(getConfig(), ...args)
|
|
91
|
+
/**
|
|
92
|
+
* Starts a chaining computation with subtraction (`a - b - c ...`).
|
|
93
|
+
*
|
|
94
|
+
* @param args Initial minuend and subtrahends
|
|
95
|
+
* @returns An {@link IChain} supporting further chaining
|
|
96
|
+
* @example
|
|
97
|
+
* chainSub(10, 1, 2)() // '7'
|
|
98
|
+
*/
|
|
99
|
+
export const chainSub = (...args: Val[]): IChain => chainSubWith(getConfig(), ...args)
|
|
100
|
+
/**
|
|
101
|
+
* Starts a chaining computation with multiplication (`a * b * c ...`).
|
|
102
|
+
*
|
|
103
|
+
* @param args Initial factors to multiply together
|
|
104
|
+
* @returns An {@link IChain} supporting further chaining
|
|
105
|
+
* @example
|
|
106
|
+
* chainMul(2, 3).add(4)() // '10'
|
|
107
|
+
*/
|
|
108
|
+
export const chainMul = (...args: Val[]): IChain => chainMulWith(getConfig(), ...args)
|
|
109
|
+
/**
|
|
110
|
+
* Starts a chaining computation with division (`a / b / c ...`).
|
|
111
|
+
*
|
|
112
|
+
* Precision defaults to the global `_precision`; pass `{ _precision }` as the **last** argument
|
|
113
|
+
* to override the division precision for this chain (including subsequent `.div()` calls)
|
|
114
|
+
* without affecting the global config.
|
|
115
|
+
*
|
|
116
|
+
* @param args Initial dividend and divisors, with an optional {@link IPrecisionOption} at the end
|
|
117
|
+
* @returns An {@link IChain} supporting further chaining
|
|
118
|
+
* @example
|
|
119
|
+
* chainDiv(100, 4)() // '25'
|
|
120
|
+
* chainDiv(100, 3, { _precision: 5 })() // '33.33333'
|
|
121
|
+
*/
|
|
122
|
+
export function chainDiv(...args: Val[]): IChain
|
|
123
|
+
export function chainDiv(...args: [...Val[], IPrecisionOption]): IChain
|
|
124
|
+
export function chainDiv(...args: Array<Val | IPrecisionOption>): IChain {
|
|
125
|
+
const [values, opt] = splitPrecision(args)
|
|
126
|
+
return chainDivWith(configWithPrecision(opt), ...values)
|
|
127
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Global config: error fallback, default format, division precision
|
|
2
|
+
|
|
3
|
+
import type { IFormat } from './format'
|
|
4
|
+
|
|
5
|
+
export interface IGlobalConfig {
|
|
6
|
+
/** Fallback value on error: defaults to `'-'`; set to `0` to return `'0'` on error. */
|
|
7
|
+
_error: string | number
|
|
8
|
+
/** Global default format ({@link IFormat} object). */
|
|
9
|
+
_fmt: IFormat | undefined
|
|
10
|
+
/** Division precision (maximum decimal places). */
|
|
11
|
+
_precision: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Default configuration values. */
|
|
15
|
+
export const DEFAULT_CONFIG: IGlobalConfig = {
|
|
16
|
+
_error: '-',
|
|
17
|
+
_fmt: undefined,
|
|
18
|
+
_precision: 50,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Per-call precision override: optional last argument accepted by `div` / `divStr` / `chainDiv` / `calcAvg`. */
|
|
22
|
+
export interface IPrecisionOption {
|
|
23
|
+
/** Division precision for this call, overrides the global `_precision` (does not affect the global config). */
|
|
24
|
+
_precision?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const config: IGlobalConfig = { ...DEFAULT_CONFIG }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Partially updates the global configuration (only the provided fields are overwritten).
|
|
31
|
+
*
|
|
32
|
+
* @param patch Configuration fields to update
|
|
33
|
+
* @example
|
|
34
|
+
* setConfig({ _precision: 10, _fmt: { decimals: 2 } })
|
|
35
|
+
*/
|
|
36
|
+
export const setConfig = (patch: Partial<IGlobalConfig>): void => {
|
|
37
|
+
Object.assign(config, patch)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resets the global configuration to its default values.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* resetConfig()
|
|
45
|
+
*/
|
|
46
|
+
export const resetConfig = (): void => {
|
|
47
|
+
Object.assign(config, DEFAULT_CONFIG)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns the current global configuration object (by reference — do not mutate directly; use {@link setConfig}).
|
|
52
|
+
*
|
|
53
|
+
* @returns The current {@link IGlobalConfig}
|
|
54
|
+
*/
|
|
55
|
+
export const getConfig = (): IGlobalConfig => config
|
|
56
|
+
|
|
57
|
+
/** Returns the global config; if `_precision` is provided, returns a copy with that field overridden without mutating the global singleton. */
|
|
58
|
+
export const configWithPrecision = (opt?: IPrecisionOption): IGlobalConfig =>
|
|
59
|
+
opt?._precision != null ? { ...config, _precision: opt._precision } : config
|