use integers to calculate formulas for evaluator (#214)

This commit is contained in:
MaysWind
2025-09-01 00:15:51 +08:00
parent 8bd0fd88af
commit 989183c8be
2 changed files with 64 additions and 22 deletions
+18 -10
View File
@@ -79,7 +79,7 @@ import type { CurrencyPrependAndAppendText } from '@/core/currency.ts';
import { DEFAULT_DECIMAL_NUMBER_COUNT } from '@/consts/numeral.ts'; import { DEFAULT_DECIMAL_NUMBER_COUNT } from '@/consts/numeral.ts';
import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '@/consts/transaction.ts'; import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '@/consts/transaction.ts';
import { isNumber, replaceAll } from '@/lib/common.ts'; import { isNumber, replaceAll } from '@/lib/common.ts';
import { evaluateExpression } from '@/lib/evaluator.ts'; import { evaluateExpressionToAmount } from '@/lib/evaluator.ts';
import type { ComponentDensity } from '@/lib/ui/desktop.ts'; import type { ComponentDensity } from '@/lib/ui/desktop.ts';
import logger from '@/lib/logger.ts'; import logger from '@/lib/logger.ts';
@@ -116,7 +116,6 @@ const {
getCurrentDecimalSeparator, getCurrentDecimalSeparator,
parseAmountFromLocalizedNumerals, parseAmountFromLocalizedNumerals,
formatAmountToLocalizedNumeralsWithoutDigitGrouping, formatAmountToLocalizedNumeralsWithoutDigitGrouping,
formatNumberToLocalizedNumerals,
getAmountPrependAndAppendText getAmountPrependAndAppendText
} = useI18n(); } = useI18n();
@@ -218,15 +217,24 @@ function calculateFormula(): void {
} }
finalFormula = numeralSystem.value.replaceLocalizedDigitsToWesternArabicDigits(finalFormula); finalFormula = numeralSystem.value.replaceLocalizedDigitsToWesternArabicDigits(finalFormula);
const calculatedValue = evaluateExpression(finalFormula);
if (isNumber(calculatedValue)) { try {
const textualValue = formatNumberToLocalizedNumerals(calculatedValue, 2); const calculatedAmount = evaluateExpressionToAmount(finalFormula);
const hasDecimalSeparator = textualValue.indexOf(decimalSeparator) >= 0;
currentValue.value = getValidFormattedValue(calculatedValue * 100, textualValue, hasDecimalSeparator); if (isNumber(calculatedAmount)) {
formulaMode.value = false; const textualValue = getFormattedValue(calculatedAmount);
} else { const hasDecimalSeparator = textualValue.indexOf(decimalSeparator) >= 0;
snackbar.value?.showMessage('Formula is invalid'); currentValue.value = getValidFormattedValue(calculatedAmount, textualValue, hasDecimalSeparator);
formulaMode.value = false;
} else {
snackbar.value?.showMessage('Formula is invalid');
}
} catch (ex) {
logger.error('cannot evaluate formula in amount input, original formula is ' + finalFormula, ex);
if (ex instanceof Error) {
snackbar.value?.showMessage(ex.message);
}
} }
} }
+46 -12
View File
@@ -1,7 +1,14 @@
import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '../consts/transaction.ts';
import { replaceAll } from './common.ts'; import { replaceAll } from './common.ts';
import logger from './logger.ts'; import logger from './logger.ts';
const maxAllowedDecimalCount = 6;
const normalizeFactor: number = 1000000;
const normalizedDecimalsMaxZeroString: string = '000000';
const normalizedNumberToAmountFactor: number = 10000; // 1000000 / 100
const operatorPriority: Record<string, number> = { const operatorPriority: Record<string, number> = {
'+': 1, '+': 1,
'-': 1, '-': 1,
@@ -9,6 +16,36 @@ const operatorPriority: Record<string, number> = {
'/': 2, '/': 2,
}; };
function normalizeNumber(textualNumber: string): number {
const decimalSeparatorPos = textualNumber.indexOf('.');
if (decimalSeparatorPos < 0) {
return parseInt(textualNumber + normalizedDecimalsMaxZeroString);
}
const integer = textualNumber.substring(0, decimalSeparatorPos);
const decimals = textualNumber.substring(decimalSeparatorPos + 1);
if (decimals.length > maxAllowedDecimalCount) {
throw new Error('Numeric Overflow');
}
const paddedDecimals = (decimals + normalizedDecimalsMaxZeroString).substring(0, maxAllowedDecimalCount);
return parseInt(integer + paddedDecimals);
}
function denormalizeNumberToAmount(num: number): number {
return Math.floor(num / normalizedNumberToAmountFactor);
}
function checkNumberRange(num: number): void {
const amount = denormalizeNumberToAmount(num);
if (amount > TRANSACTION_MAX_AMOUNT || amount < TRANSACTION_MIN_AMOUNT) {
throw new Error('Numeric Overflow');
}
}
function toPostfixExprTokens(expr: string): string[] | null { function toPostfixExprTokens(expr: string): string[] | null {
const finalTokens: string[] = []; const finalTokens: string[] = [];
const operatorStack: string[] = []; const operatorStack: string[] = [];
@@ -143,31 +180,28 @@ function evaluatePostfixExpr(tokens: string[]): number | null {
result = a - b; result = a - b;
break; break;
case '*': case '*':
result = a * b; result = Math.floor(a * b / normalizeFactor);
break; break;
case '/': case '/':
if (b === 0) { if (b === 0) {
logger.warn(`cannot evaluate expression "${tokens.join(' ')}", because division by zero`); logger.warn(`cannot evaluate expression "${tokens.join(' ')}", because division by zero`);
return null; return null;
} }
result = a / b; result = Math.floor(a * normalizeFactor / b);
break; break;
default: default:
return null; return null;
} }
checkNumberRange(result);
// push the result back to the stack // push the result back to the stack
stack.push(result); stack.push(result);
break; break;
default: // operands default: // operands
const num = parseFloat(token); const normalizedNum = normalizeNumber(token);
checkNumberRange(normalizedNum);
if (isNaN(num)) { stack.push(normalizedNum);
logger.warn(`cannot evaluate expression "${tokens.join(' ')}", because containing invalid number`);
return null;
}
stack.push(num);
break; break;
} }
} }
@@ -179,7 +213,7 @@ function evaluatePostfixExpr(tokens: string[]): number | null {
return stack[0]; return stack[0];
} }
export function evaluateExpression(expr: string): number | undefined { export function evaluateExpressionToAmount(expr: string): number | undefined {
if (!expr) { if (!expr) {
return undefined; return undefined;
} }
@@ -196,5 +230,5 @@ export function evaluateExpression(expr: string): number | undefined {
return undefined; return undefined;
} }
return result; return denormalizeNumberToAmount(result);
} }