如何检测LDF解析中浮点数的有效数字溢出(无堆分配)
我正在编写LIN Description File(LDF)的解析器,LDF文件中包含浮点数。现有词法分析器生成两类相关令牌:Number(由0-9数字组成的ReadOnlySequence<char>,保留首尾零)、Dot(表示'.'字符)。浮点数对应的令牌序列为「Number->Dot->Number」或「Number」,可据此创建字符Span并调用double.TryParse解析。
我关注double.TryParse的精度截断问题:它会在数值过大时返回Infinity,但我希望检测因输入有效数字过多导致的精度丢失(而非IEEE-754本身的限制),并将其判定为解析错误。例如:0.1因二进制循环无法精确表示属于正常情况,而1.123456789123456789被截断为1.1234567891234568则需判定错误。
请问如何在无托管堆分配的前提下检测此类有效数字溢出?我不想通过简单限制总位数的方式,避免将前置零较多的小数误判为错误。
解决方案
核心思路
double类型的十进制有效精度为15-17位:15位以内的十进制数能被精确表示,16位可能存在歧义,17位及以上必然会被截断。我们需要区分「IEEE-754本身的精度限制(如0.1)」和「输入有效数字过多导致的主动截断」,核心是提取输入的有效数字位数,再结合解析后的值反向验证。
步骤1:无堆分配提取有效数字位数
利用SequenceReader<char>遍历ReadOnlySequence<char>,跳过无关的前导/末尾零,统计真正的有效数字位数:
- 整数场景(仅
Number令牌):- 跳过所有前导零(直到遇到第一个非零数字或遍历结束)
- 统计剩余的数字位数(如果全是零,有效位数为1)
- 小数场景(
Number->Dot->Number令牌):- 整数部分:跳过前导零,统计剩余数字位数
- 小数部分:从后往前跳过末尾零,统计剩余数字位数
- 总有效位数 = 整数部分有效位数 + 小数部分有效位数(如果全是零,有效位数为1)
示例代码片段(无堆分配):
int CountSignificantDigits(ReadOnlySequence<char> digits, bool isFractional) { var reader = new SequenceReader<char>(digits); int count = 0; bool started = false; if (!isFractional) { // 整数部分:跳过前导零 while (reader.TryRead(out char c) && c == '0') { } started = true; } // 统计有效数字 while (reader.TryRead(out char c)) { if (c != '0' || started) { started = true; count++; } } // 处理全零情况 return count == 0 ? 1 : count; } // 组合小数场景的有效位数 int integerDigits = CountSignificantDigits(integerNumberSequence, isFractional: false); int fractionalDigits = CountSignificantDigits(fractionalNumberSequence, isFractional: true); int totalSignificantDigits = integerDigits + fractionalDigits;
步骤2:初步判断有效位数阈值
如果总有效位数超过15位,直接判定为解析错误——这类输入必然会被double.TryParse截断。而像0.00000000000000123这类总位数多但有效位数仅3位的输入,会被正常放行。
步骤3:临界值反向验证(15-17位有效数字)
对于有效位数在15-17位的输入,需要验证解析后的double值是否与原始输入的有效数字完全匹配:
- 用栈分配的
Span<char>存储格式化后的数值:Span<char> buffer = stackalloc char[32]; if (!parsedDouble.TryFormat(buffer, out int written, "G17")) { // 格式化失败,判定错误 return false; } var formattedSpan = buffer[..written]; - 遍历原始输入的有效数字部分(跳过前导零、末尾零和小数点),与格式化后的字符串逐字符对比:
- 跳过格式化字符串中的科学计数法标记(如
e/E),只比较数字部分 - 如果出现不匹配,说明发生了精度截断,判定为解析错误
- 跳过格式化字符串中的科学计数法标记(如
关键注意事项
- 全程使用
SequenceReader<char>和栈分配Span,避免任何托管堆分配 - 只关注有效数字位数,不限制总位数,避免误判前置/后置零较多的合法输入
- 明确区分「IEEE-754固有精度限制」和「输入有效数字过载」:前者是单个有效数字无法精确表示(如
0.1),后者是有效数字数量超过double的承载能力
内容的提问来源于stack exchange,提问作者patvax

