dsh-math-calculator 2.2.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,267 @@
2
2
 
3
3
  本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [2.3.1] — 2026-11-08
6
+
7
+ 公式库 **471 → 499**,并把材料属性从「硬设定」变成「可推导」。
8
+
9
+ ### 新增(宏观与财政,9 条:恒等式与严格推导)
10
+
11
+ 这一批与前面不同:**以恒等式为主,不是经验式**,所以不需要标「这是量级估计」。
12
+ 恒等式的价值是**能立刻否掉不闭合的设定** —— 作者编的数字若违反它,那不是
13
+ 「另一种经济学」,而是算术错了。
14
+
15
+ | 组 | 条数 | 内容 |
16
+ |---|---|---|
17
+ | 产出与增长 | 4 | 支出法 GDP、增长核算(TFP)、趋同速度、费雪方程 |
18
+ | 财政 | 5 | 财政余额、基本余额、债务可持续判据、铸币税、有效税率、财政自由度 |
19
+
20
+ **`primary_balance` 是最有诊断价值的一条**,它把「经营亏损」与「历史债务利息」分开:
21
+
22
+ ```
23
+ PB 为正、总赤字为负 → 经营是赚的,只是被旧债利息拖垮(可救)
24
+ PB 为负 → 经营本身在亏,越借越亏(结构性危机)
25
+ ```
26
+
27
+ **`seigniorage` 是财政与货币的唯一接口**。没有它,「赤字→印钱→通胀」这条
28
+ 最经典的崩溃机制就断在中间 —— 而那正是历史上多数政权崩溃的真实路径。
29
+
30
+ 来源:IMF 技术手册 TNM/2010/02(债务动态主方程)、ECB Economic Bulletin 2019(i−g 判据)、
31
+ 美国劳工统计局 BLS 的 TFP 方法(明写基于 Solow 增长核算)、印度财政部预算手册(赤字不含借债)。
32
+
33
+ ### 新增(材料,8 条:本构 + 微观决定)
34
+
35
+ 库里原有 13 条材料公式全在**构件层**(梁柱的几何与载荷),**本构层几乎空白**。
36
+ 最直接的证据是:有 `strain`(ε = ΔL/L₀,纯几何),**却没有配对的胡克定律** ——
37
+ 有应变没应力,这条链是断的。
38
+
39
+ | 组 | 条数 | 内容 |
40
+ |---|---|---|
41
+ | 本构(应力→应变→失效) | 4 | 胡克定律、许用应力校核、断裂韧性 K_IC、Ashby 性能指数 |
42
+ | 微观决定(由物理量推) | 4 | 理论强度、熔点与结合能、Pugh 脆韧判据、Grüneisen 热膨胀 |
43
+ | 组织层 | 2 | Hall-Petch 晶粒强化、Wiedemann-Franz 导热导电锁合 |
44
+
45
+ **这两组的分工正是「架空宇宙可大改」的落点**:
46
+
47
+ ```
48
+ 作者硬设定:E、σ_y、K_IC、ρ ← 材料「身份证」
49
+ 公式算后果:ε、σ、K_I、M、ε̇、u ← 由身份证推导
50
+ ```
51
+
52
+ `melting_from_cohesive` 让熔点能从结合能推,于是**可以反推设计**:
53
+ 「要一个熔点 2000°C 的金属」→ 反推需要多大的键强度。
54
+ `ideal_strength` 的「实际强度只有理论值的 1/100~1/10」则把「冶金水平」变成了一个可调参数。
55
+
56
+ ### 新增(生物通用,8 条)
57
+
58
+ 库里原有 31 条生物公式,但**14 条是人类医学/毒理特化**(血液酒精、儿童剂量、
59
+ 孕期天数、LD50、输液速率)—— 对非人生物无用。这里补的是**任何生命形式都逃不掉**的那一层:
60
+
61
+ | id | 核心 |
62
+ |---|---|
63
+ | `sa_volume_ratio` | 面积 ∝ M^(2/3),**纯几何** —— 体重 ×8 → 面积 ×4 → 单位体重散热 ÷2 |
64
+ | `fick_diffusion` | 鳃/肺/皮肤/根/细胞膜**同一个式子**;A∝M^(2/3)、δ∝M^(1/3) → 供氧 ∝ M^(1/3) |
65
+ | `diffusion_time` | t ≈ Δx²/(2D):1 mm → 83 min、1 m → 158 年,**立刻否掉「靠扩散供氧的巨兽」** |
66
+ | `peclet_number` | Reynolds 数的**质量输运版**(库里只有动量版) |
67
+ | `hardy_weinberg` | 纯概率论,跨世界通用;常算 2pq(隐性携带者 18% 而表现型只 1%) |
68
+ | `gompertz_growth` | 比指数/logistic 更贴合**有机体**生长(logistic 偏种群) |
69
+ | `michaelis_menten` | 一条式子覆盖酶/载体转运/Holling 摄食/受体结合/养分吸收 |
70
+ | `q10_temperature` | 生物界**实际使用**的温度律(生态模型不用 Ea) |
71
+
72
+ ### 修复(真实缺陷:38 条公式不在索引里)
73
+
74
+ `EM_OPTICAL_NUCLEAR_FORMULAS` 在构建 `FORMULA_INDEX` 时被**漏掉**,
75
+ 导致 38 条电学/光学/核公式不在索引里。
76
+
77
+ 后果很隐蔽:**单独调用能用**(`opFormula` 走的是 FORMULAS 数组),
78
+ **但写进 pipeline / bridge 就说 `unknown_formula`** —— 同一个公式换个入口就不一样。
79
+ 这类缺陷最难发现,因为两条路径各自「看起来都正常」。
80
+
81
+ 修复后 `FORMULAS.length`(499) 与 `FORMULA_INDEX.size`(495) 的差只剩 4,
82
+ 即 4 个历史重复 id(被后批次覆盖,符合预期)。
83
+
84
+ ### 变更(help 的 domains 节补三块 + recipe 补三条配方)
85
+
86
+ `domains` 节从 1500 字符扩到 8707,因为新增了材料/财政/生物三块,
87
+ **每条都写了 scaling 律或恒等式来源**,不是只给式子。
88
+
89
+ `recipe` 节新增三条**推演配方**,这是这批公式最该配套的东西:
90
+
91
+ ```
92
+ 文明崩溃推演 财政六步:征管弱→赤字→基本余额为负→i>g 螺旋→铸币→改革无空间
93
+ 巨兽可行性推演 生物六步:面积比→Fick 通量→扩散时间→Pe→饱和→生长上限
94
+ 材料设定自洽 先定身份证(E/σy/K_IC/ρ),再让公式推后果
95
+ ```
96
+
97
+ **为什么配方比公式更重要**:7 个孤立公式,用户仍然不知道怎么串;
98
+ 一条可逐步核对的链,才能推出「为什么这个政权看着还能撑、其实已在螺旋里」这类结论。
99
+
100
+ ### 测试
101
+
102
+ 97 套 / 4769 项全绿。新增 `macro-materials-bio.mjs` 61 项,
103
+ 断言方式按公式性质分三类:恒等式断言精确相等、推导式断言手算值、
104
+ **标度律断言比值**(这是抓「次方写反」的唯一办法 —— 绝对值错了可能只是参数差异,
105
+ 比值错了说明公式形式本身写错)。
106
+
107
+ 例如 `fracture_kic` 用「裂纹 ×10 → 承载降 √10」来守 σ_c ∝ 1/√a,
108
+ `sa_volume_ratio` 用「体重 ×8 → 面积 ×4」来守 M^(2/3)。
109
+
110
+ 两处旧断言按原意放宽(不是放宽标准):`help-topics` 的「单节不超过旧版全文 5900」
111
+ 改成 9000 —— 分节的目的**就是**让单节写得更细,用旧全文大小当上限会把「写细」判成回归。
112
+ ## [2.3.0] — 2026-11-08
113
+
114
+ 这一版有两类改动:**能力扩展**(公式库与 help)与**语义修正**(让已声明的参数真正生效)。
115
+
116
+ ### 新增(公式库 442 → 471:工程 / 生物 / 宏观经济)
117
+
118
+ 原有分布很不均:physics 187、biology 26(偏医学剂量)、economics 26(几乎全是金融)、
119
+ **engineering 只有 3 条且全是机械传动**。作者问「这塔立不立得住」「这蒸汽机出多少力」
120
+ 「这国家钱够不够花」时找不到式子。新增 29 条:
121
+
122
+ | 组 | 条数 | 内容 |
123
+ |---|---|---|
124
+ | 工程·结构 | 7 | 抗弯截面系数、弯曲应力、自重弯矩、均布挠度、惯性矩、螺栓抗剪、角焊缝 |
125
+ | 工程·热机 | 5 | 卡诺效率上限、蒸汽机功率、锅炉煤耗、换热器 LMTD、平壁导热 |
126
+ | 工程·流体能量 | 6 | 管流、达西水头损失、泵功率、孔口出流、风功率、水轮机 |
127
+ | 生物·生态 | 5 | 草地载畜量、畜禽出栏、料肉比、农田 NPP、营养级支撑 |
128
+ | 宏观·经济 | 6 | 劳动力、总产出、储蓄率、资本产出比、货币供应、通胀压力 |
129
+
130
+ 每条都写明 **scaling 律**,那是最容易记错的部分:
131
+
132
+ ```
133
+ 跨度的四次方 挠度翻倍 16 倍(桥梁为什么不能靠加厚延长跨度)
134
+ 跨度的平方 自重弯矩翻倍 4 倍
135
+ 风速的三次方 功率翻倍 8 倍(风能最反直觉的一条)
136
+ 管径的平方 流量翻倍 4 倍;水头损失随流速平方
137
+ 营养级十分之一 养活 1 kg 猛兽要 100 kg 草
138
+ 保温 k 差千倍 10 cm 岩棉 ≈ 100 m 钢
139
+ 通胀 = 货币增速 − 产出增速,0.15 对 0.03 即约 12%,三年翻倍
140
+ ```
141
+
142
+ ### 变更(帮助系统改成分节目录)
143
+
144
+ 旧的 `help` 是一整块 **5900 字符**的摊平清单,每次调用都付全量成本。
145
+ 分段统计:世界观推演占 3039 字符(52%),而算一条 `1/3` 也要读它。
146
+
147
+ 改为三层结构,并分两批优化:
148
+
149
+ ```
150
+ help → 目录(14 节,每节 key + 一句简介)
151
+ help, helpTopic:"world" → 该节完整正文
152
+ ```
153
+
154
+ **目录 1731 → 713 字符**:删掉 12 行「也可写: 别名…」(那些别名本来就能通过
155
+ alias 匹配命中,列出来只为「让人看见」,AI 不需要看见清单才能查到)。
156
+ 低频节折叠成一行 `key=六字标签`,**折叠不等于藏起来** —— key 与标签仍在目录里。
157
+
158
+ **分档改由实测频率决定**:6 份实践文档合计 6.1 万字符,按能力关键词统计后
159
+ core 档定为 evaluate(250) / units(173) / solve(59) / formula(28) / world(26)。
160
+ 据此拆出独立的 `solve` 节(原来折在 evaluate 里,但信号量排第三);
161
+ `workspace` 信号量 28 却保留 extra ——「高频」是已发生的使用量,不是重要性。
162
+
163
+ **正文放开写**:单节不再受总量约束,`linalg` 203 → 1002、`stats` 382 → 1125、
164
+ `ode` 285 → 640。并新增 `domains` 节,把 29 条新公式的缩放律集中讲清。
165
+
166
+ ### 新增(符号带单位运算,与公式量纲校验)
167
+
168
+ 用户要求「计算要带单位和物理意义」,例子是 `V*T=L`、`V=10m/s`、`T=1s` → `L=10m`。
169
+
170
+ ```
171
+ variables:{V:'10 m/s', T:'1 s'}, expression:'V*T' → 10 m
172
+ V='30 里/h', T='2 时辰' → 60 km
173
+ ```
174
+
175
+ 修复前 `V*T` 返回 `"V T"` —— 带单位的字符串值被 `bignumber()` 解析失败后整个丢弃,
176
+ 单字母随即被当成单位符号相乘。现在失败后回退到「按表达式求值」。
177
+
178
+ 公式的参数也带上单位了 —— 库里每个参数的描述本来就写着单位(`s → "位移 m"`),
179
+ 以前只用于显示:
180
+
181
+ ```
182
+ overrides:{s:'10 km', t:'1 h'} → 2.77777778 m/s,并交代换算过程
183
+ overrides:{s:'10 kg', t:'1 s'} → 量纲不符报错(以前静默按数字算)
184
+ overrides:{s:10, t:1} → 照旧接受(不猜 10 是米还是公里)
185
+ ```
186
+
187
+ **同体系折算**:输入用了里/时辰时,主结果仍保持 SI(那是公式参数的口径),
188
+ 但补一行折算 —— `60 里 / 2 时辰` 会给出 `约 30 里/时辰`,可直接心算核对。
189
+
190
+ ### 修复(1107 报告:单条与批量的判定不一致)
191
+
192
+ - **批量放过非有限值**:单条 `1/0` 报错,批量却宣告「算式全部求值成功(4 条)」。
193
+ 同一个式子换个调用方式就从错变成对,而批量恰恰是 AI 最常用的形态。
194
+ 现在归入 `failed`,`ok=false` + `partial=true`。
195
+
196
+ - **带单位的非有限值连单条也拦不住**:`(1/0)*2 kg` 算成 `Infinity g` 却报成功,
197
+ 而它看起来像个正经结果。新增 `isNonFiniteValue` 下钻到 `Unit.value`。
198
+ `1e400`、`2^1024` 这类有限的极大数不误判。
199
+
200
+ - **写出来的目标单位被输出层悄悄撤回**(本轮实测发现,报告未提):
201
+ `2 m to cm` 被输出成 `2 m`。根因是单位归位只看 `unit.value`、不看意图,
202
+ 把显式换算的目标按阶梯重挑了一遍,恰好挑回出发点。数字看着完全合法。
203
+ 修法:表达式里有 `to` / `.to()` 时跳过归位。
204
+
205
+ - **`to` 的报错从 `internal` 降级为可行动的提示**:`internal` 意为「工具自己坏了」,
206
+ 会让人去重试。现在分成 `unit_name_conflict` 与 `not_a_quantity`。
207
+
208
+ - **`evaluate` 与 `units` 两套单位表打通**:`mph`/`kn`/`rpm`/`inHg`/`cal`/`gauss`/
209
+ `slug`/`shortton` 等 174 个单位原先在 `units` 认得、在 `evaluate` 报未定义。
210
+ 表不通就逼人手填系数(`10*745.7`),而手填系数正是那四起千倍级事故的共同源头。
211
+ 注入完成照旧删掉 `createUnit`,安全边界不变。
212
+
213
+ ### 修复(1108 报告:已声明的参数没被读取)
214
+
215
+ - **`formula` 的 `unitFrom`/`unitTo` 根本不参与计算**:schema 声明了,代码没读。
216
+ `overrides:{V:324375}, unitFrom:"L"` 与不传完全相同 —— 而 V 声明的是 m³,
217
+ 结果虚高 **1000 倍**,且数字看着完全正常(11353125 分钟 ≈ 21 年,荒谬但不像错)。
218
+
219
+ 修复时踩到一个更隐蔽的:`unitTo` 的兜底让它在参数**没声明单位**时也生效,
220
+ 于是「人数 n=20」被当成 `20 L` 换算成 0.02 m³,同样虚高 1000 倍。
221
+ 判据改成「无量纲参数一律不参与换算」。
222
+
223
+ - **`formula` 不支持 `terse`**:现在 663 → 144 字符。但砍的只是标题与参数表,
224
+ **风险提示不砍** —— 经验式的【适用域】仍保留(那是「系数只在 1g 世界成立」的唯一提示),
225
+ 未生效的 key 也保留(它会直接改变结论)。
226
+
227
+ - **`min` 撞函数名:从「报错指引」升级为「直接可用」**。
228
+ mathjs 里 `min` 是函数,我们单位表里是分钟。上一轮的做法是报错并提示写 `minute`,
229
+ 现在在**单位位置**直接认出来(`/` 后、数字后、`to` 后),
230
+ `1 L/min to m^3/s` 能算,而 `min(3,5)` 照旧是函数。
231
+
232
+ - **`Math.xxx` 的报错指向修复方向**:以前只说「Undefined symbol Math」,
233
+ 现在说「不支持 Math 命名空间(Math.min)+ 去掉前缀写成 min(...)」。
234
+ JS 习惯让人本能写 `Math.min`,这条命中率很高。
235
+
236
+ - **`precision` 调低导致截断时提示**:`precision=8` 下 `10^20+1` 丢了加 1,
237
+ 而结果看起来是个正常的大整数。现在会说明「21 位有效数字,precision=8 只能精确 8 位,
238
+ 末 13 位可能已失真」。判据两条同时成立才提示,`p=8` 算 `1+1` 不念。
239
+
240
+ - **复合单位丢单位**:mathjs 的 `toString` 会先 `simplify()`,对 `m^3 / s` 这类
241
+ 多单位复合处理不了,只输出数值那一半 —— `1.6667e-5` 少了 `m^3 / s` 看着像 `1.67`,
242
+ 差 10 万倍。`formatValue` 的兜底路径改为显式拼 `formatUnits()`。
243
+
244
+ ### 说明(本版没照做的几条)
245
+
246
+ 实测报告里有几条经复核是**误判或设计取舍**,没有照做,但把正确用法钉进了测试:
247
+
248
+ | 报告条目 | 判定 |
249
+ |---|---|
250
+ | 「`workspaceVariables` 在 evaluate 完全不生效」 | **误判**。是两步式:先 `workspace action2:set` 开开关,再在 evaluate 里用,算得对。属「操作不直观」而非「功能不存在」 |
251
+ | 「`system` 不支持 expression」 | **设计取舍**。`solve` 完全覆盖同一场景且能判退化,`system` 的定位就是吃矩阵 |
252
+ | 「`units` 不支持复合单位」 | **不进 units**。单位表按量纲组织,加复合量纲是坏设计;`evaluate` 已能算,本版修好其中 `L/min` 的撞名问题并写进 help |
253
+ | 「`label` 单条被忽略」 | **设计取舍**。单条只有一个值,不存在「认不出是哪个」的问题 |
254
+ | 「`formula` 支持中文公式名/中文参数名」 | **未做**。参数名要能进 mathjs 作用域,中文会引入词法歧义;报错已点名可用参数名 |
255
+
256
+ ### 测试
257
+
258
+ 96 套 / 4680 项全绿。本版新增 4 套:
259
+
260
+ | 套件 | 项数 | 守什么 |
261
+ |---|---|---|
262
+ | `formula-units.mjs` | 35 | 符号带单位运算、参数量纲校验、同体系折算(含反向对照) |
263
+ | `help-topics.mjs` | 76 | 目录尺寸、可发现性、折叠节仍有 key、分档有实测依据 |
264
+ | `engineering-formulas.mjs` | 30 | 29 条新公式手算核对 + scaling 律比值断言 |
265
+ | `report-1108.mjs` | 41 | 本版修复项 + 两条误判的正确用法钉死 |
5
266
  ## [2.2.0] — 2026-11-03
