
exact-calc
io.github.flacalesv0.1.0Updated Oct 2, 2026
Exact arithmetic for AI agents. Two independent engines cross-check every result.
Overview
Lets an assistant evaluate arithmetic expressions exactly with rational and decimal math, cross-checked by two independent engines.
- What it does
- Provides tools to calculate expressions exactly, verify a result with a trust verdict, test whether two expressions are mathematically equal, convert a decimal string to an exact fraction, and report engine status. Expressions are parsed with an AST whitelist rather than eval, and numeric literals are rebuilt from source text so decimal values stay exact. Results are returned only when a Python engine and a separate C++ evaluator agree.
- When to use it
- Useful when an assistant must produce trustworthy numbers rather than plausible-looking ones: financial or unit calculations, large integers, exact fractions, and checks where floating-point error would matter. Also usable from the command line by any agent that can run shell commands.
- Requirements
- Runs locally over stdio as a PyPI package (exact-calc-mcp), typically launched with uvx, or installed and run as a Python module. Requires a Python environment with the mcp SDK 1.x. The optional C++ engine is skipped automatically when no compiler is available. No accounts, API keys, or environment variables are declared.
Installation
In SourceWeft
- Open exact-calc in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
exact-calc-mcp
[CI] [MIT License] [Python] [MCP] [Tests]
让 AI 用代码精确计算,而不是靠猜。
给 AI agent 用的精确计算工具。通过 MCP 或命令行调用,结果由两个独立编写的引擎交叉验证后才返回。
English: Exact arithmetic for AI agents. LLMs do not compute — they generate plausible-looking tokens. This MCP server outsources arithmetic to two independently written engines (exact Python rational/decimal arithmetic + a separate C++ evaluator) and only returns a result when both agree.
问题:LLM 不做算术
这不是"模型不够聪明",是架构决定的。语言模型是自回归生成器,它输出的是在统计上最像答案的 token 序列。它没有算术电路,没有进位链,没有中间寄存器。
所以当你问它 0.1 + 0.2 时,它不是在算,是在回忆"这段对话里通常接什么"。绝大多数时候它答 0.3 —— 因为训练语料里就是这么写的。这个答案恰好是对的,但不是因为它算对了。
而在下面这些地方,它会稳定地错:
注意最后两行的区别:2 ** 100 在 float 里其实是精确的(它是 2 的幂),所以它不该被当作浮点误差的例子 —— 真正的例子是 1e16 + 1,那个 +1 被彻底吃掉了。挑例子也得较真。
结论:不要让模型算。 让它理解意图、编排步骤,把确定性计算外包给程序。这就是这个项目在做的事。
它和别的计算器 MCP 有什么不同
大部分 calculator MCP 就是一层 eval() 包装。这个不是。
最后一条是最容易被忽略、也最关键的,下面单独讲。
安装
只依赖官方 mcp SDK(锁在 1.x,见下方说明)。C++ 引擎是可选的,找不到编译器会自动跳过,不影响使用。
为什么锁
mcp<2:mcp 2.0 把FastMCP改名成了MCPServer(mcp.server.mcpserver),mcp.server.fastmcp变成一个会主动抛ModuleNotFoundError的占位模块。本项目按 1.x 的 API 写,所以pyproject.toml里写的是mcp>=1.0.0,<2.0.0。迁移到 2.x 见 官方迁移指南。
用法一:接入 MCP 客户端
任何支持 MCP 的客户端都能直接用。下面覆盖了主流的几种。
先记住两条命令,下面所有配置无非是换一种写法:
Claude Desktop
编辑 claude_desktop_config.json(macOS:~/Library/Application Support/Claude/;
Windows:%APPDATA%\Claude\):
Claude Code
Cursor
编辑 ~/.cursor/mcp.json(全局)或项目里的 .cursor/mcp.json:
VS Code
编辑 .vscode/mcp.json(工作区)或用户设置里的 mcp 段。
注意 VS Code 用的键是 servers 而不是 mcpServers,并且要显式写 type:
Codex
编辑 ~/.codex/config.toml:
Cline / Continue / 其他
配置结构基本一致,都是 mcpServers 下加一项:
不想用
uvx也行:把command换成python、args换成["-m", "exact_calc_mcp", "--serve"],前提是那个 Python 环境里装了本项目。
提供的工具
calculate 返回的结构:
用法二:命令行
不需要 MCP 客户端也能用 —— 任何能执行 shell 的 agent 都可以调。
支持的语法:+ - * / // % ** 和括号,函数 sqrt abs round min max factorial gcd lcm ln log log10 exp pow,常量 pi、e。运算符优先级与 Python 完全一致(-2 ** 2 是 -4,2 ** 3 ** 2 是 512)。两点和 Python 直觉不同:log(x) 是自然对数(与 math.log 一致),log(x, base) 指定底数,常用对数用 log10;round 是远离零的四舍五入(round(2.5) = 3),不是 Python 内建的银行家舍入。
架构
四个设计决定
1. 不用 eval()
eval() 会把 __import__('os').system('rm -rf /') 当成正常表达式执行。这里用 ast 解析成语法树后逐节点求值,只放行白名单里的节点类型和函数名。Attribute、Subscript、Lambda、IfExp、Comprehension 全部拒绝。
函数名先于实参校验 —— 否则 open('x','w') 会先因为字符串参数报一个"无法识别的数字",把真正的拒绝理由盖掉。
2. 数字从源码文本重建(最关键的一条)
0.1 在 ast 里已经变成 float(0.1),而它的真实值是
精度在拿到它的那一刻就丢了。所以这里用 ast.get_source_segment() 取回源码里的字符串 "0.1",再交给 Decimal("0.1") —— 这才是精确的十进制 0.1。
去掉这一步,整个项目的精确性主张都是空话。有专门的测试守着这条(test_数字字面量从源码文本还原)。
3. 双数值域,自动降级
Fraction—— 有理数精确。分数运算、完全平方数的开方走这条。Decimal—— 十进制精确。无理数(sqrt(2)、pi、ln)走这条。
先试 Fraction,装不下就自动降级到 Decimal,并在结果里注明为什么。sqrt(144) 仍然是精确的 12,因为它能开尽。
4. 双引擎交叉验证
一个实现错了,你不会知道。 所以这里有两个:
- Python 引擎:
Fraction/Decimal,任意精度,精确 - C++ 引擎:手写的递归下降解析器,
long double,故意不精确
C++ 那边只回答"量级和有效数字对得上吗",不参与精度判定。两边对不上就明确报警,而不是返回一个可疑的数字。
C++ 引擎明确不支持的函数(factorial、gcd 等)会返回退出码 2,调用方据此跳过验证,而不是误报不一致。
关于 C++ 源码:它全部是 ASCII 的。中文标识符在 MSVC、clang、gcc 之间行为不一致,一个要发到 GitHub 上的 C++ 文件不该埋这个雷。
局限(诚实说明)
- 不支持复数:
sqrt(-1)会报错。 - 三角/双曲函数未实现:
Decimal没有这些函数,需要它们的话得引入mpmath。 %对负数取模的行为:Python 侧用operator.mod(结果为负时向负无穷取整),C++ 侧用fmodl(向零取整)。两者对负操作数的结果不同,这类表达式会被交叉验证判为不一致 —— 这是已知的行为差异,不是 bug。- C++ 引擎精度有限:
long double约 18~19 位有效十进制。超过这个量级的验证会以"相对误差 1e-18,差异来自精度上限"的形式通过。 0x10这类十六进制字面量:Python 引擎支持,C++ 引擎不支持(strtold要求十六进制浮点必须带p指数),会走"已跳过"。round是远离零舍入:round(2.5) = 3、round(-2.5) = -3。和 Python 内建round()的银行家舍入(round(2.5) = 2)不同 —— 这是故意的,三个引擎(Fraction / Decimal / C++roundl)在这件事上保持一致,比跟随 Python 的内建行为更重要。- 资源护栏:整数次幂的指数上限 ±10,000,000(结果约 300 万位);
factorial参数上限 1,000,000;整数转字符串上限 1,000 万位(Python 3.11+ 默认只有 4300 位,本工具已放宽);指数超过 1e±100000 的结果以科学计数法表示(如1e+999999999);工作精度限定在 1~100,000 位。超出护栏的表达式会被明确拒绝,而不是把机器算死。 pi、e预存 1020 位有效数字:超过这个位数的常数相关计算以预存值为准(sqrt(2)、ln(2)这类现场计算的值不受此限,随精度参数走)。- 它不是符号计算系统:不做化简、解方程、求导。需要这些请上
sympy。
测试
44 个用例,全绿,覆盖四块:
具体地:
- 精确性:浮点误差被消除、大整数不丢精度、优先级与 Python 一致
- 安全性:14 个注入/越权表达式全部被拒
- 交叉验证:两路结果一致;C++ 超范围时正确跳过而非误报
- 协议:
serverInfo.version是项目版本而不是 mcp SDK 的版本; 5 个工具都在且都有描述(描述是给模型读的 prompt,缺了模型就不会调)
为什么值得做这件事
给 AI 加一个计算器,听起来是个小工具。但它背后的原则很大:
模型负责理解与编排,程序负责确定性计算。
把这条原则推到底,就是 agent 工程的核心 —— 凡是"有唯一正确答案"的事,都不该交给一个概率模型去猜。日期计算、单位换算、财务对账、代码执行,全都一样。
这个项目是那条原则最小的一个实例。
贡献
欢迎 issue 和 PR。开始之前请读 CONTRIBUTING.md。
跑测试只要一条命令,不需要额外依赖:
安全
这个工具不执行任意代码 —— 表达式经 ast 白名单逐节点求值,不走 eval(),
属性访问、下标、lambda、推导式一律拒绝。相关测试在
tests/test_engine.py 的「安全性」一节。
如果你发现了绕过白名单、拒绝服务或资源护栏失效的问题, 请按 SECURITY.md 的方式私下报告,不要开公开 issue。
变更记录
见 CHANGELOG.md。
License
MIT —— 见 LICENSE。
Source: README.md at commit 5bb03b1
Tools
0Version history
1- v0.1.0LatestOct 2, 2026


