版本 :v1.0
状态 :i64 毫秒 + f64 计算已实现;i128 纳秒为计划扩展
核心结论:i64 毫秒保底,i128 纳秒扩展,f64 秒/毫秒/工程单位做临时计算。不设 i64 微秒层,不单独为 f64 建结构体。
一、核心结论
- 秒:国际基准,理论正统,但工程上太粗。
- 毫秒:工程默认,精度够用,范围富余,接口通用。
- 微秒:无独立价值,只是接口遗留,跳过。
- 纳秒:用 i128 承载,精度和范围同时拉满。
- 计算:f64 仅做临时计算,不建结构体计算,结果可转回 i64。
- 兼容:不支持 i128 的场景,用高低位 i64 对接。
text
f64 秒/分/时/天/年 → 计算层(时长、速率、统计、物理公式)
i64 毫秒 → 常规时间点(默认、存储、接口、EPC)
i128 纳秒 → 高精度扩展(微秒/纳秒、超长跨度)
高低位 i64 → 传输兼容(不支持 i128 的 JSON/SQLite/FFI)
二、为什么是毫秒
2.1 精度够用
- GUI/网页:秒太粗,人眼可感知延迟在几十毫秒,毫秒够用。
- EPC/业务:99% 场景事件间隔在毫秒级,毫秒覆盖。
- 微秒级需求:真实占比极小,不值得单设一层。
2.2 范围富余
i64 毫秒范围:
text
2^63 - 1 ≈ 9.22 × 10^18 ms
换算成年:
text
9.22 × 10^18 / (1000 × 60 × 60 × 24 × 365.25)
≈ 2.92 × 10^8 年
即 ±约 2.92 亿年 ,总跨度约 5.85 亿年。
结论:i64 毫秒范围远超实际需要,富余量充足。
2.3 整数可靠
- 无浮点漂移。
- 可比较、可排序、可序列化、可回放。
- 跨平台一致。
- 大量现成 API、数据库、协议以毫秒为单位。
三、为什么跳过 i64 微秒
- 精度不是质变:微秒只是毫秒和纳秒之间的过渡。
- 范围反而更紧:i64 微秒约 ±29.2 万年,比 i64 毫秒小四个数量级,溢出概率更高。
- 接口无优势:对方给毫秒/微秒/纳秒,转 i128 纳秒都是无损乘,微秒作为中间层不简化任何转换。
- 多一层多一份成本:转换、舍入、序列化、心智负担都会增加。
- 现实不需要两个 time 类型:默认毫秒,高精度直接上 i128。
所以:不引入 i64 微秒,保持 i64 毫秒为唯一常规时间单位。
四、为什么用 i128 纳秒做扩展
4.1 范围
i128 纳秒范围:
text
2^127 - 1 ≈ 1.7 × 10^38 ns
换算成年:
text
1.7 × 10^38 / (10^9 × 60 × 60 × 24 × 365.25)
≈ 5.4 × 10^21 年
远超宇宙年龄,范围几乎无限。
4.2 精度
纳秒精度,覆盖微秒、亚微秒、纳秒级应用。
4.3 对比 i64+i32 纳秒
| 方案 | 精度 | 范围 | 复杂度 |
|---|---|---|---|
| i64 毫秒 | 毫秒 | ±2.92 亿年 | 低 |
| i64 微秒 | 微秒 | ±29.2 万年 | 冗余 |
| i64+i32 纳秒 | 纳秒 | ±2920 亿年 | 需拆装 |
| i128 纳秒 | 纳秒 | 约 5.4×10^21 年 | 低,单类型 |
结论:i128 纳秒单类型搞定,覆盖更广,接口更单一。
五、分层模型
| 层 | 类型 | 是否结构体 | 状态 | 用途 |
|---|---|---|---|---|
| 常规时间点 | i64 毫秒 | 是 | 已实现 | 默认时间戳、存储、接口、EPC |
| 高精度扩展 | i128 纳秒 | 是 | 计划 | 微秒/纳秒应用、超长跨度 |
| 传输兼容 | 高低位 i64 | 否/按需 | 计划 | 不支持 i128 的 JSON/SQLite/FFI |
| 计算 | f64 秒/毫秒 | 否 | 已实现 | 时长、速率、统计、物理公式 |
| 工程计算单位 | f64 秒/分/时/天/年 | 否 | 部分实现 | 直接换算,不建结构体 |
规则:
- 默认用 i64 毫秒。
- 需要亚毫秒或超长跨度,走 i128 纳秒。
- 不支持 i128,拆高低位 i64。
- 物理/统计计算用 f64。
- 不设 i64 微秒。
六、整数与浮点的分工
- 时间点 :整数(i64 毫秒 / i128 纳秒)。
- 可比较、可排序、可序列化、无漂移。
- 时长、速率、统计、物理公式 :浮点(f64 秒/毫秒/工程单位)。
- 连续计算自然,但精度有限,不适合做权威时间点。
- f64 不单独设结构体:只在计算入口/出口转换,允许按需转回时间点,但须显式、受控。
原因:
- f64 只有 53 位有效位,大时间戳下 ULP 变大,精度退化。
- 浮点累加会漂移。
- 浮点相等性、排序、跨平台一致性都不可靠。
所以:整数做时间点,浮点做计算,且浮点不建类型;计算结果可按需转回整数时间点,但转换须显式、受控。
七、工程计算单位
工程计算中,时间通常以 秒、分、时、天、年 等为单位表达。这些单位不单独设置结构体 ,一律在计算时直接转换为 f64。
7.1 转换常量
text
1 秒 = 1 s
1 分 = 60 s
1 小时 = 3600 s
1 天 = 86400 s
1 年 = 365.2425 天(工程概数;具体日期须走日历库。)
7.2 年的特殊性
年不是固定时长,常见定义:
- 格里高利 400 年平均年 :365.2425 天 = 31,556,952 秒。
- 400 年 = 146,097 天,146,097 / 400 = 365.2425。
- 与公历 400 年周期绝对一致;与回归年误差约 26 秒/年,约 3300 年差 1 天。
- 工程概数默认用值。
- 儒略年 :365.25 天 = 31,557,600 秒。
- 二进制简单,天文/工程历史常用。
- 与公历差 0.0075 天/年,400 年累积差 3 天。
- 回归年:约 365.2422 天,天文实际,不用于固定换算。
- 日历年:365 或 366 天,随闰年变化,必须走日历库。
使用规则:
- 工程概数、按年规划:用 365.2425 天,误差有明确上界。
- 具体年份、具体日期:必须走日历库,不得用平均年直接乘。
- 同一系统内不得混用不同年定义。
7.3 实践建议
- 定义常量:
rust
const SECONDS_PER_MIN: f64 = 60.0;
const SECONDS_PER_HOUR: f64 = 3600.0;
const SECONDS_PER_DAY: f64 = 86400.0;
/// 格里高利 400 年平均年(工程概数)
/// 365.2425 天 × 86400 秒 = 31,556,952 秒
const SECONDS_PER_YEAR: f64 = 31_556_952.0;
- 注意 f64 精度:极大跨度或需要精确日历计算时,保持整数或使用专门日历库。
八、i64 毫秒 ↔ i128 纳秒转换规则
必须提前定死:
8.1 方向
text
i64 ms → i128 ns:ms as i128 * 1_000_000,无损
i128 ns → i64 ms:显式舍入(截断/四舍五入),处理溢出
8.2 溢出
- i128 ns 转 i64 ms 超出范围,报错或饱和,不静默截断。
8.3 序列化
- i64 ms 用整数。
- i128 无原生 JSON/SQLite 支持,转字符串或拆两个 i64。
8.4 使用边界
- 默认 i64 ms。
- 只有明确需要亚毫秒精度或超长跨度,才走 i128。
九、已实现:duration_float.rs
9.1 定位
- Duration 的浮点扩展,独立文件,不改动
Duration主结构。 - 内部始终 i64 毫秒 ,浮点只在入口和出口使用。
- 不新建结构体,只通过
impl Duration扩展。
9.2 接口
浮点构造(入口):
rust
Duration::from_secs_f64(secs: f64) -> Self
Duration::from_mins_f64(mins: f64) -> Self
Duration::from_hours_f64(hours: f64) -> Self
Duration::from_days_f64(days: f64) -> Self
浮点访问器(出口):
rust
Duration::as_millis_f64(&self) -> f64
Duration::as_secs_f64(&self) -> f64
Duration::as_mins_f64(&self) -> f64
Duration::as_hours_f64(&self) -> f64
Duration::as_days_f64(&self) -> f64
9.3 精度边界
| 范围 | 精度 |
|---|---|
| ≤ 2^53(约 9.01×10^15 ms) | i64 ↔ f64 往返完全精确 |
| > 2^53 | 最多丢失低 10 位,误差 < 1024 ms(约 1.024 秒) |
| 对应精确范围 | ±约 285 万年 |
结论:在 2^53 毫秒(约 285 万年)范围内往返零损失,远超 EPC 及一般工程需求。
9.4 可选后续
- 年相关接口:
from_years_f64/as_years_f64,常量建议SECONDS_PER_YEAR = 31_557_600.0(儒略年)。 - 溢出策略:
from_secs_f64目前是as i64直接截断,极大值在 Rust 中是饱和转换,不会 panic,但语义上可考虑显式处理。 - 往返测试已有,覆盖了 2^53 边界和极大值。
十、未来计划
- i128 纳秒:作为高精度扩展层,内部计算用 i128,对外默认仍输出 i64 毫秒。
- 高低位 i64 兼容:在不支持 i128 的 JSON/SQLite/FFI 中,将 i128 拆为两个 i64 传输,规则定死(高 64 位、低 64 位、字节序、符号处理)。
- 年接口:按需补充平均年常量,统一用儒略年常量。
- 不设 i64 微秒:保持两层时间点结构(i64 毫秒 + i128 纳秒)。
十一、一句话总结
秒是理论基准,毫秒是工程默认且富余量充足,i128 纳秒是扩展上限。整数做时间点;f64 只做临时计算,秒、分、时、天、年直接换算,不单独设结构体;微秒层跳过;不支持 i128 用高低位 i64 对接。
duration_float.rs已按此原则实现浮点入口/出口。
笔记完。