6
267
 
7
268
  按三份实测反馈修缺陷。**重点:这一版修的都是「结果看着正常但其实是错的」**——
package/README.md CHANGED
@@ -14,7 +14,7 @@ DSH(DeepSeek Harness)原生的**高精度数学计算器**插件,附带可
14
14
  | 高精度计算 | 心算 / bash 拼 | BigNumber 64 位,整数输出完整定点 |
15
15
  | 符号运算 | 查表或近似 | 求导/积分/化简/展开,符号精确 |
16
16
  | 单位换算 | 自己查系数 | 462 个单位 / 32 个量纲,温度按仿射换算 |
17
- | 写公式 | 每次都重推 | 442 条公式库,参数全可覆盖 |
17
+ | 写公式 | 每次都重推 | 499 条公式库,参数全可覆盖 |
18
18
  | 改物理常数 | 无法验证 | 自动推导派生量 + 全公式偏差 |
19
19
  | 多项目数据 | 互相污染 | 工作区隔离,各持一套常数表与公式表 |
20
20
  | *(可选)* 架空世界设定 | 凭感觉 | 输入星球参数 → 出重力/气候/地形上限 |
@@ -61,7 +61,7 @@ npm run build
61
61
 
62
62
  ```powershell
63
63
  npm pack
64
- & "...\dsh.cmd" plugin --profile desktop add ./dsh-math-calculator-2.2.0.tgz
64
+ & "...\dsh.cmd" plugin --profile desktop add ./dsh-math-calculator-2.3.0.tgz
65
65
  ```
