dsh-math-calculator 2.0.0 → 2.1.0
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 +73 -0
- package/README.md +52 -10
- package/dsh.plugin.json +15 -8
- package/lib/awareness.js +16 -13
- package/lib/body-catalog.js +488 -0
- package/lib/body-scaling.js +288 -0
- package/lib/body-taxonomy.js +719 -0
- package/lib/detector.js +361 -16
- package/lib/empirical-decomposition.js +321 -0
- package/lib/format.js +113 -5
- package/lib/formula-library-generic.js +226 -0
- package/lib/formulas.js +71 -13
- package/lib/index.js +613 -77
- package/lib/ops/bodies-op.js +368 -0
- package/lib/ops/shared.js +64 -4
- package/lib/ops/workspace-op.js +19 -1
- package/lib/ops/world-op.js +22 -0
- package/lib/playbook.js +6 -1
- package/lib/precision-report.js +80 -0
- package/lib/result-cache.js +144 -0
- package/lib/types/awareness.d.ts +10 -4
- package/lib/types/body-catalog.d.ts +156 -0
- package/lib/types/body-scaling.d.ts +123 -0
- package/lib/types/body-taxonomy.d.ts +95 -0
- package/lib/types/detector.d.ts +18 -0
- package/lib/types/empirical-decomposition.d.ts +107 -0
- package/lib/types/format.d.ts +33 -4
- package/lib/types/formula-library-generic.d.ts +37 -0
- package/lib/types/formulas.d.ts +35 -7
- package/lib/types/index.d.ts +16 -0
- package/lib/types/ops/bodies-op.d.ts +35 -0
- package/lib/types/ops/shared.d.ts +94 -3
- package/lib/types/precision-report.d.ts +59 -0
- package/lib/types/result-cache.d.ts +78 -0
- package/lib/types/workspace.d.ts +24 -0
- package/lib/types/world.d.ts +8 -0
- package/lib/workspace.js +22 -0
- package/lib/world.js +38 -3
- package/package.json +1 -1
- package/src/awareness.ts +16 -13
- package/src/body-catalog.ts +619 -0
- package/src/body-scaling.ts +357 -0
- package/src/body-taxonomy.ts +806 -0
- package/src/detector.ts +403 -15
- package/src/empirical-decomposition.ts +411 -0
- package/src/format.ts +123 -5
- package/src/formula-library-generic.ts +242 -0
- package/src/formulas.ts +79 -13
- package/src/index.ts +642 -64
- package/src/ops/bodies-op.ts +415 -0
- package/src/ops/shared.ts +141 -4
- package/src/ops/workspace-op.ts +21 -1
- package/src/ops/world-op.ts +22 -0
- package/src/playbook.ts +6 -1
- package/src/precision-report.ts +87 -0
- package/src/result-cache.ts +157 -0
- package/src/workspace.ts +34 -0
- package/src/world.ts +51 -4
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 经验公式的底层原理拆解:区分「物理内核」与「纯标定」,并明确标注哪些只在现实宇宙成立。
|
|
3
|
+
*
|
|
4
|
+
* ## 对上一版的自纠(2026-10-26 修订)
|
|
5
|
+
*
|
|
6
|
+
* 上一版把 `hull_speed` 归为 `scalable`(可泛化),理由是
|
|
7
|
+
* 「1.34 = Fr·√g 组合,换重力就能重算」。**这个判断过头了。**
|
|
8
|
+
*
|
|
9
|
+
* 真实的物理内核只有 Froude 数本身:
|
|
10
|
+
*
|
|
11
|
+
* v = Fr · √(g·L) ← 这是**量纲分析**的结论,跨世界成立
|
|
12
|
+
* Fr ≈ 0.4 ← 这是**经验值**,来自地球船舶的观测
|
|
13
|
+
*
|
|
14
|
+
* 而 Fr ≈ 0.4 是怎么来的?它是「兴波阻力急剧上升」的临界点,
|
|
15
|
+
* 本质上由**自由液面重力波的色散关系**决定:
|
|
16
|
+
*
|
|
17
|
+
* 波速 c = √(g·λ/(2π)) ← 深水重力波,**含 1/(2π)**
|
|
18
|
+
* 兴波极限 ≈ 船速追上自己的船首波 → 波长 ≈ 船长 L
|
|
19
|
+
* 代入得 v_limit ≈ √(g·L/(2π)) ≈ 0.4·√(g·L)
|
|
20
|
+
*
|
|
21
|
+
* 所以 Fr ≈ 0.4 ≈ 1/√(2π) **不是纯粹的经验拟合,而是色散关系的推论**。
|
|
22
|
+
* 这一点上一版说错了:它不是「地球船舶的经验常数」,而是物理推出来的。
|
|
23
|
+
*
|
|
24
|
+
* ## 但 Fr 的推导仍依赖「水」这个介质
|
|
25
|
+
*
|
|
26
|
+
* 1/(2π) 来自重力的**色散关系**(ω² = g·k),本身与介质无关;
|
|
27
|
+
* 但 `Fr ≈ 0.4` 的**数值**还隐含:
|
|
28
|
+
* · 介质是**液体**(有自由表面)
|
|
29
|
+
* · 表面张力可忽略(内河船长尺度下成立)
|
|
30
|
+
* · 兴波阻力主导(黏性阻力次要)
|
|
31
|
+
*
|
|
32
|
+
* 换一个**液体介质**(液氨、液态甲烷、岩浆),色散关系中的表面张力与黏性会变,
|
|
33
|
+
* 临界 Fr 会移动。所以:
|
|
34
|
+
* · **量纲形式** Fr·√(g·L) → 跨世界成立
|
|
35
|
+
* · **数值 0.4** → 只在「类水液体 + 宏观尺度」域成立
|
|
36
|
+
*
|
|
37
|
+
* ## 结论:三级标注,而不是两级
|
|
38
|
+
*
|
|
39
|
+
* | 级别 | 含义 | 例 |
|
|
40
|
+
* |---|---|---|
|
|
41
|
+
* | `dimensionless` | 量纲/标度形式跨世界成立,数值也无需改 | 克莱伯指数 0.75 |
|
|
42
|
+
* | `scalable-with-caveat` | **形式**可泛化,但**系数**需重新标定或验证 | Froude 0.4、潮汐系数 |
|
|
43
|
+
* | `earth-only` | 形式本身就是地球拟合,换世界无意义 | Mifflin 代谢、风寒、Du Bois |
|
|
44
|
+
*
|
|
45
|
+
* 上一版把 `hull_speed` 放在 `dimensionless` 那一侧,等于宣称
|
|
46
|
+
* 「0.4 换世界也精确成立」——那是**过度自信**。现在它被降级到 middle 档。
|
|
47
|
+
*
|
|
48
|
+
* @module dsh-math-calculator/empirical-decomposition
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* 经验公式拆解表。
|
|
52
|
+
*
|
|
53
|
+
* 排序:从「最可靠」到「最只能在现实中用」。
|
|
54
|
+
*/
|
|
55
|
+
export const EMPIRICAL_NOTES = [
|
|
56
|
+
// ══════════════════════════════════════════════════════════════
|
|
57
|
+
// 一级:无量纲通用(形式与数值都跨世界成立)
|
|
58
|
+
// ══════════════════════════════════════════════════════════════
|
|
59
|
+
{
|
|
60
|
+
id: 'kleiber',
|
|
61
|
+
name: '克莱伯定律(代谢率)',
|
|
62
|
+
expr: '70 * M^0.75',
|
|
63
|
+
kind: 'dimensionless',
|
|
64
|
+
principle: '代谢率 ∝ 体重^0.75 来自**分形输运网络**的几何:' +
|
|
65
|
+
'从主动脉到毛细血管的分支网络要填满三维体积,' +
|
|
66
|
+
'最优层级数使「供血能力 ∝ M^(3/4)」。这是**几何与输运的推论**,' +
|
|
67
|
+
'与化学反应的具体细节无关,所以跨物种、跨星球都该成立。',
|
|
68
|
+
bakedIn: '系数 70 是地球哺乳类的平均标定,会因生物化学与体温变。',
|
|
69
|
+
genericForm: 'BMR = k × M^0.75,**指数 0.75 可直接沿用**',
|
|
70
|
+
retuneNeeded: '系数 k 需要按该世界生物的代谢效率重新标定(不是 70)。',
|
|
71
|
+
needsFrom: ['该世界的代谢标定 k'],
|
|
72
|
+
breaksWhen: '若生命不是碳基、或输运不靠分形网络(单细胞尺度扩散主导),指数会偏离 0.75。' +
|
|
73
|
+
'但对「类地球的多细胞生命」,0.75 是稳健的。',
|
|
74
|
+
advice: '指数直接用 0.75;系数 k 可改。少数真正跨世界安全的经验式。',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'allometric_dose_scale',
|
|
78
|
+
name: '跨物种剂量换算',
|
|
79
|
+
expr: 'D_human * (W_human / W_animal)^0.75',
|
|
80
|
+
kind: 'dimensionless',
|
|
81
|
+
principle: '与克莱伯同源:药物代谢速率 ∝ 代谢率 ∝ M^0.75。',
|
|
82
|
+
bakedIn: '0.75 与代谢标度同源,无量纲。',
|
|
83
|
+
genericForm: 'D ∝ W^0.75',
|
|
84
|
+
retuneNeeded: '不同药物的代谢路径(肝/肾)可能不服从 0.75(体表面积标度是 0.67)。',
|
|
85
|
+
needsFrom: [],
|
|
86
|
+
breaksWhen: '药物吸收/排泄机制不同,指数会变。跨世界时「同样的药物」也未必存在。',
|
|
87
|
+
advice: '量级估算可用;具体药物要实测。',
|
|
88
|
+
},
|
|
89
|
+
// ══════════════════════════════════════════════════════════════
|
|
90
|
+
// 二级:形式可泛化,但系数需重新验证
|
|
91
|
+
// ══════════════════════════════════════════════════════════════
|
|
92
|
+
{
|
|
93
|
+
id: 'hull_speed',
|
|
94
|
+
name: '船舶理论航速',
|
|
95
|
+
expr: '1.34 * sqrt(L)',
|
|
96
|
+
kind: 'scalable-with-caveat',
|
|
97
|
+
principle: '**这条式子有真实物理内核**:排水型船的极限航速由兴波阻力决定。' +
|
|
98
|
+
'当船速接近自身船首波的传播速度时,兴波阻力急剧上升。' +
|
|
99
|
+
'深水重力波的相速度 c = √(g·λ/(2π));取波长 λ ≈ 船长 L,' +
|
|
100
|
+
'得 v_limit ≈ √(g·L/(2π)) ≈ 0.4·√(g·L)。' +
|
|
101
|
+
'所以 Froude 数 0.4 ≈ 1/√(2π) —— **它是色散关系的推论,不只是拟合**。',
|
|
102
|
+
bakedIn: '1.34 由三部分合成:Fr≈0.4、g=9.80665、以及「米/秒 → 英尺/秒 → 节」的换算。' +
|
|
103
|
+
'具体是 0.4 · √9.80665 · 3.28084/1.68781 ≈ 1.341。' +
|
|
104
|
+
'**注意 L 的单位是英尺**(原式按 ft 定义),而换算里带着地球重力。',
|
|
105
|
+
genericForm: 'v(kn) = Fr × √(g × L_m) × 1.944,**形式跨世界成立**',
|
|
106
|
+
retuneNeeded: 'Fr ≈ 0.4 的**数值**还隐含「类水液体 + 表面张力可忽略 + 兴波阻力主导」。' +
|
|
107
|
+
'换介质(液氨、液态甲烷、岩浆)时,色散关系里的表面张力与黏性占比变化,' +
|
|
108
|
+
'临界 Fr 会移动 —— 需要按该液体的表面张力与船长重新验证。',
|
|
109
|
+
needsFrom: ['地表重力 g', '水线长 L(米)', '液体介质的表面张力(若与水量级不同)'],
|
|
110
|
+
breaksWhen: '① 液体表面张力远大于水(小船长、低重力时毛细波主导);' +
|
|
111
|
+
'② 黏性主导(高黏度液体,如岩浆);③ 非自由表面(冰下海洋)。' +
|
|
112
|
+
'这些情况下兴波不再是主导阻力,Fr≈0.4 失效。',
|
|
113
|
+
advice: '写「这个星球上的船能开多快」用 hull_speed_generic(Fr·√(g·L),米制)。' +
|
|
114
|
+
'Fr=0.4 在类水液体里可直接用;若介质不是水,按上面三条判断重标。',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'habitable_zone',
|
|
118
|
+
name: '宜居带内边界(简化)',
|
|
119
|
+
expr: 'sqrt(L / 1.1)',
|
|
120
|
+
kind: 'scalable-with-caveat',
|
|
121
|
+
principle: '**内核是辐射平衡**:行星温度由「接收的辐照」与「向外辐射」平衡决定,' +
|
|
122
|
+
'T ∝ (S·(1−A))^0.25。所谓「宜居带」就是让 T 落在液态水区间的距离范围。' +
|
|
123
|
+
'辐照 ∝ L/r²,所以固定 T 时 r ∝ √L —— **这才是 sqrt(L) 的来源**。',
|
|
124
|
+
bakedIn: '1.1 是「内边界处的辐照是地球的 1.1 倍」,这个值来自 **Kopparapu 2013 的' +
|
|
125
|
+
'「潮湿温室极限」**:CO₂/H₂O 大气下,辐照超过某个阈值就会进入失控温室。' +
|
|
126
|
+
'它是**气候模型**的结果,不是纯物理常数。',
|
|
127
|
+
genericForm: 'innerAU = √(L / fluxRatio),fluxRatio = 内边界辐照 / 地球辐照。' +
|
|
128
|
+
'**形式(√L 标度)跨世界成立**,这由平方反比律决定。',
|
|
129
|
+
retuneNeeded: 'fluxRatio ≈ 1.1 依赖大气成分:强温室(CO₂ 厚)行星的阈值可低到 0.5—0.8;' +
|
|
130
|
+
'真空岩石行星要高得多。**换大气就得重算这个数**,通常需要跑气候模型。',
|
|
131
|
+
needsFrom: ['恒星光度 L', '该行星的大气成分与温室强度'],
|
|
132
|
+
breaksWhen: '大气与地球差异大时 1.1 失效。金星是现成的反例:辐照只有地球 1.9 倍,' +
|
|
133
|
+
'却因失控温室达到 737 K —— 用 1.1 判它会误以为「勉强宜居」。',
|
|
134
|
+
advice: '光子量级下 √(L/1.1) 够用。真要判「能不能有液态水」,' +
|
|
135
|
+
'用 world 算实际表面温度再和沸点/冰点比,别只信这个边界。',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'hardness_to_yield',
|
|
139
|
+
name: '硬度估屈服强度',
|
|
140
|
+
expr: '3 * H * 9.80665e6',
|
|
141
|
+
kind: 'scalable-with-caveat',
|
|
142
|
+
principle: '**物理内核是位错运动**:硬度测的是「压入时塑性变形抗力」,' +
|
|
143
|
+
'屈服强度也是同一机制(位错滑移),所以两者成正比。' +
|
|
144
|
+
'比例 σy ≈ 3·HB 是材料学的经验,但背后机制清楚。',
|
|
145
|
+
bakedIn: '9.80665e6 = 9.80665 × 1e6,是「kgf/mm² → Pa」的换算,**含地球重力**。' +
|
|
146
|
+
'而 σy ≈ 3·HB 这个比例是无量纲的。',
|
|
147
|
+
genericForm: 'σy(Pa) = 3 × H(kgf/mm²) × g × 1e6,g 用该世界重力',
|
|
148
|
+
retuneNeeded: '比例 3 本身对大多数金属成立(误差 ±30%),但陶瓷、聚合物偏离大。' +
|
|
149
|
+
'重力不同只影响**单位换算**,不影响 3 这个比例。',
|
|
150
|
+
needsFrom: ['该世界重力 g(仅用于单位换算)'],
|
|
151
|
+
breaksWhen: '脆性材料(陶瓷、玻璃)硬度与强度关系弱;纳米尺度下尺寸效应显著。',
|
|
152
|
+
advice: 'σy≈3Ha 跨世界大致成立(机制相同);换算里的 9.8 才是地球专属。',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'reference_evapotranspiration',
|
|
156
|
+
name: '参考蒸散量(简化)',
|
|
157
|
+
expr: '0.408 * delta * (Rn - G) + 0.9 * gamma * (900 / (T + 273)) * u2 * (es - ea)',
|
|
158
|
+
kind: 'scalable-with-caveat',
|
|
159
|
+
principle: '**内核是 Penman-Monteith 的能量平衡 + 质量输运**:' +
|
|
160
|
+
'蒸发需要「提供潜热」与「带走水汽」两件事同时满足。' +
|
|
161
|
+
'前者由辐射平衡决定(Δ 项),后者由空气的饱和差与湍流输运决定(γ 项)。',
|
|
162
|
+
bakedIn: '0.408 = 1/2.45(MJ/m² → mm/day 的潜热换算);' +
|
|
163
|
+
'900 是 Dalton 型输运系数的经验值;0.34 是风速修正系数。' +
|
|
164
|
+
'**900 隐含地球大气压**:蒸散 ∝ 1/P(P 越低,同样温差能带走更多水汽)。',
|
|
165
|
+
genericForm: 'ET0 = (0.408·Δ·(Rn−G) + γ·(900/(T+273))·u2·(es−ea)) / (Δ + γ(1+0.34u2))。' +
|
|
166
|
+
'形式可沿用,但 **γ = cp·P/(0.622·λ) 随大气压 P 线性变**。',
|
|
167
|
+
retuneNeeded: '900 那个系数在非 1 atm 下要按 P/101325 缩放;' +
|
|
168
|
+
'λ(汽化潜热)换溶剂要改;cp(比热)换大气成分要改。',
|
|
169
|
+
needsFrom: ['大气压 P', '比热 cp', '汽化潜热 λ', '地表反照率'],
|
|
170
|
+
breaksWhen: '气压偏离 1 atm 数倍时整式偏几倍;0.1 atm 星球上蒸散量会小很多。',
|
|
171
|
+
advice: '要算某星球的水循环,先把 P 代入 γ 再算;900 那个数是地球专属。',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'corrosion_rate_weightloss',
|
|
175
|
+
name: '腐蚀速率(失重法)',
|
|
176
|
+
expr: '87.6 * (weight_loss / (area * time * density))',
|
|
177
|
+
kind: 'scalable-with-caveat',
|
|
178
|
+
principle: '**内核是 Faraday 定律 + 质量守恒**:腐蚀速率由「单位面积通过的电荷」决定,' +
|
|
179
|
+
'失重法只是反过来从质量损失推速率。87.6 是单位换算的合并。',
|
|
180
|
+
bakedIn: '87.6 是 mm/年 与 g·cm⁻²·h⁻¹ 之间的换算系数,不含物理常数。',
|
|
181
|
+
genericForm: 'rate = ΔW/(A·t·ρ) × 8760 × 10(显式换算,不用 87.6 这个黑箱)',
|
|
182
|
+
retuneNeeded: '**换算系数可复用,但速率值本身是化学动力学问题**:' +
|
|
183
|
+
'依赖温度(Arrhenius)、氧气分压、pH、湿度。换世界这些全变。',
|
|
184
|
+
needsFrom: ['温度', '氧化剂分压', 'pH', '材料'],
|
|
185
|
+
breaksWhen: '大气成分不同(无氧、高硫、高氯)时腐蚀机制完全变(不再是氧化),不可套用。',
|
|
186
|
+
advice: '换算可复用;「这材料在这星球上腐蚀多快」是实验问题。',
|
|
187
|
+
},
|
|
188
|
+
// ══════════════════════════════════════════════════════════════
|
|
189
|
+
// 三级:只在现实(地球)成立
|
|
190
|
+
// ══════════════════════════════════════════════════════════════
|
|
191
|
+
{
|
|
192
|
+
id: 'bmr_mifflin',
|
|
193
|
+
name: '基础代谢率(Mifflin-St Jeor)',
|
|
194
|
+
expr: '10 * m + 6.25 * h - 5 * age + 5',
|
|
195
|
+
kind: 'earth-only',
|
|
196
|
+
principle: '**没有可拆的物理内核**。这是对**人类样本**的线性多元回归:' +
|
|
197
|
+
'系数 10 / 6.25 / −5 / 5 是为最小化预测误差拟合出来的,没有独立物理含义。' +
|
|
198
|
+
'连函数形式(线性而非幂律)都只是为了局部拟合方便。',
|
|
199
|
+
bakedIn: '全部四个系数都是人类样本的统计产物。',
|
|
200
|
+
retuneNeeded: '**不可泛化**。真实代谢是 M^0.75 的幂律(见克莱伯);' +
|
|
201
|
+
'线性只在人类体重范围内近似。换物种要整条重拟合,不是改系数。',
|
|
202
|
+
needsFrom: [],
|
|
203
|
+
breaksWhen: '换物种、换重力、换体温、换生化路径 —— 全部失效。',
|
|
204
|
+
advice: '只在「人类、地球重力、常温」下用。异星生物用 metabolic_generic' +
|
|
205
|
+
'(BMR = k·M^0.75),且认识到 k 也是要自己标的。',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'human_body_surface_area',
|
|
209
|
+
name: '人体表面积(Du Bois)',
|
|
210
|
+
expr: '0.007184 * m^0.425 * h^0.725',
|
|
211
|
+
kind: 'earth-only',
|
|
212
|
+
principle: '**几何拟合,不是物理定律**。0.425 / 0.725 是对人类体型' +
|
|
213
|
+
'(近似「圆柱躯干 + 四肢」)的几何拟合。' +
|
|
214
|
+
'它不像克莱伯那样有分形网络的必然性。',
|
|
215
|
+
bakedIn: '0.007184 与两个指数全部来自人类遗体测量。',
|
|
216
|
+
retuneNeeded: '换物种要按该物种的几何形状重算(球 4πr²、圆柱 2πrL、或重新拟合)。',
|
|
217
|
+
needsFrom: [],
|
|
218
|
+
breaksWhen: '任何非人类体型都失效。',
|
|
219
|
+
advice: '异星生物表面积按它的几何形状算。散热与给药都依赖这个值,别照抄人类系数。',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'wind_chill',
|
|
223
|
+
name: '风寒指数',
|
|
224
|
+
expr: '13.12 + 0.6215*T - 11.37*v^0.16 + 0.3965*T*v^0.16',
|
|
225
|
+
kind: 'earth-only',
|
|
226
|
+
principle: '**背后是对流换热的物理**(h ∝ v^0.8 量级),但这条**具体式子**是' +
|
|
227
|
+
'对「人体皮肤在空气中的散热」做的经验拟合:' +
|
|
228
|
+
'它的常数把人体表面积、皮肤温度、空气密度、对流系数全部烘焙进去了。',
|
|
229
|
+
bakedIn: '全部系数来自 2001 年加拿大环境部的人体实验;' +
|
|
230
|
+
'指数 0.16 是拟合产物(理论对流域是 0.8,这里因温度耦合被压平)。',
|
|
231
|
+
retuneNeeded: '**不可直接泛化**。但底层原理可以复用:' +
|
|
232
|
+
'h ∝ v^0.5 · ρ^0.8(见 convective_cooling_generic),' +
|
|
233
|
+
'再乘(皮肤温 − 气温)得散热功率,才是可换世界的写法。',
|
|
234
|
+
needsFrom: ['大气密度', '风速', '皮肤温度'],
|
|
235
|
+
breaksWhen: '换物种(体表/体温/毛发)、换大气密度、换气压 —— 全部失效。' +
|
|
236
|
+
'0.1 atm 的稀薄大气里「风寒」几乎不存在。',
|
|
237
|
+
advice: '这条只能用于人类 + 地球大气。异星用 convective_cooling_generic 自己推。',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: 'heat_index',
|
|
241
|
+
name: '酷热指数',
|
|
242
|
+
expr: '42.379 + 2.04901523*T + 10.14333127*RH - ...',
|
|
243
|
+
kind: 'earth-only',
|
|
244
|
+
principle: '**原理是「汗液蒸发散热受阻」**(高湿 → 蒸发慢 → 体感更热),' +
|
|
245
|
+
'但这条具体式子是 Rothfusz 对美国人热感觉的多项式回归,' +
|
|
246
|
+
'把人体生理与地球大气全烘焙进去了。',
|
|
247
|
+
bakedIn: 'Rothfusz 1979 回归的全部系数;原式只在 T>26°C、RH>40% 内有效。',
|
|
248
|
+
retuneNeeded: '不可泛化。底层原理(蒸发散热 ∝ 饱和差 × 风速)可复用。',
|
|
249
|
+
needsFrom: ['湿度', '风速', '大气压'],
|
|
250
|
+
breaksWhen: '换物种、换湿度范围、换气压都会偏。',
|
|
251
|
+
advice: '只对人类、地球大气。但「湿热比干热难受」的定性结论跨世界成立。',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'dew_point_magnus',
|
|
255
|
+
name: '露点温度(Magnus)',
|
|
256
|
+
expr: '243.04*(...)',
|
|
257
|
+
kind: 'earth-only',
|
|
258
|
+
principle: '**背后是 Clausius-Clapeyron 方程**(饱和蒸气压 ∝ exp(−L/RT))。' +
|
|
259
|
+
'Magnus 式只是它在水、0—50°C 范围内的**近似**(把指数展开成有理式)。' +
|
|
260
|
+
'243.04 与 17.625 是对**水**拟合的常数。',
|
|
261
|
+
bakedIn: '243.04 / 17.625 隐含水的汽化潜热随温度变化。',
|
|
262
|
+
retuneNeeded: '**换溶剂必须重标**。氨、甲烷、硫酸的汽化潜热完全不同:' +
|
|
263
|
+
'Magnus 式对它们是错的。用 evaporation_saturation_generic' +
|
|
264
|
+
'(Clausius-Clapeyron,改 latent 即可)才是可泛化的写法。',
|
|
265
|
+
needsFrom: ['溶剂的汽化潜热', '参考点'],
|
|
266
|
+
breaksWhen: '非水溶剂完全失效。但「露点 = 饱和温度」这个定义跨世界成立。',
|
|
267
|
+
advice: '水世界可用。非水溶剂(如土卫六的甲烷湖)用 evaporation_saturation_generic' +
|
|
268
|
+
'改 latent,别套 243.04。',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'max_heart_rate',
|
|
272
|
+
name: '最大心率(估算)',
|
|
273
|
+
expr: '220 - age',
|
|
274
|
+
kind: 'earth-only',
|
|
275
|
+
principle: '**没有物理内核**。220 是 1930 年代对人类的粗略观察' +
|
|
276
|
+
'(后来发现个体差异 ±10—15 bpm,误差极大)。',
|
|
277
|
+
bakedIn: '220 是人类样本的算术产物。',
|
|
278
|
+
retuneNeeded: '不可泛化。心率应随**代谢率标度**走(小鼠 600 bpm、大象 30 bpm,' +
|
|
279
|
+
'都符合 M^-0.25 量级),不是「220 减年龄」。',
|
|
280
|
+
needsFrom: [],
|
|
281
|
+
breaksWhen: '换物种即失效。',
|
|
282
|
+
advice: '只对人类粗略估算。异星生物心率按「代谢率 / 每搏输出量」推。',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'widmark_bac',
|
|
286
|
+
name: '血液酒精浓度(Widmark)',
|
|
287
|
+
expr: 'A / (r * W) - beta * t',
|
|
288
|
+
kind: 'earth-only',
|
|
289
|
+
principle: '**原理是「乙醇在体液中均匀分布 + 肝脏零级代谢」**,' +
|
|
290
|
+
'但 r(分布容积)与 beta(代谢速率)都是人类肝酶动力学的经验值。',
|
|
291
|
+
bakedIn: 'r 与 beta 全部来自人类数据。',
|
|
292
|
+
retuneNeeded: '换物种(不同肝酶)、换重力(体液分布不同)都失效。',
|
|
293
|
+
needsFrom: [],
|
|
294
|
+
breaksWhen: '非人类物种、非水基体液都失效。',
|
|
295
|
+
advice: '只对人类。异星生物对酒精的代谢可能完全不同(或根本不代谢)。',
|
|
296
|
+
},
|
|
297
|
+
];
|
|
298
|
+
/** 拆解级别的人话说明。 */
|
|
299
|
+
export const KIND_LABEL = {
|
|
300
|
+
dimensionless: '无量纲通用(形式与数值都跨世界成立)',
|
|
301
|
+
'scalable-with-caveat': '形式可泛化,但系数需重验',
|
|
302
|
+
'earth-only': '仅在现实(地球)成立',
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* 按级别分组。
|
|
306
|
+
*
|
|
307
|
+
* @param kind - 拆解级别
|
|
308
|
+
* @returns 该级别的条目
|
|
309
|
+
*/
|
|
310
|
+
export function notesByKind(kind) {
|
|
311
|
+
return EMPIRICAL_NOTES.filter(function (note) { return note.kind === kind; });
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 查一条公式的拆解说明。
|
|
315
|
+
*
|
|
316
|
+
* @param id - 公式 id
|
|
317
|
+
* @returns 说明;没收录返回 undefined
|
|
318
|
+
*/
|
|
319
|
+
export function empiricalNoteOf(id) {
|
|
320
|
+
return EMPIRICAL_NOTES.find(function (note) { return note.id === id; });
|
|
321
|
+
}
|
package/lib/format.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module dsh-math-calculator/format
|
|
9
9
|
*/
|
|
10
|
+
import { reportPrecisionLoss } from './precision-report.js';
|
|
10
11
|
/** 数字转字符串:非有限值不进入 JSON。 */
|
|
11
12
|
export function safeNumber(value) {
|
|
12
13
|
return Number.isFinite(value) ? value : String(value);
|
|
@@ -35,12 +36,41 @@ export function isIntegerLike(value) {
|
|
|
35
36
|
return false;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
+
* 格式化输出值:**整数完整,小数按「小数点后几位」截断**。
|
|
39
40
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
41
|
+
* ## 计算精度 vs 显示精度
|
|
42
|
+
*
|
|
43
|
+
* `precision` 是计算精度(BigNumber 的有效位数),拿它直接当显示位数有两个毛病:
|
|
44
|
+
* · 64 位全吐出来,一行里六十个数字,人和 AI 都读不下去
|
|
45
|
+
* · 真调低 precision 又会**算错大数** —— 实测 `(10^20+1)-(10^20)`
|
|
46
|
+
* 在 precision=20 时得 0(应为 1)
|
|
47
|
+
*
|
|
48
|
+
* 所以拆成两层:计算永远按 `precision`(默认 64)走,显示用 `displayPrecision`。
|
|
49
|
+
*
|
|
50
|
+
* ## displayPrecision 的语义是「小数点后几位」,不是「有效数字」
|
|
51
|
+
*
|
|
52
|
+
* 这个区别很重要,实测对比:
|
|
53
|
+
*
|
|
54
|
+
* 数值 有效数字=8(旧) 小数位=8(现在)
|
|
55
|
+
* 12345.678901234 12345.679 12345.67890123
|
|
56
|
+
* 1/7 0.14285714 0.14285714
|
|
57
|
+
* 2^0.5 1.4142136 1.41421356
|
|
58
|
+
*
|
|
59
|
+
* 说「保留 8 位」时想的是小数位 —— 而有效数字语义下,
|
|
60
|
+
* 数字越大得到的小数位越少,`12345.678901234` 只给 3 位小数,与直觉不符。
|
|
61
|
+
*
|
|
62
|
+
* ## 整数不做截断
|
|
63
|
+
*
|
|
64
|
+
* `10^20+5`、`50!` 这类整数照原样输出。把 `10^20+5` 写成 `1e20` 是**丢信息**,
|
|
65
|
+
* 不是简洁 —— 作者问「等于多少」,答案就该精确到个位。
|
|
66
|
+
*
|
|
67
|
+
* @param math - mathjs 实例
|
|
68
|
+
* @param value - 待格式化的值
|
|
69
|
+
* @param precision - 计算精度(BigNumber 有效位数)
|
|
70
|
+
* @param displayPrecision - **小数点后保留几位**;省略则输出完整精度
|
|
71
|
+
* @returns 显示串
|
|
42
72
|
*/
|
|
43
|
-
export function formatValue(math, value, precision) {
|
|
73
|
+
export function formatValue(math, value, precision, displayPrecision) {
|
|
44
74
|
if (value === undefined)
|
|
45
75
|
return 'undefined';
|
|
46
76
|
if (value === null)
|
|
@@ -56,10 +86,88 @@ export function formatValue(math, value, precision) {
|
|
|
56
86
|
const isBig = Boolean(value && value.isBigNumber);
|
|
57
87
|
const integer = isIntegerLike(value);
|
|
58
88
|
try {
|
|
89
|
+
// 整数:完整输出,不受显示精度影响
|
|
59
90
|
if (isBig && integer) {
|
|
60
91
|
return math.format(value, { notation: 'fixed' });
|
|
61
92
|
}
|
|
62
|
-
|
|
93
|
+
const numeric = isBig ? Number(value.toString()) : typeof value === 'number' ? value : null;
|
|
94
|
+
/**
|
|
95
|
+
* 没有显式显示精度:保持旧行为(完整精度)。
|
|
96
|
+
*
|
|
97
|
+
* 只是把「极长的小数」改用科学计数法,避免一屏六十位。
|
|
98
|
+
*/
|
|
99
|
+
if (displayPrecision === undefined || !Number.isFinite(displayPrecision)) {
|
|
100
|
+
return math.format(value, { notation: 'auto', precision });
|
|
101
|
+
}
|
|
102
|
+
// 有显式显示精度:按**小数位**保留
|
|
103
|
+
const decimals = Math.max(0, Math.min(64, Math.round(displayPrecision)));
|
|
104
|
+
/**
|
|
105
|
+
* 大精度(> 15 位小数)必须走 BigNumber 原值,不能先转 Number。
|
|
106
|
+
*
|
|
107
|
+
* 双精度只有约 15—17 位有效数字,`Number('0.333...'.repeat)` 之后
|
|
108
|
+
* 再 toFixed 会得到垃圾尾数。实测 `1/3` 在 precision=30 时应给 30 位,
|
|
109
|
+
* 转换后只能给 16 位。
|
|
110
|
+
*
|
|
111
|
+
* 这条路径直接让 mathjs 格式化原始 BigNumber。
|
|
112
|
+
*/
|
|
113
|
+
if (decimals > 15 && isBig) {
|
|
114
|
+
const formatted = math.format(value, {
|
|
115
|
+
notation: 'fixed',
|
|
116
|
+
precision: Math.min(precision, decimals + 8),
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* 去掉尾随零:`notation:'fixed'` 会补齐到位数,
|
|
120
|
+
* 于是 `1.23456789e-6` 在 16 位下变成 `0.000001234567890000000000`。
|
|
121
|
+
* 尾随零不携带信息,去掉后更易读。
|
|
122
|
+
*/
|
|
123
|
+
return formatted.includes('.')
|
|
124
|
+
? formatted.replace(/0+$/, '').replace(/\.$/, '')
|
|
125
|
+
: formatted;
|
|
126
|
+
}
|
|
127
|
+
if (numeric === null || !Number.isFinite(numeric)) {
|
|
128
|
+
return math.format(value, { notation: 'auto', precision });
|
|
129
|
+
}
|
|
130
|
+
if (Number.isInteger(numeric))
|
|
131
|
+
return String(numeric);
|
|
132
|
+
const abs = Math.abs(numeric);
|
|
133
|
+
// 量级过大时小数位无意义,改用科学计数法
|
|
134
|
+
if (abs >= 1e15)
|
|
135
|
+
return numeric.toExponential(decimals);
|
|
136
|
+
/**
|
|
137
|
+
* 极小值:**只要会被四舍五入成 0,就必须改用科学计数法**。
|
|
138
|
+
*
|
|
139
|
+
* 踩过:`3.3e-9` 在 8 位小数下 toFixed 得 "0.00000000",转回数字变成 `0`
|
|
140
|
+
* —— 那不是「不精确」,是**把非零值显示成零**,属于数据丢失。
|
|
141
|
+
* 判据用 `10^-decimals` 而不是更保守的阈值,正好卡在「会不会归零」上。
|
|
142
|
+
*/
|
|
143
|
+
if (abs !== 0 && abs < Math.pow(10, -decimals)) {
|
|
144
|
+
return numeric.toExponential(Math.max(decimals, 3));
|
|
145
|
+
}
|
|
146
|
+
// toFixed 会补尾零(1.5 → "1.50000000"),用**文本**去掉,不要转 Number 再转回
|
|
147
|
+
const fixed = numeric.toFixed(decimals);
|
|
148
|
+
const trimmed = fixed.includes('.')
|
|
149
|
+
? fixed.replace(/0+$/, '').replace(/\.$/, '')
|
|
150
|
+
: fixed;
|
|
151
|
+
/**
|
|
152
|
+
* 为什么要保留 fixed 文本、不用 `String(Number(fixed))`:
|
|
153
|
+
*
|
|
154
|
+
* `Number("0.00000081")` 再 `String()` 会得到 `"8.1e-7"` ——
|
|
155
|
+
* 数值没错,但**看起来只有两位有效数字**,读者会以为精度很低。
|
|
156
|
+
* 保留定点文本既忠实于位数设定,也不会让人误判精度。
|
|
157
|
+
*/
|
|
158
|
+
const normalizedText = trimmed === '-0' ? '0' : trimmed;
|
|
159
|
+
/**
|
|
160
|
+
* 顺手记录相对误差(供「精度不够就提醒」用)。
|
|
161
|
+
*
|
|
162
|
+
* 为什么要上报:截断是**悄悄**发生的。8 位小数对 `1/7` 毫无影响,
|
|
163
|
+
* 但对 `0.00000123456789` 只留下 3 位有效数字 —— 作者看到
|
|
164
|
+
* `0.00000123` 不会意识到相对误差已有 0.3%。
|
|
165
|
+
*/
|
|
166
|
+
const shown = Number(normalizedText);
|
|
167
|
+
if (abs > 0 && Number.isFinite(shown)) {
|
|
168
|
+
reportPrecisionLoss(Math.abs(shown - numeric) / abs);
|
|
169
|
+
}
|
|
170
|
+
return normalizedText;
|
|
63
171
|
}
|
|
64
172
|
catch {
|
|
65
173
|
try {
|