【问题标题】:System.ArithmeticException not thrown in F#System.ArithmeticException 未在 F# 中引发
【发布时间】:2011-06-14 13:12:32
【问题描述】:

考虑下面的代码

let a = 5. / 0.
let b = sin a
let c = sqrt(-5.)

它同时产生 Infinity 和 NaN。在这两种情况下,我都希望抛出异常(出于调试目的)。

我使用 Visual Studio 2010。我将 Debug/Exceptions.../Common Language Runtime Exceptions/System/System.ArithmeticException 设置为“Thrown”,但是在运行代码时,没有抛出异常。

知道为什么以及如何在 NaN 或 Infinity 上引发异常吗?

【问题讨论】:

    标签: .net exception f# nan infinity


    【解决方案1】:

    如果您想要算术异常,请尝试将整数除以零。 System.Double 类型(F# 中的 float)按设计不会引发异常(所有异常情况都以 NaN 结尾)。

    来自the MSDN docs

    浮点运算符, 包括赋值运算符,做 不抛出异常。相反,在 特殊情况的结果 浮点运算为零, 无穷大,或 NaN....


    更新:如果您希望在InfinityNaN 的情况下引发异常,我会提供same advice as desco 并建议您包装您要调用的方法。

    很遗憾,我对 F# 不够熟悉,无法以您选择的语言提供代码示例;但在 C# 中,您可能会为 sqrt 函数执行此操作:

    public static double CheckedSqrt(double x)
    {
        double sqrt = Math.Sqrt(x);
        if (double.IsNaN(sqrt))
        {
            throw new ArithmeticException("The square root of " + x + " is NaN.");
        }
    
        return sqrt;
    }
    

    更新 2:另一种选择是为 double 类型本身编写自己的包装器,它不允许 InfinityNaN 值(同样,下面是 C#-如果这在 F# 中是不可能的,我深表歉意,在这种情况下,我给你的建议绝对是无用的):

    public struct CheckedDouble // : IEquatable<CheckedDouble>, etc.
    {
        double m_value;
    
        public CheckedDouble(double value)
        {
            if (double.IsInfinity(value) || double.IsNaN(value))
            {
                throw new ArithmeticException("A calculation resulted in infinity or NaN.");
            }
    
            m_value = value;
        }
    
        public static implicit operator CheckedDouble(double value)
        {
            return new CheckedDouble(value);
        }
    
        public static implicit operator double(CheckedDouble checkedDouble)
        {
            return checkedDouble.m_value;
        }
    }
    

    然后,无论您在编写不想允许InfinityNaN 的代码,请使用此类型而不是直接使用double

    只是另一种选择。

    【讨论】:

    • 作为对这个简洁的答案的补充,这里有一个我觉得很有趣的网页:kossovsky.net/index.php/2010/04/…
    • 也就是说,没有简单的方法可以检查代码中是否出现 NaN 或 Infinity,对吧? IsInfinity 不是一个选项,因为它意味着检查每一行代码、每个操作、每个数组等。太糟糕了...
    【解决方案2】:

    想一想,这只有通过在 sin\sqrt 上提供自定义包装器才能实现。记录了 Math.SqrtMath.Sin 的当前行为。

    【讨论】:

      【解决方案3】:

      正如其他人所说,您必须明确检查 NaN 条件。如果您想使用一些高级 F# 功能来做到这一点,那么您也可以使用计算表达式。

      当您使用let! 绑定值时,可以定义一个自动检查 Nan 的构建器。然后你可以这样写:

      check { let! a = 5. / 0.    // 'a' is checked here
              let! b = sin a      // 'b' is checked here
              let c = sqrt(-5.)   // 'c' is not checked (no 'let!')
              return a, b, c }
      

      对于简单的检查来说,这可能过于复杂,但我觉得它非常好。计算构建器的定义如下所示(您需要添加WhileFor 和其他一些以支持所有语言结构):

      open System
      
      type CheckedBuilder() = 
        member x.Bind(v:float, f) = 
          if Double.IsNaN(v) |> not then f v
          else raise (new ArithmeticException())
        member x.Return(v) = v
      
      let check = CheckedBuilder()
      

      【讨论】:

      • 我不太喜欢您的解决方案,但我会将其标记为已接受的答案,因为它可能是除了包装浮动之外的唯一方法。但是绝对应该有一个选项可以在 NaN 上抛出异常,用于调试目的的双精度无穷大......
      • 我喜欢 Double.IsNaN(v) |&gt; not 成语。不是!
      猜你喜欢
      • 1970-01-01
      • 2018-10-13
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-16
      相关资源
      最近更新 更多