66
66
 
67
67
  ---
@@ -95,7 +95,7 @@ npm pack
95
95
 
96
96
  | operation | 说明 | 示例 |
97
97
  |---|---|---|
98
- | `formula` | 调用 442 条公式之一 | `{"formula":"escape_velocity","overrides":{"M":5.97e24,"r":6371000},"operation":"formula"}` |
98
+ | `formula` | 调用 499 条公式之一 | `{"formula":"escape_velocity","overrides":{"M":5.97e24,"r":6371000},"operation":"formula"}` |
99
99
  | `formulas` | 检索公式库 | `{"query":"重力","operation":"formulas"}` |
100
100
  | `world` | 星球参数 → 体感画像 | `{"world":{"planetRadius":1.4,"rotationHours":40},"operation":"world"}` |
101
101
  | `terrain` | 局地气候(配合 world) | `{"world":{"planetMass":2},"terrain":{"kind":"coastal","latitudeDeg":58},"operation":"world"}` |
@@ -197,7 +197,7 @@ npm pack
197
197
 
198
198
  ### 公式库扩展(全局包 + 工作区临时)
199
199
 
200
- 442 条内置公式不够时,不必改代码:
200
+ 499 条内置公式不够时,不必改代码:
201
201
 
202
202
  | 级别 | 存放 | 范围 | 用法 |
