dsh-math-calculator 2.3.4 → 2.3.5

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/src/formulas.ts CHANGED
@@ -29,6 +29,7 @@ import { ENGINEERING_FORMULAS } from './formula-library-engineering.js'
29
29
  import { BIOECON_FORMULAS } from './formula-library-bioecon.js'
30
30
  import { MACRO_MATERIAL_FORMULAS } from './formula-library-macro-materials.js'
31
31
  import { BIO_GENERAL_FORMULAS } from './formula-library-biol-general.js'
32
+ import { PROCESS_FORMULAS } from './formula-library-process.js'
32
33
  import { getActiveConstants } from './constants-graph.js'
33
34
  import { loadGlobalFormulas } from './formula-pack.js'
34
35
  import { findUnit, convert } from './units.js'
@@ -52,6 +53,7 @@ export const FORMULAS: FormulaDef[] = [
52
53
  ...BIOECON_FORMULAS,
53
54
  ...MACRO_MATERIAL_FORMULAS,
54
55
  ...BIO_GENERAL_FORMULAS,
56
+ ...PROCESS_FORMULAS,
55
57
  ]
56
58
 
57
59
  /** id 索引(后批次覆盖同名 id 时以后者为准,便于后续修订)。 */
@@ -80,6 +82,7 @@ for (const formula of ENGINEERING_FORMULAS) FORMULA_INDEX.set(formula.id, formul
80
82
  for (const formula of BIOECON_FORMULAS) FORMULA_INDEX.set(formula.id, formula)
81
83
  for (const formula of MACRO_MATERIAL_FORMULAS) FORMULA_INDEX.set(formula.id, formula)
82
84
  for (const formula of BIO_GENERAL_FORMULAS) FORMULA_INDEX.set(formula.id, formula)
85
+ for (const formula of PROCESS_FORMULAS) FORMULA_INDEX.set(formula.id, formula)
83
86
 
84
87
  /**
85
88
  * 把全局公式包并入内置公式(幂等:已存在的 id 跳过)。
package/src/guards.ts CHANGED
@@ -85,7 +85,34 @@ export function magnitudeOfLabel(label: string): { word: string; power: number }
85
85
  */
86
86
  let body = text
87
87
  const bracketPair = body.match(/[((\[【]\s*(.+?)\s*[))\]】]$/)
88
- if (bracketPair) body = bracketPair[1].trim()
88
+ /**
89
+ * ## 括号前还有描述文字时,括号内是**输入条件**,不是结果单位
90
+ *
91
+ * 实测反馈(2026-11-08 第二份):
92
+ *
93
+ * ```
94
+ * label:"煤储量/年耗 可支撑年数(2万吨)" expr:"20000/1.364" → 14662.75
95
+ * → ⚠ label 含「万」,而结果 14662 本身已达 10⁴;若按字面读成「万」则为 1.47e8
96
+ * ```
97
+ *
98
+ * 而作者的真实意思是「**2 万吨煤**能支撑多少年」—— 括号里是那个煤量的口径说明,
99
+ * 不是「结果单位是万吨」。这条警告纯属噪声,而且会把真正的量纲问题盖过去。
100
+ *
101
+ * 判据:成对括号**在末尾**,但括号**前面还有非空白文字**,且那段文字里有
102
+ * 中文描述(不是纯数字/符号),则括号内是条件描述,直接放过。
103
+ *
104
+ * 保留的正当情形:`年发电量(亿)` —— 括号前是「年发电量」这个**量名**,
105
+ * 括号里是它的单位。所以还要排除「括号前那段是纯量名(不含数字、不含斜杠)」…
106
+ * 太细了,实测两类写法都出现,所以这里取一个更稳的折衷:
107
+ * **括号内含数字 + 量级词 + 单位词**(如 `2万吨`、`300万件`)→ 判为条件;
108
+ * **括号内只有量级词 + 单位词**(如 `亿`、`万吨`)→ 仍按结果单位处理。
109
+ */
110
+ if (bracketPair) {
111
+ const inner = bracketPair[1].trim()
112
+ const hasQuantity = /^[0-9][0-9,.]*\s*/.test(inner)
113
+ if (!hasQuantity) body = inner
114
+ else return null
115
+ }
89
116
 
90
117
  /**
91
118
  * ## 判据:量级词必须处在「单位位置」
package/src/help.ts CHANGED
@@ -624,6 +624,79 @@ const D_ECOLOGY_BODY = (): string => `【生物·生态(载畜量/料肉比/NP
624
624
  这解释了为什么食物链一般不超过 5 级,也立刻否掉「满地猛兽」
625
625
  `
626
626
 
627
+ /**
628
+ * 【工艺链】能源→材料→传动→农业。
629
+ *
630
+ * 外部反馈的核心判断:库里有 246 条物理规律,但缺「往下接一层到工艺参数」的桥。
631
+ * 这一节就是那座桥 —— 每条都给「什么场景该用」,而不只是式子。
632
+ */
633
+ const D_PROCESS_BODY = (): string => `【工艺链】把物理规律接到工艺参数上(14 条,engineering/process)
634
+
635
+ 调用方式:{operation:"formula", formula:"id", overrides:{参数}}
636
+ 参数可带单位,会校验量纲并换算。
637
+
638
+ ════ 能源链:窑炉 → 烧成 → 围护 ════
639
+ kiln_heat_balance Q = m·c·ΔT·(1+损比)
640
+ ★ 全库第一缺口:有了它「单窑耗煤 8kg」才能验算
641
+ 100kg 坯 20→1100℃ 本身只需 97MJ,实际耗煤对应 8~15 倍,
642
+ 差的那部分是窑体蓄热+窑壁散热+烟气带走
643
+ ★ 省燃料的关键在损比:间歇窑 12、连续窑/余热回收 3~6
644
+ kiln_firing_time t = ΔT / 升温速率(K/h)
645
+ ★ 升温速率被热应力卡住,不是工艺保守:
646
+ 薄壁 150~300 K/h、厚壁 60~120 —— 壁厚翻倍,工期翻倍
647
+ wall_heat_loss Q = A·ΔT/R + 通风
648
+ ★ 用总热阻 R(多层墙要累加 δ/k),不是导热系数 k
649
+ 64㎡、R=9.34、ΔT=25℃ → 171W;实测常比计算大(缝隙与热桥)
650
+ ★ 反推用途:有实测耗热 282W → 反推真实 R 值,检验保温够不够
651
+
652
+ ════ 材料链:矿石 → 金属 → 硬化 → 强度 ════
653
+ metallurgy_yield m = m_矿 × 品位 × 回收率
654
+ ★ 一眼看出「年产 5kg」与「15~20kg」差在哪
655
+ 反推最实用:两炉年产 5kg → 每炉投 5/(2×0.35×0.7)≈10kg 矿
656
+ 三因子(品位/回收率/矿量)各自可调,外推时先看哪个先到顶
657
+ case_depth x ≈ 2√(D·t),D = D₀·exp(−Q/RT)
658
+ ★ **温度是平方级杠杆、时间是线性**:
659
+ 900→950℃ 层深涨 √2 倍,而 3h→5h 只涨 1.29 倍
660
+ 所以「渗层不够多烧一会儿」效率低,提温才是真手段
661
+ gear_bending_strength σ = F_t/(b·m·Y)
662
+ ★ 能验算「模数 4 比 2.5 厚 60% 足以承受 30kg 重锤」
663
+ 实算:m4/z30 → d=120mm,30kg@r60 → F_t≈294N,σ≈7.7MPa(铸铁许用 30~60)
664
+ ⚠ 简化式,未含动载/应力集中/齿面接触疲劳;手工低速齿轮够用
665
+ gear_geometry d = m·z
666
+ ★ 齿轮一切尺寸都从模数与齿数派生(外径 d+2m、中心距 (d₁+d₂)/2)
667
+ 传动比 i = z_从/z_主,同一模数下可自由组合齿数
668
+
669
+ ════ 传动/储能/水力 ════
670
+ pendulum_clock_drift Δt/天 = 86400·(−½·ΔL/L + ½·Δg/g)
671
+ ★ 钢摆冬夏差 20℃ → 每天慢约 10.4s(这就是要重新校的原因)
672
+ 周期只与 √L 有关,所以摆长误差的影响被压掉一半
673
+ ★ 异星设定直接可用:g 变多少,钟就快慢多少
674
+ gravity_storage_discharge t = m·g·h / P
675
+ ★ 用途是**检查设定自不自洽**,不是算新数:
676
+ 25kg×2.5m=625J,若说「8W 撑 12 分钟」→ 实为 78 秒,差一个数量级
677
+ ★ 反推:要 8W×12min 需 5760J → 235kg×2.5m 或 25kg×23.5m
678
+ water_turbine_output P = ρgQHη
679
+ ★ 与泵同形但反向:低水头大流量 → 转速慢扭矩大,能直驱锻锤
680
+ ★ 效率 0.4→0.85 就是「木轮改铁轮+导流」的全部收益,翻 2 倍
681
+
682
+ ════ 农业与化工 ════
683
+ gdd_accumulated GDD = 超出基点的日均温 × 天数
684
+ ★ 四季节奏的数学骨架:把时间换算成热量
685
+ 无霜期 180~210 天 → 反推能攒多少积温,对照作物需求判断能不能熟
686
+ (玉米 2500~2800、小麦 1800~2200、水稻 3000+)
687
+ leaching_residue C_n = C₀·rⁿ(r 为每次换水的残留比)
688
+ ★ **换水次数比每次泡多久更重要**:
689
+ r=0.6 换 4 次剩 13%、6 次剩 4.7%;而泡 4 天不换几乎不衰减
690
+ fermentation_time t = −ln(S_残/S₀)/k
691
+ ★ 温度低 → k 小 → 时间成倍延长(k=0.07→43天,k=0.035→86天)
692
+ 与 q10_temperature 串联:由温度得 k,再得工期
693
+ syrup_concentration V_f = V₀·Brix₀/Brix_f
694
+ ★ **得率只由糖度比决定**,与锅大小、火大小无关
695
+ 所以「熬多久」只影响风味,不影响得率
696
+
697
+ ★ 共同点:这些式子大多**形式上简单,但接住了此前断开的链**。
698
+ 「窑温、配比、精度、工期、产量」这些散落的经验数,有了它们才能互相验算。`;
699
+
627
700
  /** 【宏观·经济(劳力/产出/储蓄/货币/通胀)】按公式 id 调用,参数可带单位。 */
628
701
  const D_MACRO_BODY = (): string => `【宏观·经济(劳力/产出/储蓄/货币/通胀)】
629
702
 
@@ -1095,6 +1168,16 @@ export const HELP_TOPICS: HelpTopic[] = [
1095
1168
  aliases: ['宏观', '劳力', '储蓄率', '资本产出比', '货币供应', '通胀', '经济'],
1096
1169
  body: D_MACRO_BODY,
1097
1170
  },
1171
+ {
1172
+ key: 'd-process',
1173
+ tier: 'core',
1174
+ weight: 18,
1175
+ short: '工艺链',
1176
+ title: '工艺链(窑炉/冶金/齿轮/发酵·14 条)',
1177
+ summary: '把物理规律接到工艺参数:窑炉热平衡、烧成时间、围护热损、冶金收得率、渗碳层深、齿轮强度、摆钟漂移、重力储能、水轮、积温、浸提、发酵、浓缩。',
1178
+ aliases: ['工艺', '窑炉', '烧成', '冶金', '渗碳', '齿轮', '发酵', '酿造', '糖浆', '窑', '工程工艺', 'process'],
1179
+ body: D_PROCESS_BODY,
1180
+ },
1098
1181
  {
1099
1182
  key: 'd-material',
1100
1183
  tier: 'core',
@@ -1385,7 +1468,7 @@ function renderIndex(): string {
1385
1468
  * 拆开后它们仍然属于「建模」,因为它们都是公式(而不是算术)。
1386
1469
  */
1387
1470
  const MODELING_KEYS = ['ode', 'formula', 'derivation', 'recipe', 'recipe-errors', 'recipe-fiscal', 'recipe-bio', 'recipe-material', 'world',
1388
- 'd-struct', 'd-thermal', 'd-fluid', 'd-ecology', 'd-macro', 'd-material', 'd-bio', 'd-fiscal', 'd-gdp']
1471
+ 'd-struct', 'd-thermal', 'd-fluid', 'd-ecology', 'd-macro', 'd-material', 'd-bio', 'd-fiscal', 'd-gdp', 'd-process']
1389
1472
  const isModeling = (topic: HelpTopic) => MODELING_KEYS.includes(topic.key)
1390
1473
  const arithmetic = core.filter((topic) => !isModeling(topic))
1391
1474
  const modeling = core.filter(isModeling)
package/src/index.ts CHANGED
@@ -3810,7 +3810,7 @@ export function runMathTool(rawArguments: unknown): MathResult {
3810
3810
  if (hintNote) result.notice = hintNote
3811
3811
 
3812
3812
  /** 调低计算精度导致结果被截 —— 那种「看起来正常、末位是假的」情况。 */
3813
- const truncNote = checkPrecisionTruncation(requestedPrecision, precision, result.result)
3813
+ const truncNote = checkPrecisionTruncation(requestedPrecision, precision, result.result, input)
3814
3814
  if (truncNote) {
3815
3815
  result.notice = result.notice ? result.notice + '\n' + truncNote : truncNote
3816
3816
  }
@@ -4602,6 +4602,7 @@ function checkPrecisionTruncation(
4602
4602
  requested: number | undefined,
4603
4603
  effective: number,
4604
4604
  text: string,
4605
+ source: string,
4605
4606
  ): string {
4606
4607
  /**
4607
4608
  * 判据:调用方显式给的精度**低于默认 64** 才算「调低了」,
@@ -4611,15 +4612,62 @@ function checkPrecisionTruncation(
4611
4612
  * (effective 就是 clamp 过的 requested),于是这条永远不触发。
4612
4613
  */
4613
4614
  if (requested === undefined || requested >= DEFAULT_PRECISION) return ''
4614
- /** 只看纯数字结果:带单位的另有提示通道。 */
4615
+ /**
4616
+ * ## 两个判据,缺一不可(本轮补的是第二个)
4617
+ *
4618
+ * ① **结果装不下**:整数位数 > precision,末几位被截。
4619
+ * ② **表达式本身超出精度**:即使结果看起来很小或干脆是 0。
4620
+ *
4621
+ * ★ ② 是本轮实测抓到的漏洞(外部反馈文档 2.3):
4622
+ *
4623
+ * ```
4624
+ * (10^20+1)-(10^20), precision=20 → 0 ok=true, notice 空
4625
+ * ```
4626
+ *
4627
+ * 结果被截成了 **0** —— 而 0 只有 1 位,判据① 立刻放行。
4628
+ * 于是「两个 20 位数相减、结果被算成 0」这种最危险的失真,
4629
+ * 恰恰因为结果太小而绕过了检查。
4630
+ *
4631
+ * 这类「结果越离谱、越容易逃过检查」的结构必须堵掉,
4632
+ * 所以改成**看输入而不只看输出**:表达式里出现 10^n(n 超过 precision)
4633
+ * 或长整数串时,无论结果多小都提示。
4634
+ */
4615
4635
  const numeric = text.trim().match(/^-?([0-9]+)(?:\.([0-9]+))?$/)
4616
- if (!numeric) return ''
4617
- const integerDigits = numeric[1].replace(/^0+(?=.)/, '').length
4618
- if (integerDigits <= effective) return ''
4619
- return '本次结果有 ' + integerDigits + ' 位有效数字,而 precision=' + effective +
4620
- ' 只能精确表示 ' + effective + ' 位——末 ' + (integerDigits - effective) +
4621
- ' 位可能已失真。需要精确值时请用 precision:64(默认),' +
4622
- '只想让输出短些请用 displayPrecision。'
4636
+ const integerDigits = numeric ? numeric[1].replace(/^0+(?=.)/, '').length : 0
4637
+ const resultOverflows = numeric !== null && integerDigits > effective
4638
+ /**
4639
+ * 表达式里是否有超出 precision 的大数。
4640
+ *
4641
+ * 两种形态:
4642
+ * · 幂字面量 `10^20`、`1e20`
4643
+ * · 长整数串(连续 16 位以上数字)
4644
+ *
4645
+ * 阈值用 effective(而不是固定值):precision=20 时 `10^20` 是边界,
4646
+ * `10^21` 才必然丢位;而 precision=8 时 `10^9` 就丢。
4647
+ */
4648
+ /**
4649
+ * 临界点是 `power >= effective`,不是 `>`。
4650
+ *
4651
+ * 因为 `10^20` 是「1 后跟 20 个零」= **21 位**数字,
4652
+ * precision=20 只能表示 20 位有效数字,装不下它。
4653
+ * 所以 precision=20 时 `10^20` 已在边界上,`10^20+1` 里的 +1 必然被吃掉。
4654
+ *
4655
+ * 第一版写成 `>` 时,`(10^20+1)-(10^20)` 恰好卡在边界外,漏掉了。
4656
+ */
4657
+ const powerMatch = source.match(/10\s*\^\s*(\d+)/)
4658
+ const powerBeyond = powerMatch ? Number(powerMatch[1]) >= effective : false
4659
+ /** 科学记数法字面量(`1e20`)同样算大数。 */
4660
+ const expMatch = source.match(/[0-9][0-9.]*[eE]\s*(\d+)/)
4661
+ const expBeyond = expMatch ? Number(expMatch[1]) >= effective : false
4662
+ const longDigits = /(^|[^0-9.eE+-])[0-9]{16,}(?![0-9])/.test(source)
4663
+ const sourceOverflows = powerBeyond || expBeyond || longDigits
4664
+ if (!resultOverflows && !sourceOverflows) return ''
4665
+ const reason = resultOverflows
4666
+ ? '本次结果有 ' + integerDigits + ' 位有效数字,precision=' + effective +
4667
+ ' 只能精确表示 ' + effective + ' 位——末 ' + (integerDigits - effective) + ' 位可能已失真。'
4668
+ : '表达式里出现了超出 precision=' + effective + ' 能表示的大数,结果可能已失真' +
4669
+ (numeric !== null ? '(本次结果 ' + text.trim() + ')' : '') + '。'
4670
+ return reason + '需要精确值时请用 precision:64(默认),只想让输出短些请用 displayPrecision。'
4623
4671
  }
4624
4672
 
4625
4673
  /**