Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I force the C# compiler to throw an exception when any math operation produces 'NaN' in .net 4?

I have a long complicated source code and I need to find the exact location at which a variable value set to nan. So I need the compiler to throw an exception at that point. This question has been asked before. I found the following answer as a good answer. This code works well in .net 3.5.But when I use .net 4, this solution doesn't work correctly. Even when I Enable "break when an exception is thrown" in the debugger, the exception can't be located in the code. Any idea?

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());
    }
  }
}
like image 896
Soraya Avatar asked Oct 21 '22 01:10

Soraya


1 Answers

Found the problem. Modify the calls to use _controlfp instead of _control87, and then change the DllImport to:

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

You have to explicitly tell .NET that your methods must be called with the Cdecl convention. Then it works. Next time please write the full exception... The full message should be:

A call to PInvoke function '_controlfp' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

Or you can cheat :-)

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

and then:

DoubleNoNan dn = 0.0;
dn = / 0.0;

For the float, replace the word Double with Single and the word double with float. Operators will be provided by the implicit casts.

Clearly it has its limits (it can't be used in ref/out expressions, and many others)

Your code seems to work correctly... I've checked it at 32 and 64 bits

like image 163
xanatos Avatar answered Oct 23 '22 18:10

xanatos