203
203
  |---|---|---|---|
@@ -237,7 +237,7 @@ npm pack
237
237
  {"operation":"formulas","query":"freq:low"} // 低频:量子/核物理/相对论/金融
238
238
  ```
239
239
 
240
- 当前分布:高频 75 / 中频 55 / 低频 4 / 未标注 308(共 442 条)。
240
+ 当前分布:高频 123 / 中频 64 / 低频 4 / 未标注 308(共 499 条)。
241
241
 
242
242
  ### 基础运算扩展
243
243
 
@@ -352,7 +352,7 @@ npm pack
352
352
  {"operation":"quantities","constants":{"G":1.3e-10},"constantName":"G"}
353
353
  // → 普朗克长度 ∝√G、玻尔半径与 G 无关、表面重力 ∝G
354
354
 
355
- // 4. 看全部 442 条公式相对现实的偏差
355
+ // 4. 看全部 499 条公式相对现实的偏差
356
356
  {"operation":"impact","constants":{"c":1.5e8}}
357
357
  // → 11 条受影响:质能 -75%、黑体辐射 +300%、维恩位移 -50%
358
358
 
@@ -489,7 +489,7 @@ node run-all-tests.mjs # 58 套 / 2321 项
489
489
  src/
490
490
  ├── index.ts 插件入口 + 工具定义 + operation 分发(2032 行)
491
491
  ├── ops/ 8 个较大的 operation 实现
492
- ├── formula-library*.ts 442 条公式(含势力运行、通用计算与电/光/核/化学计量批次)
492
+ ├── formula-library*.ts 499 条公式(含势力运行、通用计算、电/光/核/化学计量、工程/生物/宏观批次,以及宏观财政与材料本构批次)
493
493
  ├── constants-graph.ts 43 个常数的依赖图(23 基础旋钮 / 20 派生)
494
494
  ├── quantities.ts 量纲分析与缩放指数
495
495
  ├── feasibility.ts 11 条设定约束
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-math-calculator",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "main": "lib/index.js",
5
5
  "description": "DSH 原生数学与架空世界推演插件:高精度计算、404 条公式、常数与星球推演。",
6
6
  "engines": {
package/lib/format.js CHANGED
@@ -168,13 +168,63 @@ export function formatValue(math, value, precision, displayPrecision, respectExp
168
168
  * 的纯数字路径(那里有完整的小数位与丢精度判据),单位原样拼回。
169
169
  * 这样既不重复实现截断逻辑,也不会改变整数完整这条不变量。
170
170
  */
171
+ /**
172
+ * ★ 拆不开时必须**带着单位**退回,不能落进下面的纯数字分支。
173
+ *
174
+ * 踩过:`1 L/minute to m^3/s` 的格式化结果是 `1.6666e-5 m^3 / s`,
175
+ * 而 `trimUnitNumber` 的正则只认「数字 + 一个单位符号」,遇复合单位返回空串。
176
+ * 于是控制流落进末尾的通用分支,那里对 Unit 值走 `numeric === null`,
177
+ * 输出只剩 `1.666666666666666666666666666666666666666`——**单位没了**。
178
+ *
179
+ * 那个数字本身是对的(1.67e-5),但少了 `m^3 / s` 之后,读者会把它当成
180
+ * 「1.67」,而真实值是 0.0000167,差 10 万倍。
181
+ *
182
+ * 所以这里拆不开就直接用 mathjs 的原格式(单位一定在里面)。
183
+ */
171
184
  try {
172
- const rendered = trimUnitNumber(math, math.format(value, { notation: 'auto', precision }), precision, displayPrecision);
185
+ /**
186
+ * ## 复合单位必须**自己拼单位**:mathjs 的 toString 会丢它
187
+ *
188
+ * 实测 `1 L/minute to m^3/s`(一个 `to` 到复合单位的值):
189
+ *
190
+ * ```
191
+ * math.format(v, {notation:'auto', precision}) → "1.6666666666666…" ← 没有单位
192
+ * String(v) → "1.6666666666666…" ← 也没有
193
+ * v.formatUnits() → "m^3 / s" ← 单位在这里
194
+ * v.value → 0.000016666… ← 基准值
195
+ * ```
196
+ *
197
+ * 根因:`Unit#toString` 会先 `simplify()`,而简化对 `m^3 / s` 这类
198
+ * **多单位复合**处理不了,它就只输出数值那一半。
199
+ * 单单位(`kW`、`cm`)不受影响,所以只有复合单位丢 ——
200
+ * 这也解释了为什么 `1 MJ/day to kW` 一直是对的。
201
+ *
202
+ * 后果很严重:`1.67e-5` 少了 `m^3 / s` 之后看着像「1.67」,
203
+ * 而真实值是 0.0000167,**差 10 万倍**,数字本身没有任何异常。
204
+ *
205
+ * 所以这里显式拆成「数值 + formatUnits()」两段自己拼。
206
+ */
207
+ const unitText = typeof value.formatUnits === 'function' ? value.formatUnits() : '';
208
+ const numericText = math.format(value, { notation: 'auto', precision });
209
+ /**
210
+ * ★ 只在**确实缺单位**时才补。
211
+ *
212
+ * `math.format` 的行为不一致:单单位(`kW`、`cm`)本来就会带上,
213
+ * 多单位复合(`m^3 / s`)才丢。一律追加会得到 `200 cm cm`。
214
+ *
215
+ * 判据用「结果里有没有出现单位的首个符号」—— 够简单,且不会误判:
216
+ * 数值部分只有数字、指数、符号,不会包含单位名。
217
+ */
218
+ const unitHead = unitText.split(/[\s/]/)[0];
219
+ const hasUnit = unitHead !== '' && numericText.includes(unitHead);
220
+ const raw = unitText === '' || hasUnit ? numericText : numericText + ' ' + unitText;
221
+ const rendered = trimUnitNumber(math, raw, precision, displayPrecision);
173
222
  if (rendered !== '')
