3

How to check in C# if the given double number is normal, i.e. is neither zero, subnormal, infinite, nor NaN.

In C++ there was a method std::isnormal which was exactly checking this condition. Is there an equivalent in C#?

Sebastian Widz
  • 1,962
  • 4
  • 26
  • 45
  • 3
    What's abnormal about zero?! – Kerrek SB Oct 26 '14 at 16:44
  • 1
    [System.Double](http://msdn.microsoft.com/en-us/library/system.double.aspx) has `IsNaN` and `IsInfinity`. – Kerrek SB Oct 26 '14 at 16:46
  • @KerrekSB Because it has to be coded [differently](http://stackoverflow.com/questions/26251228/how-does-the-number-0-look-like-in-binary-float-format/26251323#26251323). – Mathias Oct 26 '14 at 16:47
  • You could hardcode the smallest normal double and simply see if your value is smaller than that. – Kerrek SB Oct 26 '14 at 16:51
  • 4
    The low level way is to check if the exponent is 0x000 or 0x7ff. So something like `exp=(val>>53)&0x7ff; return (exp!=0)&&(exp!=0x7ff)`. – Mathias Oct 26 '14 at 16:56

2 Answers2

8

Recent versions of .NET have Double.IsNormal():

public static bool IsNormal(double d);

Returns true if the value is normal; false otherwise.

Applies to

  • .NET Core 3.1, 3.0, 2.2, 2.1
  • .NET Standard 2.1

Unfortunately .NET Framework proper (up until 4.8) does not have it.

Michel de Ruiter
  • 7,131
  • 5
  • 49
  • 74
6

Mathias gave the basic approach to detecting subnormal values in a comment. Here it is coded up:

const long ExponentMask = 0x7FF0000000000000;
static bool IsSubnormal(double v)
{
    if (v == 0) return false;
    long bithack = BitConverter.DoubleToInt64Bits(v);
    return (bithack & ExponentMask ) == 0;
}

static bool IsNormal(double v)
{
    long bithack = BitConverter.DoubleToInt64Bits(v);
    bithack &= ExponentMask;
    return (bithack != 0) && (bithack != ExponentMask);
}

And now it's been tested. Test suite:

static void TestValue(double d)
{
    Console.WriteLine("value is {0}, IsSubnormal returns {1}, IsNormal returns {2}", d, IsSubnormal(d), IsNormal(d));
}

static void TestValueBits(ulong bits)
{
    TestValue(BitConverter.Int64BitsToDouble((long)bits));
}

public static void Main(string[] args)
{
    TestValue(0.0);
    TestValue(1.0);
    TestValue(double.NaN);
    TestValue(double.PositiveInfinity);
    TestValue(double.NegativeInfinity);
    TestValue(double.Epsilon);
    TestValueBits(0xF000000000000000);
    TestValueBits(0x7000000000000000);
    TestValueBits(0xC000000000000000);
    TestValueBits(0x4000000000000000);
    TestValueBits(0xFFF0000000000005);
    TestValueBits(0x7FF0000000000005);
    TestValueBits(0x8010000000000000);
    TestValueBits(0x0010000000000000);
    TestValueBits(0x8001000000000000);
    TestValueBits(0x0001000000000000);
}

Demo: https://rextester.com/CMFOR3934

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • This is what I waited for! Ben you are great. Just to be 100% compliant with C++ reference (http://en.cppreference.com/w/cpp/numeric/math/isnormal), IsNormal should return false for 0, 0.0, Epsilon / 2.0 and all Subnormal numbers. Not sure about Double.Epsilon? – Sebastian Widz Oct 26 '14 at 18:01
  • @SebastianWidz: If you have a test case it fails, please let me know. I did fix a bug with the special casing for zero, so if you copied the first posted answer, you'll need to take the zero test line out of `IsNormal`. – Ben Voigt Oct 26 '14 at 18:04
  • @SebastianWidz: Also, if you test with Epsilon/2.0, make sure that it is the same epsilon mentioned in the C++ or C standard. It looks like in .NET, double.Epsilon/2 might underflow to zero (no longer subnormal). – Ben Voigt Oct 26 '14 at 18:07
  • There is a BUG! TestValue(-0.0) which is equivalent to TestValueBits(0x8000000000000000); returns subnormal = true. But that float value is not subnormal. It is zero. – andreaplanet Apr 30 '21 at 21:23
  • @andreaplanet: Zeros are subnormal (But yes there is an inconsistency between +0.0 and -0.0.... doesn't affect the `IsNormal()` result though) – Ben Voigt May 01 '21 at 23:39
  • A subnormal number is defined in IEEE Std 754™-2008, section 2.1.51, as a non-zero floating point number with magnitude less than the magnitude of that formats smallest normal number. By definition, zero is neither a normal number nor a subnormal number. Also for C++ and C# (.NET Core) the two zero values 0.0 and -0.0 are NOT subnormal but the category zero. Here a code example https://en.cppreference.com/w/cpp/numeric/math/fpclassify However it's easy to fix your implementation. – andreaplanet May 02 '21 at 00:11
  • @andreaplanet: really, zeros are a subset of subnormals. They don't have the implied 1 leading the mantissa, which is the defining characteristic of a subnormal. But I've fixed the code in the interest of checking the category named "subnormals" which consists of subnormals other than zeros. – Ben Voigt May 02 '21 at 00:16
  • "2.1.51 subnormal number: In a particular format, a non-zero floating-point number with magnitude less than the magnitude of that format’s smallest normal number. A subnormal number does not use the full precision available to normal numbers of the same format." "5.7.2 General operations: class(x) tells which of the following ten classes x falls into: signalingNaN, quietNaN, negativeInfinity, negativeNormal, negativeSubnormal, negativeZero, positiveZero, positiveSubnormal, positiveNormal, positiveInfinity. " – andreaplanet May 02 '21 at 00:20
  • I'm citing the IEEE Standard! "IEEE Std 754™-2008, 5.7.2 General operations: boolean isSubnormal(source) isSubnormal(x) is true if and only if x is subnormal. boolean isZero(source) isZero(x) is true if and only if x is ±0. boolean isFinite(source) isFinite(x) is true if and only if x is zero, subnormal or normal (not infinite or NaN). " – andreaplanet May 02 '21 at 00:22
  • @andreaplanet: That definition is probably chosen for the sake of the explanation of "flush subnormals to zero". But everything about zero -- its representation, its magnitude, its treatment -- is just a pair of very special subnormals – Ben Voigt May 02 '21 at 00:26
  • The new code still returns true for isSubnormal TestValue(-0.0) or TestValueBits(0x8000000000000000); – andreaplanet May 02 '21 at 00:32
  • @andreaplanet: No it doesn't. Did you update your test with the new code? – Ben Voigt May 02 '21 at 00:34
  • Oh, I pressed directly the Demo link, which still points to the old code. – andreaplanet May 02 '21 at 00:37
  • 1
    Now that is fixed as well – Ben Voigt May 02 '21 at 00:37
  • BTW if anyone is interested in the float version then the ExponentMask is 0x7F800000 (if I'm not wrong) and instead of BitConverter.DoubleToInt64Bits (when also unsave isn't directly allowed in the code), then for the conversion it is possible to use the System.Runtime.CompilerServices.Unsafe nuget package and the code Unsafe.As(value); – andreaplanet May 02 '21 at 00:48
  • @andreaplanet: `Buffer.BlockCopy` can also be used to implement `SingleToInt32Bits` in .NET versions that don't have it, no nuget package needed. – Ben Voigt May 02 '21 at 00:56