testfloat_expr 教程
本教程介绍如何针对 bin_float 运行 Berkeley TestFloat 向量。TestFloat 向量文件由 testfloat_gen 针对某一个函数(如 f64_mulAdd)在某一种舍入模式下生成;每一行以十六进制给出操作数、预期结果和预期异常标志。你用 TestFloatSpec 描述函数,解析各行,执行它们,并读取摘要。命令行运行器是 testfloat_expr_cli。
快速入门
将该包添加到 moon.pkg:
import {
"Luna-Flow/floating/frontend/testfloat_expr",
}
在 binary64 中检验 :
///|
test "quick start" {
let spec = @testfloat_expr.TestFloatSpec::parse("f64_mul", "rnear_even").unwrap()
let vectors =
#|3FF8000000000000 4000000000000000 4008000000000000 00
#|
let document = @testfloat_expr.parse_testfloat("mul.tv", vectors, spec).unwrap()
let summary = @testfloat_expr.execute_document(document)
inspect(summary.passed_cases(), content="1")
inspect(summary.success(), content="true")
}
最后一个字段是 SoftFloat 标志掩码:01 不精确,02 下溢,04 上溢,08 除以零(无穷),10 无效。
日常任务
值和标志都会被检查
编码结果或标志掩码只要有一个不同,该向量即失败:
///|
test "flag mismatch" {
let spec = @testfloat_expr.TestFloatSpec::parse("f16_add", "rnear_even").unwrap()
// 1 + 2^-11 is a tie in binary16 and rounds to 1, inexact
let vectors =
#|3C00 1000 3C00 01
#|3C00 1000 3C00 00
#|
let summary = @testfloat_expr.execute_document(
@testfloat_expr.parse_testfloat("add.tv", vectors, spec).unwrap(),
)
let messages = summary.results().map(r => r.id() + " " + r.message())
inspect(
messages.join("; "),
content="f16_add:1 ; f16_add:2 flags mismatch: expected 0, actual 1",
)
}
id 由函数名和行号组成。
NaN 结果
当预期结果为 NaN 时,任何静默 NaN 都可接受,因为 IEEE 754 并未规定生成的 NaN 的载荷或符号。但标志仍须匹配:
///|
test "nan results" {
let spec = @testfloat_expr.TestFloatSpec::parse("f32_mul", "rnear_even").unwrap()
// inf * 0 is invalid; SoftFloat's default NaN is FFC00000
let vectors =
#|7F800000 00000000 FFC00000 10
#|
let summary = @testfloat_expr.execute_document(
@testfloat_expr.parse_testfloat("nan.tv", vectors, spec).unwrap(),
)
inspect(summary.passed_cases(), content="1")
}
到整数的转换与 exact 变体
对于 to_i32、to_i64、to_ui32 和 to_ui64,结果字段是十六进制的整数。使用 exact=true(对应 TestFloat 的 -exact)时,不精确的转换还会引发不精确标志。无效转换只通过其标志检查:
///|
test "integer conversions" {
let plain = @testfloat_expr.TestFloatSpec::parse("f64_to_i32", "rnear_even").unwrap()
let exact = @testfloat_expr.TestFloatSpec::parse(
"f64_to_i32",
"rnear_even",
exact=true,
).unwrap()
// 1.5 -> 2; NaN -> invalid (the value field is SoftFloat's sentinel)
let plain_rows =
#|3FF8000000000000 00000002 00
#|7FF8000000000000 7FFFFFFF 10
#|
let exact_rows =
#|3FF8000000000000 00000002 01
#|
let a = @testfloat_expr.execute_document(
@testfloat_expr.parse_testfloat("plain.tv", plain_rows, plain).unwrap(),
)
let b = @testfloat_expr.execute_document(
@testfloat_expr.parse_testfloat("exact.tv", exact_rows, exact).unwrap(),
)
inspect(a.passed_cases(), content="2")
inspect(b.passed_cases(), content="1")
}
比较
比较函数预期结果为 0 或 1。发信号的谓词(eq_signaling、le、lt)对任何 NaN 操作数都引发无效;静默谓词(eq、le_quiet、lt_quiet)只对信号 NaN 引发:
///|
test "comparisons" {
let le = @testfloat_expr.TestFloatSpec::parse("f64_le", "rnear_even").unwrap()
let le_quiet = @testfloat_expr.TestFloatSpec::parse("f64_le_quiet", "rnear_even").unwrap()
let row =
#|7FF8000000000000 3FF0000000000000 0 10
#|
let quiet_row =
#|7FF8000000000000 3FF0000000000000 0 00
#|
let signaling = @testfloat_expr.execute_document(
@testfloat_expr.parse_testfloat("le.tv", row, le).unwrap(),
)
let quiet = @testfloat_expr.execute_document(
@testfloat_expr.parse_testfloat("le_quiet.tv", quiet_row, le_quiet).unwrap(),
)
inspect(signaling.passed_cases(), content="1")
inspect(quiet.passed_cases(), content="1")
}
分片
RunOptions 从索引 i 开始,每隔 n 个运行一个向量,因此 n 个进程可以共享同一个文件:
///|
test "shards" {
let spec = @testfloat_expr.TestFloatSpec::parse("f16_add", "rnear_even").unwrap()
let vectors =
#|3C00 3C00 4000 00
#|0001 0001 0002 00
#|7C00 FC00 7E00 10
#|8000 0000 0000 00
#|3C00 BC00 0000 00
#|
let document = @testfloat_expr.parse_testfloat("add.tv", vectors, spec).unwrap()
let second = @testfloat_expr.execute_document(
document,
options=@testfloat_expr.RunOptions::new(shard_count=2, shard_index=1),
)
inspect(second.total_cases(), content="5")
inspect(second.selected_cases(), content="2")
inspect(second.passed_cases(), content="2")
}
深入了解
- 生成向量。
just conformance fetch binary安装固定版本的 SoftFloat 和 TestFloat 源码,just conformance run binary --level 1生成并执行完整矩阵(所有格式、运算、舍入模式以及两种微小性模式);参见验证。 - 微小性。 向
TestFloatSpec::parse传入tininess="before",以匹配用-tininessbefore生成的向量;默认是舍入后判定。 - 函数名。 spec 接受 TestFloat 名称:
f16_、f32_、f64_或f128_后接add、sub、mul、div、sqrt、mulAdd、rem、roundToInt、to_i32、to_i64、to_ui32、to_ui64、eq、le、lt、eq_signaling、le_quiet或lt_quiet。
常见陷阱
- 每个文档只对应一个函数。 spec 作用于每一行;请拆分混合文件。
- 舍入参数不可省略。
rem和比较运算会忽略它,但TestFloatSpec::parse仍需要一个有效名称,例如rnear_even。 - 不支持
rodd。 只接受 IEEE 的五种方向。 exact很重要。 来自-exact运行的向量中,roundToInt和整数转换带有不精确标志;请用exact=true解析它们。
后续步骤
- testfloat_expr API:每个条目。
- testfloat_expr 设计:精确的通过规则。
- bin_float 符合性:已发布的 TestFloat 声明。