当任何数学运算在 .net 4 中产生“NaN”时,如何强制 C# 编译器抛出异常?

Posted

技术标签:

【中文标题】当任何数学运算在 .net 4 中产生“NaN”时,如何强制 C# 编译器抛出异常?【英文标题】:How do I force the C# compiler to throw an exception when any math operation produces 'NaN' in .net 4? 【发布时间】:2013-09-19 15:01:24 【问题描述】:

我有一个很长很复杂的源代码,我需要找到将变量值设置为 nan 的确切位置。所以我需要编译器在那时抛出异常。 这个问题以前有人问过。我发现以下答案是一个很好的答案。此代码在 .net 3.5 中运行良好。但是当我使用 .net 4 时,此解决方案无法正常工作。即使我在调试器中启用“抛出异常时中断”,也无法在代码中找到异常。有什么想法吗?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace ConsoleApplication2

  class Program
  
    [System.Runtime.InteropServices.DllImport("msvcrt.dll")]
    public static extern uint _control87(uint a, uint b);

    [System.Runtime.InteropServices.DllImport("msvcrt.dll")]
    public static extern uint _clearfp();

    static void Main(string[] args)
    
      float zero = 0.0f - args.Length; // Want 0.0f. Fool compiler...
      System.Console.WriteLine("zero = " + zero.ToString());

      // A NaN which does not throw exception
      float firstNaN = zero / 0.0f;
      System.Console.WriteLine("firstNaN= " + firstNaN.ToString());

      // Now turn on floating-point exceptions
      uint empty = 0;
      uint cw = _control87(empty, empty); // Debugger halts on this one and complains about false signature, but continue works.
      System.Console.WriteLine(cw.ToString());
      uint MCW_EM = 0x0008001f; // From float.h
      uint _EM_INVALID = 0x00000010; // From float.h (invalid corresponds to NaN
      // See http://www.fortran-2000.com/ArnaudRecipes/CompilerTricks.html#x86_FP

      cw &= ~(_EM_INVALID);
      _clearfp(); // Clear floating point error word.
      _control87(cw, MCW_EM); // Debugger halts on this one and complains about false signature, but continue works.      
      System.Console.WriteLine(cw.ToString());

      // A NaN which does throw exception
      float secondNaN = 0;
      try
      
        // Put as much code here as you like.
        // Enable "break when an exception is thrown" in the debugger
        // for system exceptions to get to the line where it is thrown 
        // before catching it below.
        secondNaN = zero / 0.0f;
      
      catch (System.Exception ex)
      
        _clearfp(); // Clear floating point error word.
            

      System.Console.WriteLine("secondNaN= " + secondNaN.ToString());
    
  

【问题讨论】:

你的问题是什么...编译时错误或吞下运行时错误? @SimonWhitehead 我会说他想将信号 NaN 捕获为异常。 如果你想捕获所有个浮点异常,改变uint _EM_INVALID = 0x0000001F;。请参阅_controlfp 命令的文档。例如,捕捉Math.Log(0.0) . 【参考方案1】:

发现问题。将调用修改为使用_controlfp 而不是_control87,然后将DllImport 更改为:

[System.Runtime.InteropServices.DllImport("msvcrt.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
public static extern uint _controlfp(uint a, uint b);

[System.Runtime.InteropServices.DllImport("msvcrt.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
public static extern uint _clearfp();

您必须明确告诉 .NET,您的方法必须使用 Cdecl 约定调用。然后它工作。下次请写完整的异常...完整的消息应该是:

对 PInvoke 函数“_controlfp”的调用导致堆栈不平衡。这可能是因为托管 PInvoke 签名与非托管目标签名不匹配。检查 PInvoke 签名的调用约定和参数是否与目标非托管签名匹配。

或者你可以作弊:-)

struct DoubleNoNan

    public readonly double Value;

    public DoubleNoNan(double value)
    
        if (Double.IsNaN(value))
        
            throw new Exception("NaN");
        

        this.Value = value;
    

    public static implicit operator double(DoubleNoNan value)
    
        return value.Value;
    

    public static implicit operator DoubleNoNan(double value)
    
        return new DoubleNoNan(value);
    

    public override bool Equals(object obj)
    
        return this.Value.Equals(obj);
    

    public override int GetHashCode()
    
        return this.Value.GetHashCode();
    

    public override string ToString()
    
        return this.Value.ToString();
    

然后:

DoubleNoNan dn = 0.0;
dn = / 0.0;

对于float,将单词Double 替换为Single,将单词double 替换为float。运算符将由隐式转换提供。

显然它有其局限性(它不能用于ref/out 表达式以及许多其他表达式)

您的代码似乎可以正常工作...我已经检查了 32 位和 64 位的代码

【讨论】:

感谢您的回答。您的第一个解决方案解决了一半的问题,但我仍然看到堆栈溢出异常。当代码到达我希望发生 nan 异常的位置时,我看到:“mscorlib.dll 中发生了类型为 'System.***Exception' 的未处理异常”。 @Soraya 然后有一个*** :-) 看调用栈(Debug->Windows->Call Stack) 不幸的是调用堆栈不能帮助我。 “[外部代码]”是我的调用堆栈的内容:D @Soraya 只有“外部代码”?不能向下或向上滚动? 是的。无非是“外部代码”。用您的新代码替换 dll 并找到 nan 异常的确切位置后,代码是否在您的计算机上正常运行?

以上是关于当任何数学运算在 .net 4 中产生“NaN”时,如何强制 C# 编译器抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

MATLAB中NaN是怎么产生的,又如何具体的去解决?

c ++:在数学错误中创建实际错误而不是nan

附加列在 pandas DataFrame 中产生 NaN

如何在 c 中产生 NaN 浮点数?

NaN 在 scikit-learn 中产生问题

日期数学不会在 R 与 Redshift 中产生相同的结果