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

Posted

技术标签:

【中文标题】当任何数学运算产生“NaN”时,如何强制 C# 编译器抛出异常?【英文标题】:How do I force the C# compiler to throw an exception when any math operation produces 'NaN'? 【发布时间】:2010-02-12 11:44:33 【问题描述】:

我最近编写的程序中的一些数学函数返回了不可接受的值,例如 NaN(可能是由于未检查某些函数的输入参数)。问题是很难追踪哪些函数传递了错误的值。这会导致错误在整个代码中传播,并使程序在几分钟或几小时后崩溃(如果有的话)。

我想知道是否有办法在任何操作产生 NaN 值时捕获这些错误操作(如果我记得的话,这与某些 C/C++ 编译器抛出的“DivisionByZero 异常”几乎相同)。

提前致谢。

P.D:如果需要,请随时重新标记我的问题。

【问题讨论】:

在我看来,这是一个应该考虑将测试纳入开发的案例。如果您的数学函数不是太多,您可能需要正确定义前置条件和后置条件并为它们编写测试以避免它们首先返回 NaN。 在这种情况下访问索引 = (long)NaN 值的数组,但它可能是其他任何值。 @Frank 是的,我应该这样做,但我没有这样做,这就是我在这里问的原因:) 【参考方案1】:

如果没有看到你的代码,这个答案必然是模糊的,但一种方法是检查你的函数的输出,如果它是“NaN”引发和异常:

if (double.IsNaN(result))

    throw new ArithmeticException();

但有更多关于异常的详细信息。

更新

要捕获引发特定异常的位置,您可以(暂时)在调试器中引发异常时中断。

选择 Debug > Exceptions,然后展开树选择 Common Language Runtime Exceptions > System > System.ArithmeticException 并选中“Thrown”选项。

这样做的问题是,它会在抛出它的任何地方中断,而不仅仅是在您的代码中。将显式代码放在足够低的级别可以解决这个问题。

【讨论】:

我想要做的是捕捉 NaN 值的产生位置,而无需调试整个应用程序并像您一样对每个接受双精度的方法进行检查。 +1 这正是我想要的,非常感谢更新:) 我也有同样的问题,但困扰我的值不是 NaN,而是“Infinity”。我如上所述检查了 System.ArithmeticException,但是当我的变量设置为“Infinity”时,它不会触发。是否还有其他异常可以检查抛出的选项? @Aaginor - 你想要Double.IsInifinity 方法 像 Trap 一样,我想捕获 Infinite 值何时产生,以了解我的代码中有问题的部分在哪里能够修复它 - 无需逐行逐行找出它在哪里。【参考方案2】:

这个问题似乎有点老了,但是因为我偶然发现了同样的问题: Alexander Torstling 的回答和下面的 cmets 实际上对我来说效果很好。

很好的是,即使 c# 没有提供自己的方式来启用浮点异常,它仍然可以捕获它们(对于 c++,您需要先进行转换)。

C#代码在这里:

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());
    
  

我得到的异常是“算术运算中的溢出或下溢。” System.Exception System.ArithmeticException

不确定为什么调试器会抱怨 _control87 的签名;有人可以改进吗? 不过,“继续”对我来说效果很好。

【讨论】:

【参考方案3】:

我不知道这是否适用于 CLR,但您可以使用 _controlfp_s from 触发浮点异常:

unsigned int _oldState;
errno_t err = _controlfp_s(&oldState, 0, MCW_EM);
assert(!err);

重置:

errno_t err = _controlfp_s(0, _oldState, MCW_EM);
assert(!err);

【讨论】:

您链接了 CRT(C 运行时库)帮助页面,底部是表示这不适用于 .NET 运行时。 OTOH,它确实提到您可以使用 P/Invoke 来调用 C 函数。 我不是 .NET 用户,但假设 C# 使用普通浮点指令似乎是合理的,然后应该抓住这一点。如果 C# 没有混淆这些标志,则应该为 NaN 触发 _EM_INVALID,如果掩码关闭,则应该得到一个 FP 异常。 我也知道这些异常在 VC++ 程序中默认是关闭的,所以值得一试。 谢谢,但这似乎对我没有影响。【参考方案4】:

您的意思是您正在寻找一些设置或选项,以便在任何int 被分配值NaN 时您想要抛出异常?我很确定不存在这样的事情。有checked 选项会警告您溢出,但那不是一回事。

我认为手动调试的唯一替代方法是按照 ChrisF 建议的方式修改代码。如果抛出生产代码,您总是可以在抛出周围放置一个#if DEBUG 以停止。

【讨论】:

是的,更具体地说,当任何值类型(不仅是 int)被分配为 NaN 时。【参考方案5】:

您可以创建一个定义与 int(或 double)相同的操作的类,它包装 int(或 double)。此类将在每次操作后检查 NaN(注意它会比简单的 int 或 double 慢得多)。

在您的代码中,您可以在任何有 int(或 double)的地方使用这个新类。您甚至可以在代码中使用模板类型 TIntegerType 来决定是需要 int 还是类 SafeInt

可能有些人不喜欢这种方法,但我在一些问题上使用它取得了不错的成功(例如,只对需要它的问题使用高精度数学,否则使用机器精度)。

【讨论】:

【参考方案6】:

在调试模式下,您始终可以尝试条件中断,以下是完整答案的链接,因为我用类似的问题回答了其他人:

Conditional Debug

这将允许您在满足条件时简单地暂停执行,这可能不是例外,但它应该对您有所帮助。

【讨论】:

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

隐式转换和强制转换

Java运算符概要与数学函数

为啥模运算符的行为与数学派系 MOD 不同

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

为啥 Math.round() 为 NaN 参数返回 0?

Java中NaN的数学规则[重复]