decimal_gda_checked 教程
本教程展示如何把一次通用十进制算术(GDA)计算作为遵守 GDA 陷阱的流水线来运行:你选择一个带有应用所需陷阱的 GdaContext,开始一个 GdaDecimalChecked,串联运算,找出触发陷阱的步骤,检查粘滞状态,并显式决定是否继续。这正是 Python 的 decimal 模块和 GDA 测试套件所采用的模型:状态标志是粘滞的,已启用的陷阱会终止计算。运算来自 decimal_gda;设计页面证明了状态与陷阱的定律;API 参考列出了所有方法。
快速入门
moon add Luna-Flow/floating@0.8.0
import {
"Luna-Flow/floating/decimal_gda",
"Luna-Flow/floating/decimal_gda_checked",
}
在会捕获除以零的 GDA basic 上下文下做除法:
///|
test "quick start: a trapped division" {
let ctx = @decimal_gda.GdaContext::default()
let r = @decimal_gda_checked.GdaDecimalChecked::parse("1", ctx).divide(
@decimal_gda.Decimal::zero(),
)
inspect(r.is_trapped(), content="true")
inspect(
r.trapped_signal() == Some(@decimal_gda.GdaSignal::DivisionByZero),
content="true",
)
}
日常任务
示例使用如下辅助函数列出标志:
///|
fn gda_flags(f : @decimal_gda.GdaFlags) -> String {
let named = [
("inexact", f.inexact),
("rounded", f.rounded),
("invalid_operation", f.invalid_operation),
("division_by_zero", f.division_by_zero),
("overflow", f.overflow),
("conversion_syntax", f.conversion_syntax),
("invalid_context", f.invalid_context),
]
[ for p in named if p.1 => p.0 ].join(",")
}
选择陷阱
GdaContext 携带一个陷阱集合。各预定义上下文有所不同:GdaContext::default()(GDA basic 上下文:精度 9、HalfUp)捕获 DivisionByZero、InvalidOperation、Overflow、Underflow 和 Clamped;decimal32()、decimal64()、decimal128() 和 new(...) 不捕获任何信号。用 trap 添加或移除陷阱:
///|
test "trap inexact results" {
let exact_only = @decimal_gda.GdaContext::decimal64().trap(
@decimal_gda.GdaSignal::Inexact,
)
let ok = @decimal_gda_checked.GdaDecimalChecked::parse("10", exact_only).divide(
@decimal_gda.Decimal::from_int(4),
)
inspect(ok.is_trapped(), content="false")
inspect(ok.value().to_string(), content="2.5")
let stopped = @decimal_gda_checked.GdaDecimalChecked::parse("10", exact_only).divide(
@decimal_gda.Decimal::from_int(3),
)
inspect(stopped.is_trapped(), content="true")
inspect(
stopped.trapped_signal() == Some(@decimal_gda.GdaSignal::Inexact),
content="true",
)
}
读取粘滞状态
raised() 保存最近一次运算的信号,status() 保存自上下文创建以来的所有信号:
///|
test "status is sticky" {
let ctx = @decimal_gda.GdaContext::new(precision=5)
let r = @decimal_gda_checked.GdaDecimalChecked::parse("1.234567", ctx)
.add(@decimal_gda.Decimal::one())
.multiply(@decimal_gda.Decimal::from_int(2))
inspect(r.value().to_string(), content="4.4692")
inspect(gda_flags(r.raised()), content="")
inspect(gda_flags(r.status()), content="inexact,rounded")
}
陷阱之后不再执行任何运算
一旦被陷阱捕获,后续每个运算都原样返回流水线;值是被捕获步骤的确定结果:
///|
test "short circuit after a trap" {
let ctx = @decimal_gda.GdaContext::default()
let r = @decimal_gda_checked.GdaDecimalChecked::parse("1", ctx)
.divide(@decimal_gda.Decimal::zero())
.add(@decimal_gda.Decimal::one())
.multiply(@decimal_gda.Decimal::from_int(5))
inspect(r.is_trapped(), content="true")
inspect(r.value().to_string(), content="inf")
}
有意识地恢复
resume_defined() 接受确定结果并继续执行。状态仍然记录着被捕获的信号,因此这一决定事后可见:
///|
test "resume with the defined result" {
let ctx = @decimal_gda.GdaContext::default()
let trapped = @decimal_gda_checked.GdaDecimalChecked::parse("1", ctx).divide(
@decimal_gda.Decimal::zero(),
)
let resumed = trapped.resume_defined().minus()
inspect(resumed.is_trapped(), content="false")
inspect(resumed.value().to_string(), content="-inf")
inspect(gda_flags(resumed.status()), content="division_by_zero")
}
恢复之后陷阱仍然启用:再次除以零会再次触发陷阱。
深入了解
语法错误属于无效运算
GDA 把转换语法错误、不可能的除法、未定义的除法和无效上下文归类为无效运算情形。因此,捕获 InvalidOperation 的上下文会在遇到格式错误的字符串时停止,并且状态中除具体情形外还会加入 invalid_operation:
///|
test "a malformed literal" {
let r = @decimal_gda_checked.GdaDecimalChecked::parse(
"12,5",
@decimal_gda.GdaContext::default(),
)
inspect(r.is_trapped(), content="true")
inspect(
r.trapped_signal() == Some(@decimal_gda.GdaSignal::InvalidOperation),
content="true",
)
inspect(gda_flags(r.status()), content="invalid_operation,conversion_syntax")
}
数学函数需要有界上下文
exp、ln、log10 和 power 遵循 GDA 的限制,精度和指数界须在 以内。交换格式上下文满足这一条件;采用默认无界范围的 GdaContext::new 则不满足,这些函数会返回带 invalid_context 的 NaN(在 InvalidOperation 下会触发陷阱):
///|
test "exp needs a bounded context" {
let good = @decimal_gda_checked.GdaDecimalChecked::parse(
"2",
@decimal_gda.GdaContext::decimal64(),
).exp()
inspect(good.value().to_string(), content="7.389056098930650")
let bad = @decimal_gda_checked.GdaDecimalChecked::parse(
"2",
@decimal_gda.GdaContext::new(precision=16),
).exp()
inspect(bad.value().to_string(), content="nan")
inspect(gda_flags(bad.raised()), content="invalid_context")
}
与普通 GDA 函数结合使用
任何 decimal_gda 运算都返回一个 GdaOutcome。当流水线没有对应的方法时,在 value() 和 context() 上执行该运算,再包装其结果:
///|
test "use an operation without a pipeline method" {
let ctx = @decimal_gda.GdaContext::decimal64()
let start = @decimal_gda_checked.GdaDecimalChecked::parse("7.5", ctx)
let rounded = @decimal_gda_checked.GdaDecimalChecked::from_outcome(
@decimal_gda.to_integral_value(start.value(), start.context()),
)
inspect(rounded.value().to_string(), content="8")
}
只在未被陷阱捕获的流水线上这样做;from_outcome 不检查先前的状态。
常见陷阱
default()会捕获陷阱,new()不会。 请审慎选择上下文。raised()与status()。 前者是最近一步,后者是粘滞的历史。resume_defined保留状态和陷阱。 它只清除陷阱标记和raised()。如有需要,请在上下文上用GdaContext::clear_status清除状态,并开始一条新的流水线。- 没有错误。 陷阱不会被转换为
ArithmeticError;请检查is_trapped()。 - 两个十进制包。
decimal_gda.Decimal不是decimal.Decimal;不带陷阱的 IEEE 风格标志累积见decimal_checked。
后续步骤
decimal_gda_checked设计:作为幺半群的粘滞状态、陷阱谓词以及短路的证明。decimal_gda_checkedAPI:所有方法。decimal_gda教程:上下文、舍入模式以及完整的 GDA 运算集合。