META ← 测试元信息:描述与分类
2026/9/18 23:33:43 网站建设 项目流程

META ← 测试元信息:描述与分类

【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc

description=Negative float literal type=expr

SOURCE ← 被测输入源码

-2.5

EXPECTED ← 期望结果(NIL 表示无错误)

NIL

PROBLEMS ← 诊断问题列表(NIL 表示无诊断)

NIL

TOKENS ← 词法分析(Lexer)输出的 Token 序列

Float, EndOfFile,

PARSE ← 语法分析(Parser)产出的 AST(S-表达式)

(e-frac (raw "-2.5"))

FORMATTED ← 格式化器输出(NO CHANGE 表示已是最佳格式)

NO CHANGE

CANONICALIZE ← 规范化阶段产出的 CIR(规范化内部表示)

(e-dec-small (numerator "-25") (denominator-power-of-ten "1") (value "-2.5"))

TYPES ← 类型检查后的结果

(expr (type "Dec"))
这份文件按编译管线顺序(词法→解析→格式化→规范化→类型检查)依次展示了 `-2.5` 在每一阶段的"体检结果"。下面我们逐阶段深入。 ## 阶段一:词法分析 —— 一个 `Float` Token ```zig Float, EndOfFile,

词法层面,-2.5被整体识别为一个Float类型的 Token,而不是"负号运算符 + 数字"两个 Token。这与普通表达式-foo形成鲜明对比——参考 unary_negation.md,后者被切分为OpUnaryMinus, LowerIdent, EndOfFile三个 Token。

也就是说:负号紧贴在数字字面量前时,负号属于字面量的一部分,词法器不会为它单独产生运算符 Token。这一设计在 docs/langref/numbers.md 中有明确的语义解释:负数前的减号是字面量语法,与对表达式生效的一元取负运算符(negate)是两回事——例如-x是把一元取负运算符作用于x,而-1只是一个普通数字字面量,不会触发任何"取负运算"。

阶段二:语法解析 ——e-frac节点

(e-frac (raw "-2.5"))

解析器(Parser)把该 Token 构建为一个e-frac表达式节点,raw字段保留源码原文"-2.5"。这里的frac即"分数/小数"(fractional)之意,与整数(int)相对。在 src/parse/NumericLiteral.zig 中可以看到词法-解析层对数字字面量的分类:

pub const Kind = enum(u8) { int, frac, };

该文件注释明确指出:数字语法在解析阶段被解释(interpreted),后续阶段直接消费这些解析结果,不得再次解析数字 Token 文本。这正是快照中PARSECANONICALIZE两阶段产物彼此独立、层层递进的原因。

阶段三:格式化 —— 已是最佳格式

NO CHANGE

-2.5经过 Roc 格式化器(formatter)处理后不需要任何改动。从源码结构看,这说明该字面量的排版(负号位置、小数点、数字分布)已经符合格式规范,属于"保持原样"的稳定快照——同样的结论也出现在 float_simple.md(3.14)与 float_scientific.md(1.23e-4)中。

阶段四:规范化 ——SmallDecValue紧凑十进制表示

(e-dec-small (numerator "-25") (denominator-power-of-ten "1") (value "-2.5"))

这是全文最核心的一行。规范化(canonicalization)阶段把 AST 转换成编译器的规范化内部表示(CIR),而e-dec-small代表small decimal(紧凑小数)表达式。它携带三个信息:

字段含义
numerator-25去掉小数点的整数分子(含符号)
denominator-power-of-ten1分母为 10 的幂次,即 $10^{-1}$
value-2.5保留的原始字面量文本

于是 $-2.5 = -25 / 10^1$。之所以叫"small",是因为它的分子用i16存储。在 src/canonicalize/CIR.zig 中可以看到其定义:

/// Represents an arbitrary precision smallish decimal value pub const SmallDecValue = struct { numerator: i16, denominator_power_of_ten: u8,
  • numerator: i16:分子为 16 位有符号整数,取值范围 $[-32768, 32767]$,负号直接编码在分子上(所以-2.5的分子是-25,符号没有丢失);
  • denominator_power_of_ten: u8:10 的幂次(分母位数),无符号 8 位。

同一结构体还提供了两种实用的转换方法(CIR.zig):

/// Convert a small dec to f64 (use for size comparisons) pub fn toF64(self: @This()) f64 { const numerator_f64 = @as(f64, @floatFromInt(self.numerator)); const divisor = std.math.pow(f64, 10, @as(f64, @floatFromInt(self.denominator_power_of_ten))); return numerator_f64 / divisor; } /// Convert to RocDec representation (i128 scaled by 10^18) pub fn toRocDec(self: SmallDecValue) RocDec { return RocDec.fromFraction(self.numerator, self.denominator_power_of_ten); }
  • toF64:分子除以 $10^{幂次}$ 得到f64值,主要用于大小比较;
  • toRocDec:把紧凑表示转成完整的RocDec(以 $10^{18}$ 缩放的i128定点表示),说明e-dec-small是完整Dec表示的高效"快路径"。

该结构体自带单元测试(CIR.zig),其中恰好覆盖了负数用例:

test "SmallDecValue.toF64 - basic cases" { // ... { const val = SmallDecValue{ .numerator = -500, .denominator_power_of_ten = 3 }; try std.testing.expectApproxEqAbs(@as(f64, -0.5), val.toF64(), 0.0001); } // ... }

-500 / 10^3 = -0.5,与-2.5的分子-25 / 10^1是同一编码思路:符号在分子、位数在幂次

阶段五:类型检查 —— 默认推断为Dec

(expr (type "Dec"))

-2.5在没有上下文约束的情况下被推断为Dec类型。这与 docs/langref/numbers.md 中"默认回落到Dec"(Defaulting toDec)的机制一致:当数字字面量没有被任何调用点约束出具体类型时,Roc 使用内建Dec作为默认——因为Dec既支持小数,又能在 REPL 快速计算时给出精确答案。

如果想显式指定类型,可以使用类型后缀语法,例如-2.5.Dec-2.5.F64(详见 docs/langref/numbers.md)。

源码佐证:frac_test.zig中的同类用例

规范化阶段的单元测试 src/canonicalize/test/frac_test.zig 与快照测试相互印证,其中与负数小数直接相关的用例包括:

负数科学计数法(frac_test.zig):

test "fractional literal - negative scientific notation" { const source = "-1.5e-5"; // ... try testing.expectEqual(.e_dec_small, std.meta.activeTag(expr)); try testing.expectEqual(@as(i16, -15), expr.e_dec_small.value.numerator); try testing.expectEqual(@as(u8, 6), expr.e_dec_small.value.denominator_power_of_ten); }

-1.5e-5 = -15 / 10^6,分子-15同样携带负号,幂次6由"小数点后 1 位 + 科学计数法指数 5"合计得出。这也解释了为何 float_scientific.md 中1.23e-4的规范化结果是(numerator "123") (denominator-power-of-ten "6") (value "0.000123")——指数部分会被折算进分母幂次。

超出i16范围自动回退(frac_test.zig):

test "small dec - exceeds i16 range falls back to Dec" { const source = "32768.0"; // ... // Falls back to Dec because 32768 > 32767 (max i16). try testing.expectEqual(.e_dec, std.meta.activeTag(expr)); }

【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询