big.js和php精度函数封装
php
code
function bc()
{
$argv = func_get_args();
$precision = $argv[1] ?? 2;
$formulas = $argv[0];
bcscale(10);
if (is_numeric($formulas)) {
return number_format($formulas, $precision, '.', '');
}
$fisrt_number_regex = '((?:[-][\d\.]+|[\d\.]+))';
$two_number_regex = '((?:[-][\d\.]+|[\d\.]+))';
$regexs = array_map(
fn ($operate_regex) => sprintf('/%s%s%s/', $fisrt_number_regex, $operate_regex, $two_number_regex),
['([\*\/\%])', '([\+\-])'] // 优先级问题
);
$string = str_replace(' ', '', '('.$argv[0].')');
$string = preg_replace_callback('/\$([0-9\.]+)/', fn ($matches) => '$argv[$1]', $string);
while (preg_match('/(()?)\(([^\)\(]*)\)/', $string, $match)) {
while (
preg_match($regexs[0], $match[3], $m)
|| preg_match($regexs[1], $match[3], $m)
) {
switch ($m[2]) {
case '+':
$result = bcadd($m[1], $m[3]);
break;
case '-':
$result = bcsub($m[1], $m[3]);
break;
case '*':
$result = bcmul($m[1], $m[3]);
break;
case '/': // 解决不能/0的问题
$result = b(0) === b($m[3]) ? '0' : bcdiv($m[1], $m[3]);
break;
case '%':
$result = bcmod($m[1], $m[3]);
break;
}
$match[3] = str_replace($m[0], $result, $match[3]);
}
if (!empty($match[1]) && function_exists($func = 'bc'.$match[1])) {
$match[3] = $func($match[3]);
}
$string = str_replace($match[0], $match[3], $string);
}
return number_format((float) $string, $precision, '.', '');
}
big.js
code
import Big from 'big.js'
// import { getConfig } from '@/utils/localStorage'
const getConfig = () => ({
price_precise: 4 // 准确度
})
export default class {
private static instance: any
private precis:number
constructor (precise:number) {
this.precis = precise
}
public static that () { // 当前实例
if (!this.instance) {
const config:any = getConfig()
this.instance = new this(config.price_precise) // config.price_precise为精准度
}
return this.instance
}
// 当需要重新加载时 删除当前实例
public static delInstance () {
this.instance = undefined
}
public init (val:Number) { // 初始化数字
return val.toFixed(this.precis)
}
public bc (formulas: string, precise = null): string { // 传递字符串 执行big计算
formulas = `(${formulas})`
formulas = formulas.replace(/ /g, '')
const regex = {
bracketRegex: /(()?)\(([^\)\(]*)\)/, // 括号的内容
operates:[
'([\\*\\/\\%])','([\\+\\-])' // 运算符优先级问题
]
}
const result: any = {
bracket: null, // 括号的内容匹配结果
number: null, // 两数内容匹配结构
result: '', // 两数计算的结果
bracket_str: '' // 两数计算的公式字符串 如: 1*2
}
const numberRegex =(operateRegex:string) => new RegExp(`((?:[-][\\d\\.]+|[\\d\\.]+))${operateRegex}((?:[-][\\d\\.]+|[\\d\\.]+))`)
console.log( (numberRegex(regex.operates[0])))
// eslint-disable-next-line no-cond-assign
while (result.bracket = regex.bracketRegex.exec(formulas)) {
result.bracket_str = result.bracket[3] // 括号内的字符串
while ( // 从公式提取两数之间的计算
// eslint-disable-next-line no-cond-assign
result.number =
(numberRegex(regex.operates[0]).exec(result.bracket_str) || numberRegex(regex.operates[1]).exec(result.bracket_str))
) {
const firstNumber = result.number[1] // 第一个数
const operator = result.number[2] // 运算符
const secondNumber = result.number[3] // 第二个
const big = Big(firstNumber)
switch (operator) {
case '+':
result.result = big.plus(secondNumber)
break
case '-':
result.result = big.minus(secondNumber)
break
case '*':
result.result = big.times(secondNumber)
break
case '/':
result.result = big.div(secondNumber)
break
case '%':
result.result = big.mod(secondNumber)
break
// 异或可继续扩展
}
// 两数计算的结束 替换成 结果 `
result.bracket_str = result.bracket_str.replace(result.number[0], result.result)
}
// 括号内部计算结束 替换为结果
formulas = formulas.replace(result.bracket[0], result.bracket_str)
}
formulas = formulas.trim()
return precise === null ? Big(formulas).toFixed(this.precis) : Big(formulas).toFixed(precise)
}
}
test
<?php
declare(strict_types=1);
/**
* @internal
* @coversNothing
*/
final class BcTest extends TestCase
{
// 公式测试
public function formulaResultProvider()
{
return [
['-2 * (2 + 2) - 4 * 2 - -1 + -3.11', bc(-18.11)],
['-1 - -2 - -3 - -4 -5 - -6 - -7 - -8 - -9 - -10', bc(43)],
['1 - -2 - -3 - -4 -5 - -6 - -7 - (-31 * 4.11) - -9 - -10', bc(164.41000000000003)],
['2 * (2.31 + 3.11) / 2.11 /2.11 / -2.11 + -1', bc(-2.1539365149690797)],
['0 * (2.31 + 3.11) / 2.11 /2.11 / -2.11 + -1', bc(-1)],
['0 * (2.31 + 3.11) / 2.11 /2.11 / -2.11 + -1', bc(-1)],
['-4.22 * -11 / -11.11 * -9.9898 / -9.211 ', bc(-4.531490652050766)],
['-4.22 * -11 / -11.11 * (-9.9898 - -9.211) / 6 + 9.11', bc(9.652332673267326)],
['-2 * 2 + 2', bc(-2)],
['-2 * (2 + 2)', bc(-8)],
['1.11 * 1.11 - 1.11 + -2 - -2 +4', bc(4.12)],
['1.11 * 1.11 - 1.11 + -2 - -2 +4', bc(4.12)],
['99.11 * (1.11 * 3 ) / (2.11 + 2) -1.11', bc(79.19080291970803)],
['(((1.11 * (2.11 - (3.11 * (2.11 + .4 ))) / (.91 * .2 * -.612) ) * 4 + 1.21) * 5.11 - 9999.11 % 35)', bc(1142.3420366515832)],
];
}
/**
* @dataProvider formulaResultProvider
*
* @param mixed $formula
* @param mixed $result
*/
public function testFormula($formula, $result): void
{
static::assertSame($result, bc($formula));
}
}
└─$ ./vendor/bin/phpunit tests/BcTest.php
PHPUnit 9.5.19 #StandWithUkraine
.............. 14 / 14 (100%)
Time: 00:00.560, Memory: 14.00 MB
OK (14 tests, 14 assertions)
后续
修复坑 经过测试
本作品采用《CC 协议》,转载必须注明作者和本文链接