{"id":"decimal-expression-evaluate","version":"1.0.0","description":"Evaluate a decimal arithmetic expression exactly with variables, abs, min, max and round, rounding once at the end.","supported_operations":["evaluate a decimal expression exactly","calculator with round to 2 decimals","compute a formula with named variables exactly","exact arithmetic expression with parentheses","evaluate price times quantity plus tax formula","expression result as an exact fraction","evaluate min max abs of decimals","safe calculator without floating point"],"unsupported_operations":["cron expressions","trigonometry, logarithms or other math functions","percent signs, factorials or scientific notation","fractional exponents or symbolic algebra","dates, units or currency symbols"],"semantics":["Grammar: expr := term ((\"+\"|\"-\") term)*; term := unary ((\"*\"|\"/\") unary)*; unary := \"-\" unary | power; power := atom (\"^\" \"-\"? INTEGER)?; atom := NUMBER | NAME | NAME \"(\" args \")\" | \"(\" expr \")\". Binary operators are left associative: 8/2/2 = 2, 2-3-4 = -5. Precedence: ^ binds tighter than unary minus, which binds tighter than * and /, then + and -: -2^2 = -4, 2*-3 = -6, 2--3 = 5, (-2)^2 = 4.","^ takes an integer literal exponent (optionally negative) of magnitude at most 100; the exponent is not an expression and a fraction, a variable, a plus sign or a second minus after ^ are invalid_input. ^ does not chain: 2^3^2 -> invalid_input (write (2^3)^2). Exponent magnitude over 100 -> invalid_input. There is no unary plus.","Numbers follow the decimal grammar of the family: digits with an optional \".digits\" part; no leading zeros (\"05\"), no leading or trailing point (\".5\", \"5.\"), no exponent, no separators. A literal of 63 or more characters -> limit_exceeded; otherwise more than 60 digits or more than 40 fraction digits -> invalid_input.","Whitespace is space, tab, line feed and carriage return and separates or is ignored between tokens. Any other character (including non-breaking space, other Unicode, %, **, $, brackets, \"e\" notation like 1e3 which lexes as 1 followed by the name e3) -> invalid_input. Names are lowercase [a-z_][a-z0-9_]* and at most 32 characters; upper case is invalid_input.","Functions (a NAME immediately followed by \"(\"): abs(x) takes exactly 1 argument; min(a, b, ...) and max(a, b, ...) take 2 to 50 arguments (fewer -> invalid_input, more than 50 -> limit_exceeded); round(x, n) takes an expression and an integer literal n 0..40 and rounds x to n fraction digits using the call's rounding mode. round() without a rounding field -> invalid_input. Any other NAME followed by \"(\" (including a variable name) -> invalid_input. A function name not followed by \"(\" is an ordinary variable name.","Variables: a NAME not followed by \"(\" must be present in variables, otherwise invalid_input (detected while parsing, even in a branch that is never reached). Every variable value is validated as a decimal string (63+ chars -> limit_exceeded, other violations -> invalid_input) even if unused. More than 100 variables -> limit_exceeded. A variable name outside ^[a-z_][a-z0-9_]{0,31}$ -> invalid_input. \"__proto__\" is a valid variable name and behaves like any other.","Evaluation is exact rational arithmetic; nothing is rounded except by round() calls and the final scale. Zero has no sign. min and max compare numerically.","Size limits: expression at most 2,000 UTF-8 bytes (checked first, as bytes not characters: 1,001 \"é\" is 2,002 bytes -> limit_exceeded even though \"é\" is not a valid character), then a lone surrogate -> invalid_input, then tokenisation (bad character -> invalid_input, literal length as above), then more than 500 tokens -> limit_exceeded, then parsing (syntax, unknown function or variable, arity, depth), then evaluation.","Nesting depth: every \"(\" (including the one after a function name) and every pending unary minus adds one level; more than 64 levels -> limit_exceeded (64 nested parentheses succeed, 65 do not).","Intermediate size: a reduced numerator or denominator over 200 digits -> limit_exceeded. For ^, max(digits(numerator), digits(denominator)) * |exponent| > 200 is rejected before exponentiation. A 60-digit value ^3 can succeed; ^4 exceeds the cap.","Arithmetic work: a request-local budget of 50,000 units is charged before every rational input reduction, add, multiply, divide, comparison, power and explicit round. Each charge is the square of the largest numerator/denominator digit count among that operation's operands. Exhaustion throws limit_exceeded with details.reason arithmetic_work before costly GCD/multiplication. A permitted byte/token/digit count does not guarantee admission when cumulative work exceeds this bound.","Division by zero (1/0, 0/0, 1/(2-2)) -> not_computable with details.reason \"division_by_zero\"; 0 to a negative power -> not_computable with details.reason \"zero_to_negative_power\". 0^0 is 1. These are evaluated only when that node is evaluated.","Result: fraction is the exact result in lowest terms with the sign on the numerator, denominator \"1\" for integers, \"0\" is 0/1. value: with scale (which requires rounding) the exact result is rounded once to scale fraction digits and written with exactly that many digits, exact true iff nothing changed. Without scale the exact result must be a terminating decimal and value is canonical (trailing zeros stripped) with exact true; a non-terminating result (1/3) -> not_computable with details.reason \"non_terminating_decimal\" (give a scale). A negative result that rounds to zero is written \"0.00\", never \"-0.00\".","rounding alone (without scale) is allowed: it only feeds round() calls. scale without rounding -> invalid_input.","Rounding modes act on the exact rational result and are: half_even (ties to the even neighbour), half_up (ties away from zero), half_down (ties toward zero), half_ceiling (ties toward +infinity), half_floor (ties toward -infinity), up (away from zero), down (toward zero), ceiling (toward +infinity), floor (toward -infinity), unnecessary (any inexact result -> not_computable with details.reason \"rounding_necessary\"). rounding \"unnecessary\" applied to an inexact round() call or an inexact final scale -> not_computable with details.reason \"rounding_necessary\".","Validation order: input is an object; unknown field; missing expression; field types and variable values (input rational reductions consume arithmetic work); scale/rounding pairing; then the expression pipeline in the order above. Literal rational reductions consume work during parsing, so their work limit can be reported before a later syntax fault. Each syntax/name/arity/depth check still precedes evaluation of the resulting tree."],"limits":{"max_expression_bytes":2000,"max_tokens":500,"max_depth":64,"max_intermediate_digits":200,"max_arithmetic_work":50000,"max_variables":100,"max_call_args":50,"max_power_exponent":100,"max_literal_chars":62,"max_scale":40},"pricing":{"status":"unpriced","charge_usd":null},"input_schema":{"type":"object","additionalProperties":false,"required":["expression"],"properties":{"expression":{"type":"string","minLength":1,"maxLength":2000,"description":"Arithmetic expression, at most 2,000 UTF-8 bytes and 500 tokens."},"variables":{"type":"object","maxProperties":100,"additionalProperties":{"type":"string","minLength":1,"maxLength":62,"pattern":"^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"},"description":"Named decimal values usable in the expression. Names match ^[a-z_][a-z0-9_]{0,31}$ (checked by the tool; \"__proto__\" is an ordinary valid name)."},"scale":{"type":"integer","minimum":0,"maximum":40,"description":"Fraction digits of the reported value. Requires rounding. Omit for the exact canonical value."},"rounding":{"type":"string","enum":["half_even","half_up","half_down","half_ceiling","half_floor","up","down","ceiling","floor","unnecessary"],"description":"Rounding mode for the final scale rounding and for every round(x, n) call. Required when scale is given or round() is used."}}},"output_schema":{"type":"object","additionalProperties":false,"required":["value","exact","fraction"],"properties":{"value":{"type":"string","minLength":1,"maxLength":9000,"description":"Result: canonical without a scale, otherwise fixed at scale fraction digits."},"exact":{"type":"boolean","description":"true when value equals the exact result of the expression (the final scale rounding changed nothing). round() calls are part of the expression and do not clear it."},"fraction":{"type":"object","additionalProperties":false,"required":["numerator","denominator"],"properties":{"numerator":{"type":"string","minLength":1,"maxLength":201,"description":"Signed numerator of the exact result in lowest terms."},"denominator":{"type":"string","minLength":1,"maxLength":200,"description":"Positive denominator in lowest terms; \"1\" for integers."}}}}},"examples":[{"input":{"expression":"0.1 + 0.2"},"output":{"value":"0.3","exact":true,"fraction":{"numerator":"3","denominator":"10"}}},{"input":{"expression":"price * qty * (1 + tax / 100)","variables":{"price":"19.99","qty":"3","tax":"8.25"},"scale":2,"rounding":"half_up"},"output":{"value":"64.92","exact":false,"fraction":{"numerator":"2596701","denominator":"40000"}}},{"input":{"expression":"round(10 / 3, 4) - abs(-1)","rounding":"half_even"},"output":{"value":"2.3333","exact":true,"fraction":{"numerator":"23333","denominator":"10000"}}}],"execute_url":"/v1/tools/decimal-expression-evaluate/versions/1.0.0/execute"}