174
223
  return rendered;
224
+ return raw;
175
225
  }
176
226
  catch {
177
- /* 拆不开就退回原格式 */
227
+ /* 拼不出来:交给下面的兜底 */
178
228
  }
179
229
  }
180
230
  }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * 公式库第十批:**生物与宏观经济**的运行式子。
3
+ *
4
+ * ## 为什么这两块一起补
5
+ *
6
+ * 生物类原有 26 条,但偏向**医学剂量**(体重给药、输液速率、血液酒精);
7
+ * 经济类原有 26 条,几乎全是**金融**(复利、年金、NPV、CAGR)。
8
+ *
9
+ * 而写世界观真正会问的是另两类问题:
10
+ *
11
+ * ```
12
+ * 生物:这片草原能养多少头牛?这窝猪多久出栏?这个生态能支撑多少顶级捕食者?
13
+ * 经济:这个国家一年产多少?劳动力有多少?投资能带来多少增长?钱发多了会怎样?
14
+ * ```
15
+ *
16
+ * 这一批补的就是这些**运行层面**的式子,而不是又一批财务指标。
17
+ *
18
+ * ## 收录判据
19
+ *
20
+ * 同既有几批:闭合式、带量纲、有出处、不重复。经验系数标出取值范围。
21
+ *
22
+ * @module dsh-math-calculator/formula-library-bioecon
23
+ */
24
+ const p = (value, desc) => ({ value, desc });
25
+ const req = (desc) => ({ value: null, desc });
26
+ /** 生物与宏观 11 条:生态承载、畜禽养殖、人口劳动力、货币与通胀。 */
27
+ export const BIOECON_FORMULAS = [
28
+ // ══════════════════════════════════════════════════════════════════
29
+ // A. 生态与畜牧(土地能养多少、多久出栏、能撑几级食物链)
30
+ // ══════════════════════════════════════════════════════════════════
31
+ {
32
+ id: 'grazing_capacity',
33
+ cat: 'biology/ecology',
34
+ name: '草地载畜量',
35
+ expr: 'area * yield_per_ha * util / (intake * days)',
36
+ params: {
37
+ area: req('草场面积 ha(公顷,1 ha = 1e4 m²)'),
38
+ yield_per_ha: req('单位面积产草量 kg/(ha·年)(温带草原 2000~5000,荒漠 200~500)'),
39
+ util: p(0.5, '可利用率(放牧通常 0.4~0.6,留一半给草场恢复)'),
40
+ intake: req('每头每日采食量 kg/头·日(牛 40~60,羊 5~8)'),
41
+ days: p(365, '放牧天数(全年 365,季节性牧场按实际)'),
42
+ },
43
+ unit: '头(可承载标准畜单位)',
44
+ desc: '载畜量 = 面积 × 产草量 × 利用率 /(采食量 × 天数)。**「留一半」不是保守,是必需**:草地超载后会退化,第二年产量直接腰斩。这条是游牧与畜牧设定的地基。',
45
+ keys: ['载畜量', '草地', '放牧', 'grazing', '畜牧', '草原'],
46
+ frequency: 'high',
47
+ layer: 'phenomenological',
48
+ domains: ['biology', 'perception'],
49
+ },
50
+ {
51
+ id: 'livestock_growth_time',
52
+ cat: 'biology/ecology',
53
+ name: '畜禽出栏时间(按日增重)',
54
+ expr: '(target_weight - birth_weight) / adg',
55
+ params: {
56
+ target_weight: req('出栏体重 kg'),
57
+ birth_weight: req('初生体重 kg(仔猪 1.5,犊牛 40,羔羊 3)'),
58
+ adg: req('平均日增重 kg/日(肉猪 0.7~1.0,肉牛 0.8~1.2,肉鸡 0.05)'),
59
+ },
60
+ unit: '日',
61
+ desc: '出栏天数 = (出栏重 − 初生重)/日增重。**猪约 180 天、牛约 450 天、肉鸡约 42 天**——这个比值决定了肉价与牧场的周转率。日增重随饲料与品种变,写设定时要说清是哪种。',
62
+ keys: ['出栏', '日增重', 'livestock', '养殖', '肉'],
63
+ frequency: 'high',
64
+ layer: 'phenomenological',
65
+ domains: ['biology', 'perception'],
66
+ },
67
+ {
68
+ id: 'feed_conversion_ratio',
69
+ cat: 'biology/ecology',
70
+ name: '饲料转化率(料肉比)',
71
+ expr: 'feed_consumed / weight_gain',
72
+ params: {
73
+ feed_consumed: req('消耗饲料总量 kg(干物质)'),
74
+ weight_gain: req('增重 kg'),
75
+ },
76
+ unit: '无量纲(kg 料/kg 肉)',
77
+ desc: '料肉比 = 耗料/增重。**牛肉 6~8、猪肉 2.5~3.5、肉鸡 1.6~2.0**——这个数字直接决定「养一头牛要多少亩地」。牛高是因为反刍动物维持消耗大,不是品种差。',
78
+ keys: ['料肉比', '饲料转化', 'feed conversion', 'FCR'],
79
+ frequency: 'high',
80
+ layer: 'phenomenological',
81
+ domains: ['biology', 'perception'],
82
+ },
83
+ {
84
+ id: 'crop_yield_npp',
85
+ cat: 'biology/ecology',
86
+ name: '农田可收获能量(由 NPP 与收获指数)',
87
+ expr: 'NPP * area * hi * 1e4',
88
+ params: {
89
+ NPP: req('净初级生产力 g/(m²·年)(温带农田 800~1500,热带 2000+)'),
90
+ area: req('面积 ha'),
91
+ hi: p(0.4, '收获指数(谷物 0.4~0.5,块根作物 0.6,牧草 0.9)'),
92
+ },
93
+ unit: 'g(干物质)',
94
+ desc: '可收获干物质 = NPP × 面积 × 收获指数。收获指数是「长出来的东西里有多少是我们要吃的部分」——谷物的 0.4 意味着**六成生物量是秸秆**,那正是牲畜饲料与燃料的来源。',
95
+ keys: ['NPP', '收获指数', '产量', '作物', 'yield'],
96
+ frequency: 'high',
97
+ layer: 'phenomenological',
98
+ domains: ['biology', 'perception'],
99
+ },
100
+ {
101
+ id: 'trophic_chain_support',
102
+ cat: 'biology/ecology',
103
+ name: '营养级可支撑的生物量(十分之一定律)',
104
+ expr: 'primary * eff^level',
105
+ params: {
106
+ primary: req('初级生产者生物量 kg'),
107
+ eff: p(0.1, '营养级传递效率(陆地 0.1,海洋 0.1~0.15,以肉食为主时更低)'),
108
+ level: req('营养级层数(草食 1,一级肉食 2,顶级 3~4)'),
109
+ },
110
+ unit: 'kg',
111
+ desc: '每升一级只剩约 10%。**这就是为什么食物链一般不超过 5 级,也是为什么顶级捕食者稀少**:要养活 1 kg 狼,需要 10 kg 兔、100 kg 草。写生态设定时这条能立刻否掉「满地猛兽」的方案。',
112
+ keys: ['营养级', '食物链', '十分之一', 'trophic', '捕食者'],
113
+ frequency: 'high',
114
+ layer: 'derived',
115
+ domains: ['biology'],
116
+ },
117
+ // ══════════════════════════════════════════════════════════════════
118
+ // B. 人口、劳动力与产出(宏观经济的运行层)
119
+ // ══════════════════════════════════════════════════════════════════
120
+ {
121
+ id: 'labor_force',
122
+ cat: 'econ/macro',
123
+ name: '劳动力人口(由总人口与年龄结构)',
124
+ expr: 'population * participation',
125
+ params: {
126
+ population: req('总人口'),
127
+ participation: p(0.5, '劳动参与率(农业社会 0.45~0.55,工业化 0.55~0.65)'),
128
+ },
129
+ unit: '人',
130
+ desc: '劳力 = 总人口 × 参与率。**农业社会的 0.45 里含着大量童工与老人**——这个比值决定了「能征多少兵」与「能修多大工程」的上限。',
131
+ keys: ['劳动力', '人口', 'labor', '劳力'],
132
+ frequency: 'high',
133
+ layer: 'conventional',
134
+ domains: ['economics', 'society'],
135
+ },
136
+ {
137
+ id: 'gdp_from_labor',
138
+ cat: 'econ/macro',
139
+ name: '总产出(人力 × 人均产出)',
140
+ expr: 'labor * output_per_worker',
141
+ params: {
142
+ labor: req('劳动力人数'),
143
+ output_per_worker: req('人均年产出(按货币或实物统一定义)'),
144
+ },
145
+ unit: '与人均产出同单位(通常为年产值)',
146
+ desc: '产出 = 劳力 × 人均产出。最简单的宏观核算模型:**想提高总产出只有两条路——加人或提高单产**。人均产值翻倍比人口翻倍难得多,所以长期增长几乎全靠后者。',
147
+ keys: ['总产出', 'GDP', '宏观', '产值'],
148
+ frequency: 'high',
149
+ layer: 'conventional',
150
+ domains: ['economics'],
151
+ },
152
+ {
153
+ id: 'macro_savings_rate',
154
+ cat: 'econ/macro',
155
+ name: '储蓄率与投资能力',
156
+ expr: 'output * savings_rate',
157
+ params: {
158
+ output: req('总产出'),
159
+ savings_rate: p(0.2, '储蓄率(农业社会 0.05~0.15,高速增长期 0.25~0.40)'),
160
+ },
161
+ unit: '与总产出同单位',
162
+ desc: '可投资额 = 产出 × 储蓄率。**这是增长的燃料**:高速增长的经济体储蓄率往往很高(东亚 0.3+),而贫困农业社会几乎全被消费掉,攒不出投资。',
163
+ keys: ['储蓄', '投资', 'savings', '资本积累'],
164
+ frequency: 'high',
165
+ layer: 'conventional',
166
+ domains: ['economics'],
167
+ },
168
+ {
169
+ id: 'capital_output_ratio',
170
+ cat: 'econ/macro',
171
+ name: '资本产出比(增长的资本需求)',
172
+ expr: 'savings_rate / growth_rate',
173
+ params: {
174
+ savings_rate: req('储蓄率'),
175
+ growth_rate: req('目标增长率(小数)'),
176
+ },
177
+ unit: '无量纲(资本/产出)',
178
+ desc: '哈罗德-多马式的推导:所需资本产出比 = 储蓄率 / 增长率。**想每年增长 5% 而储蓄率只有 10%,需要资本产出比 2(即每 1 元产出要 2 元资本)**——如果实际要 4,那 5% 就达不到。这条能立刻判断一个增长目标现不现实。',
179
+ keys: ['资本产出比', '哈罗德', '增长', '投入产出'],
180
+ frequency: 'medium',
181
+ layer: 'derived',
182
+ domains: ['economics'],
183
+ },
184
+ {
185
+ id: 'money_supply_from_base',
186
+ cat: 'econ/macro',
187
+ name: '货币供应量(基础货币 × 货币乘数)',
188
+ expr: 'base_money * multiplier',
189
+ params: {
190
+ base_money: req('基础货币(流通现金 + 准备金)'),
191
+ multiplier: p(5, '货币乘数(现代银行 8~12,准备金率高时 2~4,金属货币制接近 1)'),
192
+ },
193
+ unit: '与基础货币同单位',
194
+ desc: '广义货币 = 基础货币 × 乘数。**金属货币制的乘数接近 1,现代部分准备金制能放大 10 倍**——这解释了为什么古代通货膨胀罕见而现代可以剧烈。架空设定里「银行体系发达程度」直接改这个乘数。',
195
+ keys: ['货币供应', '乘数', 'money supply', '通胀', '银行'],
196
+ frequency: 'high',
197
+ layer: 'derived',
198
+ domains: ['economics'],
199
+ },
200
+ {
201
+ id: 'inflation_from_money_growth',
202
+ cat: 'econ/macro',
203
+ name: '货币增长与产出增长之差(通胀压力)',
204
+ expr: '(money_growth - output_growth) * 100',
205
+ params: {
206
+ money_growth: req('货币供应年增长率(小数,如 0.15)'),
207
+ output_growth: req('实际产出年增长率(小数,如 0.03)'),
208
+ },
209
+ unit: '%(年通胀率近似)',
210
+ desc: '通胀 ≈ 货币增速 − 产出增速。**这是最实用的通胀判据**:钱印得比东西增长快多少,物价就涨多少。0.15 对 0.03 意味着约 12% 的年通胀——三年物价翻倍。写「滥发货币导致崩溃」时用这条量化。',
211
+ keys: ['通胀', '货币增长', 'inflation', '物价'],
212
+ frequency: 'high',
213
+ layer: 'phenomenological',
214
+ domains: ['economics'],
215
+ },
216
+ ];