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

CIL解析Double类型字符串"15"出现精度偏差的原因咨询

浮点数解析精度差异问题:C#与手动编写CIL的行为不一致

我同时实现了C#版本的解析器和手动编写的CIL解析器,发现一个异常现象:

  • C#代码将字符串"15"解析为double类型时,结果是15
  • 手动编写的CIL代码解析同一个字符串,得到的结果却是15.000000000000004

测试中,"+"运算的用例能通过,但"*"、"-"、"/"运算的测试会出现预期值15和实际值15.000000000000004不匹配的问题。我猜测C#编译为CIL时会对浮点数精度做特殊处理,但手动编写的CIL直接转机器码时没有额外转换,想请教这个现象的原因和解决办法。


CIL代码

.method private static valuetype Hw2.CalculatorOperation ParseOperation(string arg) cil managed
{
    ldarg.0
    ldstr "+"
    call bool [System.Runtime]System.String::op_Equality(string, string)
    brtrue.s PlusCase
    
    ldarg.0
    ldstr "-"
    call bool [System.Runtime]System.String::op_Equality(string, string)
    brtrue.s MinusCase
    
    ldarg.0
    ldstr "/"
    call bool [System.Runtime]System.String::op_Equality(string, string)
    brtrue.s DivideCase
    
    ldarg.0
    ldstr "*"
    call bool [System.Runtime]System.String::op_Equality(string, string)
    brtrue.s MultiplyCase
    
    ldc.i4.4
    ret

    PlusCase:
    ldc.i4.0
    ret

    MinusCase:
    ldc.i4.1
    ret

    MultiplyCase:
    ldc.i4.2
    ret
    
    DivideCase:
    ldc.i4.3
    ret
}
.method private static bool IsArgLengthSupported(string[] args) cil managed
{
    ldarg.0
    ldlen
    ldc.i4.3
    ceq
    ret
}
.method public static void ParseCalcArguments(string[] args,
    [out] float64& val1,
    [out] valuetype Hw2.CalculatorOperation& op,
    [out] float64& val2) cil managed
{
    ldarg.0
    call bool Hw2.Parser::IsArgLengthSupported(string[])
    ldc.i4.0
    beq.s ArgumentException
    
    ldarg.0
    ldc.i4.1
    ldelem.ref
    call valuetype Hw2.CalculatorOperation Hw2.Parser::ParseOperation(string)
    ldc.i4.4
    ceq
    brtrue InvalidOperationException
    
    ldarg.0 
    ldc.i4.0 
    ldelem.ref 
    ldarg.1 
    call bool [System.Runtime]System.Double::TryParse(string, float64&) 
    brtrue.s SecondTryParse
    ldstr "first number isn't actually a number"
    newobj instance void [System.Runtime]System.ArgumentException::.ctor(string)
    throw
    
    SecondTryParse:
    ldarg.0
    ldc.i4.2
    ldelem.ref
    ldarg.3
    call bool [System.Runtime]System.Double::TryParse(string, float64&)
    brtrue.s CalculationOperationDefining
    ldstr "second number isn't actually a number"
    newobj instance void [System.Runtime]System.ArgumentException::.ctor(string)
    throw
    
    CalculationOperationDefining:
    ldarg.1
    ldarg.0
    ldc.i4.1
    ldelem.ref
    call valuetype Hw2.CalculatorOperation Hw2.Parser::ParseOperation(string)
    stind.i4
    ret
    
    InvalidOperationException:
    ldstr "unknown operation"
    newobj instance void [System.Runtime]System.InvalidOperationException::.ctor(string)
    throw
    
    ArgumentException:
    ldstr "array length should be 3"
    newobj instance void [System.Runtime]System.ArgumentException::.ctor(string)
    throw
}

测试代码

[InlineData("*", CalculatorOperation.Multiply)]
public void TestCorrectOperations(string operation, CalculatorOperation operationExpected)
{
    // arrange
    var args = new[] { "15", operation, "5" };

    //act
    Parser.ParseCalcArguments(args, out var val1, out var operationResult, out var val2);

    //assert
    Assert.Equal(15, val1);
    Assert.Equal(operationExpected, operationResult);
    Assert.Equal(5, val2);
}

C#解析器代码

public static void ParseCalcArguments(string[] args, 
        out double val1, 
        out CalculatorOperation operation, 
        out double val2)
{
    if (!IsArgLengthSupported(args)) 
        throw new ArgumentException("array length should be 3");

    if (ParseOperation(args[1]) == CalculatorOperation.Undefined)  
        throw new InvalidOperationException();
    
    
    if (Double.TryParse(args[0], out val1) && Double.TryParse(args[2], out val2))
    {
        operation = ParseOperation(args[1]);
    }
    else
        throw new ArgumentException("one of the numbers isn't actually a number");
}

问题原因与解决办法

核心原因

你编写的CIL代码存在逻辑错误:在CalculationOperationDefining代码块中,错误地将解析出的操作码写入了val1的输出参数(ldarg.1是val1的引用),而不是目标参数op(对应ldarg.2)。

这个错误会导致已经解析好的double类型val1的内存被覆盖成操作码的整数值(比如乘法操作是2),当double类型读取这段内存时,会解析出一个接近15的异常数值,也就是你看到的15.000000000000004。

解决办法

  1. 修复CIL代码的参数错误:
    修改CalculationOperationDefining部分的代码,将操作码写入正确的op参数:

    CalculationOperationDefining:
        ldarg.2  // 替换原有的ldarg.1,指向op参数
        ldarg.0
        ldc.i4.1
        ldelem.ref
        call valuetype Hw2.CalculatorOperation Hw2.Parser::ParseOperation(string)
        stind.i4
        ret
    
  2. 规范浮点数断言方式:
    即使修复了逻辑错误,后续处理浮点数时也不应该用精确相等比较。在测试中使用带容差的断言:

    // 指定精度位数
    Assert.Equal(15, val1, precision: 10);
    // 或者直接设置容差值
    Assert.Equal(15, val1, 1e-9);
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 16:53:12