formula-calc v0.2.4
formula-calc
formula-calc is a library for formula calculation through strings for javascript/typescript.
Note: The internal numerical calculation uses the decimal.js library.
中文说明
Main Features
Basic Calculations: Supports basic mathematical operations such as addition, subtraction, multiplication, division, exponentiation, and modulus.
Variable Support: Allows parameters to be passed through objects or arrays, supporting optional parameters and ternary operations.
Built-in Functions: Provides various built-in functions like max, min, sum, avg, round, etc.
Custom Functions: Users can define their own functions to extend the library's functionality.
Asynchronous Support: Supports Promise calculations, allowing for the handling of asynchronous parameters.
Precision Control: Users can set the precision of calculations and the precision for each step.
Type Conversion: Supports converting values to strings, numbers, and booleans.
Null Value Handling: Can treat
null
,undefined
,NaN
,empty string
as zero.Result referencing: Supports referencing the results of calculations in order using
$1...$n
, similar to regular expressions.
Install
npm install --save formula-calc
or
yarn add formula-calc
Usage
- basic
import formulaCalc from 'formula-calc';
const result = formulaCalc('1 + 1');
console.log(result); // 2
const result = formulaCalc('1 - 1');
console.log(result); // 0
const result = formulaCalc('2 * 3');
console.log(result); // 6
const result = formulaCalc('4 / 2');
console.log(result); // 2
// calculate with power
const result = formulaCalc('5 ^ 2');
console.log(result); // 25
// calculate with percent
const result = formulaCalc('2% + 1')
console.log(result); // 1.02
// divide to integer
const result = formulaCalc('5 // 4');
console.log(result); // 1
const result = formulaCalc(' -1.1 // 6');
console.log(result); // 0
// calculate with mod
const result = formulaCalc('5 % 2')
console.log(result); // 1
// with parenthesis
const result = formulaCalc('4 * (1 + 1) + 2')
console.log(result); // 10
- with variable
import formulaCalc, { createFormula } from 'formula-calc';
// get param from object
const result = formulaCalc('a + b.c', {
params: {
a: 1,
b: {
c: 2
}
}
});
console.log(result); // 3
// get param from array
const result = formulaCalc('a + b.c.1', {
params: {
a: 1,
b: {
c: [1, 2, 3]
}
}
});
console.log(result); // 3
// optional parameter
const result = formulaCalc('a.b?.c', {
params: {
a: {
b: 1
},
}
});
console.log(result); // 1
// optional parameter in ternary operator
const result = formulaCalc('a.b?.c ? 1 : 2', {
params: {
a: {
b: {}
},
}
});
console.log(result); // 2
// calc with params list
const result = formulaCalc('a + 1', {
params: [
{ a: 1 },
{ a: 2 },
{ a: 3 },
]
});
console.log(result); // [2, 3, 4]
// calc with formula instance
const formula = createFormula('a + 1');
const result = formulaCalc(formula, {
params: {
a: 1
}
});
console.log(result); // 2
// with promise
const result = await formulaCalc('a + 1', {
params: {
a: Promise.resolve(2),
}
});
console.log(result); // 3
- with function, built in functions: add, avg, ceil, eval, exist, floor, if, max, min, noref, random, round, sqrt, sum, trunc
import formulaCalc from 'formula-calc';
const result = formulaCalc('max(1, 2, 3, 4, 5)');
console.log(result); // 5
const result = formulaCalc('min(1, 2, 3, 4, 5)');
console.log(result); // 1
const result = formulaCalc('abs(-1)');
console.log(result); // 1
const result = formulaCalc('sum(1, 2, 3, 4, 5)');
console.log(result); // 15
const result = formulaCalc('sum(1, 2, 3, 4, a)', {
params: {
a: [5, 6, 7, 8]
}
});
console.log(result); // 36
const result = formulaCalc('round(2.335)');
console.log(result); // 2.34
const result = formulaCalc('round(2.335, 1)');
console.log(result); // 2.3
const result = formulaCalc('if(a, 1, 2)', {
params: {
a: true
}
});
console.log(result); // 1
const result = formulaCalc('if(a, 1, 2)', {
params: {
a: false
}
});
console.log(result); // 2
const result = formulaCalc('a ? 1 : 2', {
params: {
a: true
}
});
console.log(result); // 1
const result = formulaCalc('a ? 1 : 2', {
params: {
a: false
}
});
console.log(result); // 2
// with ref: like regex, $1...$n will match the ordinal of parentheses that do not contain functions
const result = formulaCalc(
`if(
(a + 2) > 0,
$1,
0 - $1
)`, {
params: {
a: -3
}
});
console.log(result); // 1
- with ref: like regex, $1...$n will match the ordinal of parentheses that do not contain functions
const result = formulaCalc(
`if(
(a + 2) > 0,
$1,
0 - $1
)`, {
params: {
a: -3
}
});
console.log(result); // 1
- with custom function
import formulaCalc from 'formula-calc';
const result = formulaCalc('add1(2.11)', {
customFunctions: {
add1: {
argMin: 1,
argMax: 1,
execute(params) {
return params[0] + 1;
}
}
}
});
console.log(result); // 3.11
- with eval
import formulaCalc from 'formula-calc';
const result = formulaCalc(
`if(
a > 0,
eval(planA),
eval(planB)
)`, {
params: {
a: -3,
planA: 'a + 1',
planB: '0 - a + 1',
}
});
console.log(result); // 4
- handle precision
import formulaCalc from 'formula-calc';
const result = formulaCalc('10 / 3');
console.log(result); // 3.3333333333333
const result = formulaCalc('10 / 3', { precision: 2 });
console.log(result); // 3.33
- rounding at each step of the operation
import formulaCalc from 'formula-calc';
const result = formulaCalc('3.334 + 3.335', {
stepPrecision: true,
});
console.log(result); // 6.67
const result = formulaCalc('3.3334 + 3.3315', {
precision: 2,
stepPrecision: 3,
};
console.log(result); // 6.67
- cast value
import formulaCalc from 'formula-calc';
const result = formulaCalc('string(1)');
console.log(result); // '1'
const result = formulaCalc('string(a)', { params: { a: undefined } });
console.log(result); // ''
const result = formulaCalc('number("1")');
console.log(result); // 1
const result = formulaCalc('boolean(1)');
console.log(result); // true
const result = formulaCalc('boolean(0)');
console.log(result); // false
- null as zero
import formulaCalc from 'formula-calc';
const result = formulaCalc('a.b.c', {
params: {
a: {}
},
nullAsZero: true
});
console.log(result); // 0
// empty string as zero
const result = formulaCalc('1 + a.b', {
params: {
a: {
b: ''
}
},
nullAsZero: true
});
console.log(result); // 1
Documentation
API
formulaCalc
formulaCalc
is the default export method. It is the core function for performing formula calculations.
type RoundingType = 'UP'|'DOWN'|'CEIL'|'FLOOR'|'HALF_UP'|'HALF_DOWN'|'HALF_EVEN'|'HALF_CEIL'|'HALF_FLOOR'|'EUCLID';
type FormulaValueOptions = {
Decimal?: typeof Decimal,
precision?: number,
rounding?: RoundingType,
stepPrecision?: boolean|number,
tryStringToNumber?: boolean,
ignoreRoundingOriginalValue?: boolean,
ignoreRoundingParams?: boolean|(name: string) => boolean,
returnDecimal?: boolean,
nullAsZero?: boolean,
nullIfParamNotFound?: boolean,
eval?: null|((expr: string, dataSource: IFormulaDataSource, options: FormulaValueOptions, forArithmetic?: boolean) => any),
}
interface FormulaOptions extends FormulaValueOptions {
}
interface FormulaCreateOptions extends FormulaOptions {
customFunctions?: Record<string, FormulaCustomFunctionItem>,
}
interface FormulaCalcCommonOptions extends FormulaCreateOptions {
dataSource?: IFormulaDataSource
}
interface FormulaCalcOptions extends FormulaOptions {
params?: FormulaCalcParams|Array<FormulaCalcParams>,
onFormulaCreated?: (formula: Formula) => void,
cache?: boolean,
}
declare function formulaCalc<T extends any = any>(
expressionOrFormula: string|Formula,
options: FormulaCalcOptions = {},
returnReferenceType?: T|((result: any) => T)
): T;
export default formulaCalc;
expressionOrFormula
Supports the following types:
Expression strings, such as:
a + b
.Formula instances. You can create them using
new Formula()
. If you need to perform a large number of repeated calculations on the same expression, it is recommended to use formula instances, as they cache the parsing result of the expression to improve execution performance.
use formula instance:
import formulaCalc from 'formula-calc';
const formula = new Formula();
formula.parse('1 + a');
const result = [1, 2, 3].map(a => formulaCalc(formula, { params: { a } }));
console.log(result); // [2, 3, 4]
you can also use params list to execute multiple times:
import formulaCalc from 'formula-calc';
const result = formulaCalc('1 + a', { params: [1, 2, 3].map(a => ({ a })) });
console.log(result); // [2, 3, 4]
options
The following is a tabular description of the parameters supported by options in formulaCalc:
Parameter Name | Type | Default Value | Description |
---|---|---|---|
params | FormulaCalcParams| Array\<FormulaCalcParams> | - | Parameters passed to the expression, which can be an object or an array. If it is an array, each object in the array will be traversed to execute the expression, returning an array of results. |
dataSource | IFormulaDataSource | - | Custom data source passed to the expression. If not provided, the default data source (which handles the retrieval of params) will be used. If provided, the retrieval of params will be handled by the user. |
customFunctions | Record\<string, FormulaCustomFunctionItem> | - | Custom function mapping table for registering custom functions. |
onFormulaCreated | (formula: Formula) => void | - | Callback function executed after creating the formula instance. |
cache | boolean | false | Whether to cache the formula instance, default is false. If true, instances of the same expression will be cached, and the next call to the same expression will return the cached instance without recreating it. |
Decimal | typeof Decimal | - | Custom instance of Decimal.js used for numerical calculations. |
precision | number | 2 | Sets the precision of the calculation results. |
rounding | RoundingType | 'HALF_UP' | Sets the rounding type. Optional values include: UP, DOWN, CEIL, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN, HALF_CEIL, HALF_FLOOR, EUCLID. |
stepPrecision | boolean | number | - | Whether to round at each step of the operation, or set the precision for each step. |
tryStringToNumber | boolean | false | Whether to attempt to convert strings to numbers. |
ignoreRoundingOriginalValue | boolean | false | Whether to ignore rounding of primitive values (number, string, boolean, params, ref). |
ignoreRoundingParams | boolean |(name) => boolean | false | Whether to ignore rounding of parameters. |
returnDecimal | boolean | false | Whether to return the number type as Decimal type. |
nullAsZero | boolean | false | Whether to treat null, undefined, NaN, and empty strings as zero in calculations. |
nullIfParamNotFound | boolean | false | Whether to return null if a parameter is not found. If false, an exception will be thrown when a parameter is not found. |
eval | null|Function | - | Custom expression eval function. |
returnReferenceType
The return value type of formulaCalc
can be referenced in the parameters. In js code, users can set returnReferenceType
to the corresponding type to facilitate IDE type hints.
like this:
/** IDE should be able to automatically recognize that the result is of type number */
const result = formulaCalc('1 + 1', {}, 0);
If returnReferenceType
is a function, it can process the result value before formulaCalc returns, and the return value of that function will be the final return value.
formulaUtils
formulaUtils
is a set of helper utility functions (sum
, avg
, min
, max
, round
) designed to facilitate simple calculations that don't need to be performed through expressions.
type FormulaUtilsParam = number|string|null|undefined|Decimal;
type FormulaUtilsOptions = Omit<FormulaCalcOptions, 'params'|'onCreateParam'>;
type FormulaUtils = {
sum: (params: Array<FormulaUtilsParam>, options?: FormulaUtilsOptions) => number,
avg: (params: Array<FormulaUtilsParam>, options?: FormulaUtilsOptions) => number,
min: (params: Array<FormulaUtilsParam>, options?: FormulaUtilsOptions) => number,
max: (params: Array<FormulaUtilsParam>, options?: FormulaUtilsOptions) => number,
add(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
sub(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
mul(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
div(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
divToInt(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
pow(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
mod(a: FormulaUtilsParam, b: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
abs(a: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
ceil(a: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
floor(a: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
trunc(a: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
sqrt(a: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
cbrt(a: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
clamp(a: FormulaUtilsParam, min: FormulaUtilsParam, max: FormulaUtilsParam, options?: FormulaUtilsOptions) => number,
round: (
value: Decimal.Value,
decimalPlaces: number = 2,
rounding: Decimal.Rounding|RoundingType = Decimal.ROUND_HALF_UP,
) => number,
toFixed: (
value: Decimal.Value|null|undefined,
options: {
precision?: number|[min: number, max: number],
comma?: boolean,
commaStr?: string,
nullStr?: string,
trimTrailingZero?: boolean,
trimTrailingZeroIfInt?: boolean,
rounding?: Decimal.Rounding|RoundingType,
} = {}
) => number,
}
declare const formulaUtils: FormulaUtils;
export {
formulaUtils
}
Note: The sum
, avg
, min
, and max
functions in formulaUtils
are configured by default with nullAsZero
, tryStringToNumber
, and nullIfParamNotFound
set to true
.
Below is an example:
import { formulaUtils } from 'formula-calc';
const result = formulaUtils.sum([1, 2, 3]);
console.log(result); // 6
const result = formulaUtils.sum([1, 2, 3, null, undefined, '']);
console.log(result); // 6
const result = formulaUtils.sum([]);
console.log(result); // 0
const result = formulaUtils.avg([1, 2, 3]);
console.log(result); // 2
const result = formulaUtils.avg([]);
console.log(result); // 0
const result = formulaUtils.min([1, 2, 3]);
console.log(result); // 1
const result = formulaUtils.min([]);
console.log(result); // 0
const result = formulaUtils.max([1, 2, 3]);
console.log(result); // 3
const result = formulaUtils.max([]);
console.log(result); // 0
const result = formulaUtils.round(1.2345, 3);
console.log(result); // 1.235
formulaUtils.toFixed
formulaUtils.toFixed
is a utility function used to format numbers. It converts a number to a string and adds thousand separators, decimal points, precision, etc. Its options
parameter supports the following formatting options:
Parameter Name | Type | Default Value | Description |
---|---|---|---|
precision | number | min: number, max: number | - | Sets the precision. Can be a single number or an array containing minimum and maximum precision values. |
comma | boolean | false | Whether to add a thousands separator in the number. |
commaStr | string | ',' | The string used as the thousands separator, defaults to a comma. |
commaDigit | number | 3 | The thousands separator is added every commaDigit digits. |
nullStr | string | '' | The string returned If the value is null , undefined , NaN , Infinity , or other content that cannot be converted to a numeric type. |
trimTrailingZero | boolean | false | Whether to trim trailing zeros after the decimal point. |
trimTrailingZeroIfInt | boolean | false | Whether to trim trailing zeros after the decimal point if the value is an integer. |
rounding | Decimal.Rounding | RoundingType | Decimal.ROUND_HALF_UP | Sets the rounding type. Optional values include: UP, DOWN, CEIL, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN, HALF_CEIL, HALF_FLOOR, EUCLID. |
import { formulaUtils } from 'formula-calc';
const result = formulaUtils.toFixed(1.2);
console.log(result); // '1.20'
const result = formulaUtils.toFixed(1.2, { precision: 3 });
console.log(result); // '1.200'
const result = formulaUtils.toFixed(1.2, { precision: [2, 4] });
console.log(result); // '1.20'
const result = formulaUtils.toFixed(1.234, { precision: [2, 4] });
console.log(result); // '1.234'
const result = formulaUtils.toFixed(1.23456, { precision: [2, 4] });
console.log(result); // '1.2346'
const result = formulaUtils.toFixed(1.23456, { precision: [2, 4], rounding: 'FLOOR' });
console.log(result); // '1.2345'
const result = formulaUtils.toFixed(1000.2, { comma: true });
console.log(result); // '1,000.20'
const result = formulaUtils.toFixed(100000.2, { comma: true, commaDigit: 4, commaStr: '`' });
console.log(result); // '10`0000.20'
const result = formulaUtils.toFixed(null, { nullStr: '--' });
console.log(result); // '--'
const result = formulaUtils.toFixed(1);
console.log(result); // '1.00'
const result = formulaUtils.toFixed(1, { trimTrailingZeroIfInt: true });
console.log(result); // '1'
const result = formulaUtils.toFixed(1.2, { trimTrailingZeroIfInt: true });
console.log(result); // '1.20'
Values
Supports the following values
number
- number, like 1, 2, 3, 1e3, 1e+3, 1e-3string
- string, it is quoted with"
, like "1", "2", "3"boolean
- boolean, like true, falsenull
- null,undefined
also be asnull
.null
will be converted to0
whennullAsZero
istrue
.NaN
- NaNInfinity
- Infinityparams
- params, likea
,a.b
,a.b.0
, "params" is taken from the "params" parameter in the second parameter of theformulaCalc
method. If theparam
name contains special characters, it can be quoted with'
, like this:'a()*_(&_&*)b'
ref
-$1
...$99
, similar to regular expressions, it will match the ordinal of parentheses that do not contain functions
Operators
Supports the following operators
+
- add-
- subtract*
- multiply/
- divide//
- divide to integer, like6 // 3
is2
,5 // 4
is1
, ifa
is negative, the result will be0
, like-1.1 // 6
is0
^
- power%
- mod=
- equal, likea = b
, ifa
orb
isundefined
, it will be asnull
!=
or<>
- not equal>
- greater than>=
- greater than or equal to<
- less than<=
- less than or equal to&
or&&
- and|
or||
- or!
- not? :
- ternary operator, likea ? b : c
()
- parenthesis
Note:
- For the comparison operators (
=
,!=
,<>
,>
,<
,>=
,<=
), before performing the comparison, if one side is a number, both sides will be attempted to be converted to numbers for comparison; otherwise, string comparison will be performed. For example,10 > "2"
is true, while"10" > "2"
is false. If thetryStringToNumber
option is set totrue
, then will attempt to convert both sides of the comparison to numbers.
Functions
Supports the following built in functions:
Basic Functions
abs(x)
- Returns the absolute value of xceil(x)
- Rounds x up to the nearest integerfloor(x)
- Rounds x down to the nearest integerround(x, y?)
- Rounds x to y decimal places (defaults to2
)trunc(x)
- Removes decimal places from x without roundingsign(x)
- Returns the sign of x (-1, 0, or 1)clamp(x, min, max)
- Restricts x to be between min and max values
Exponential and Logarithmic Functions
sqrt(x)
- Returns the square root of xcbrt(x)
- Returns the cube root of xln(x)
- Returns the natural logarithm (base e) of xlog(x)
- Returns the natural logarithm (base e) of x (alias of ln)log10(x)
- Returns the base-10 logarithm of xlog2(x)
- Returns the base-2 logarithm of x
Trigonometric Functions
sin(x)
- Returns the sine of x (x in radians)cos(x)
- Returns the cosine of x (x in radians)tan(x)
- Returns the tangent of x (x in radians)asin(x)
- Returns the arcsine of x in radiansacos(x)
- Returns the arccosine of x in radiansatan(x)
- Returns the arctangent of x in radiansatan2(y, x)
- Returns the arctangent of the quotient of y and x in radians
Hyperbolic Functions
sinh(x)
- Returns the hyperbolic sine of xcosh(x)
- Returns the hyperbolic cosine of xtanh(x)
- Returns the hyperbolic tangent of xasinh(x)
- Returns the inverse hyperbolic sine of xacosh(x)
- Returns the inverse hyperbolic cosine of xatanh(x)
- Returns the inverse hyperbolic tangent of x
String Functions
concat(n1, n2, ..., n99)
- return all parameters into a string
Special Functions
eval(expr)
- evaluate expressionexist(o, key, type?)
- check if the key exists in the object, if type is not specified, it will be checked for all types, if type is specified, it will be checked for the specified type.if(a, b, c?)
- ifa
is true, then returnb
, otherwise returnc
noref(x)
- directly return x, becauseref
does not count the parentheses of a function, the parentheses wrapped innoref
will not be included in theref
.max(n1, n2, ..., n99)
- maximum, note :nX
can be number array.min(n1, n2, ..., n99)
- minimum, note :nX
can be number array.sum(n1, n2, ..., n99)
- sum, note :nX
can be number array.avg(n1, n2, ..., n99)
- average, note :nX
can be number array.random(n)
- random numberhypot(x1, x2, ..., xn)
- Returns the square root of the sum of squares of its arguments
Cast Functions
string(expr)
- cast expr to string, note:null
will be cast to an empty string, ifexpr
isundefined
, it will be asnull
number(expr)
- cast expr to numberboolean(expr)
- cast expr to boolean
Custom Functions
Custom functions can be used to extend the formula language. due to the formula supporting promise calculation, you can even provide UI related interactions in custom methods.
Custom functions must include the following properties:
type FormulaCustomFunctionItem = {
preExecute?: true,
arithmetic?: boolean,
argMin: number
argMax: number;
execute: (params: any[], dataSource: IFormulaDataSource, options: FormulaValueOptions, forArithmetic?: boolean) => any
} | {
preExecute: false,
arithmetic?: boolean,
argMin: number
argMax: number;
execute: (params: FormulaValues, dataSource: IFormulaDataSource, options: FormulaValueOptions, forArithmetic?: boolean) => any
}
Property Name | Type | Default Value | Description |
---|---|---|---|
preExecute | boolean | true | Whether to preprocess parameters before execution. If true , all parameters will be executed to get results before executing execute . If set to false , the params passed to execute will be FormulaValues objects, and the user needs to call the parameter's execute method themselves (e.g., params[0].execute(dataSource, options) ) to get the parameter values. |
arithmetic | boolean | false | The arithmetic nature of the custom function. If true , it indicates that the return value of this function will be a number type (or Decimal type). If this function is used as one of the parameters of a comparison operator, it will attempt to convert the other side's parameter to a number for comparison. If false , it indicates unknown. |
argMin | number | - | Minimum number of parameters. |
argMax | number | - | Maximum number of parameters. |
execute | (params, dataSource, options, forArithmetic) => any | - | Execution function, including parameters, data source, and options. Additionally, the forArithmetic parameter indicates whether the current function is being executed as one of the parameters in an arithmetic operation. |
Example:
import formulaCalc from 'formula-calc';
const result = await formulaCalc('confirm("some prompt", 1, 2) + 1', {
customFunctions: {
confirm: {
argMin: 3,
argMax: 3,
execute([prompt, a, b]) {
return new Promise((resolve) => {
// some UI interaction
setTimeout(() => {
resolve(a > b ? a : b);
}, 1000);
});
}
}
}
});