You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测LDF解析中浮点数的有效数字溢出(无堆分配)

问题: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. 跳过所有前导零(直到遇到第一个非零数字或遍历结束)
    2. 统计剩余的数字位数(如果全是零,有效位数为1)
  • 小数场景(Number->Dot->Number令牌):
    1. 整数部分:跳过前导零,统计剩余数字位数
    2. 小数部分:从后往前跳过末尾零,统计剩余数字位数
    3. 总有效位数 = 整数部分有效位数 + 小数部分有效位数(如果全是零,有效位数为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值是否与原始输入的有效数字完全匹配:

  1. 用栈分配的Span<char>存储格式化后的数值:
    Span<char> buffer = stackalloc char[32];
    if (!parsedDouble.TryFormat(buffer, out int written, "G17"))
    {
        // 格式化失败,判定错误
        return false;
    }
    var formattedSpan = buffer[..written];
    
  2. 遍历原始输入的有效数字部分(跳过前导零、末尾零和小数点),与格式化后的字符串逐字符对比:
    • 跳过格式化字符串中的科学计数法标记(如e/E),只比较数字部分
    • 如果出现不匹配,说明发生了精度截断,判定为解析错误

关键注意事项

  • 全程使用SequenceReader<char>和栈分配Span,避免任何托管堆分配
  • 只关注有效数字位数,不限制总位数,避免误判前置/后置零较多的合法输入
  • 明确区分「IEEE-754固有精度限制」和「输入有效数字过载」:前者是单个有效数字无法精确表示(如0.1),后者是有效数字数量超过double的承载能力

内容的提问来源于stack exchange,提问作者patvax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:25:03