C# math gotchas

C# has three mathematical constants that look like constants in the C header file float.h. Two of these are not what you might expect.

The constant double.MaxValue in C# looks like the constant DBL_MAX in C, and indeed it is. Both give the maximum finite value of a double, which is on the order of 10^308. This might lead you to believe that double.MinValue in C# is the same as DBL_MIN in C or that double.Epsilon in C# is the same as DBL_EPSILON. If so, you’re in for a surprise.

The constants DBL_MAX and double.MaxValue are the same because there is no ambiguity over what “max” means: the largest finite value of a double. But DBL_MIN and double.MinValue are different because they minimize over different ranges. The constant DBL_MIN is the smallest positive value of a normalized double. The constant double.MinValue in C# is the smallest (i.e. most negative) value of a double and is the negative of double.MaxValue. The difference between DBL_MIN and double.MinValue is approximately the difference between 10^-308 and -10^308, between a very small positive number and a very large negative number.

C has a constant DBL_EPSILON for the smallest positive double precision number x such that 1 + x does not equal 1 in machine precision. Typically a double has about 15 figures of precision, and so DBL_EPSILON is on the order of 10^-16. (For a more precise description, see Anatomy of a floating point number.)

You might expect double.Epsilon in C# corresponds to DBL_EPSILON in C. I did, until a unit test failed on some numerical code I was porting from C++ to C#. But in C# double.Epsilon is the smallest positive value a (denormalized) double can take. It is similar to DBL_MIN, except that double.Epsilon is the possible smallest value of a double, not requiring normalization. The constant DBL_MIN is on the order of 10^-308 while double.Epsilon is on the order of 10^-324 because it allows denormalized values. (See Anatomy of a floating point number for details of denormalized numbers.)

Incidentally, the C constants DBL_MAX, DBL_MIN, and DBL_EPSILON equal the return values of max, min, and epsilon for the C++ class numeric_limits<double>.

To summarize,

  • double.MaxValue in C# equals DBL_MAX in C.
  • double.MinValue in C# equals -DBL_MAX in C.
  • double.Epsilon is similar to DBL_MIN in C, but orders of magnitude smaller.
  • C# has no analog of DBL_EPSILON from C.

One could argue that the C# names are better than the C names. It makes sense for double.MinValue to be the negative of double.MaxValue. But the use of Epsilon was a mistake. The term “epsilon” in numeric computing has long been established and is not limited to C. It would have been better if Microsoft had used the name MinPositiveValue to be more explicit and not conflict with established terminology.

Related posts

12 thoughts on “C# math gotchas

  1. Great post. Thanks for detailing this out! Glad you used unit tests to catch this instead of finding out in the wild :).

  2. Hi, read your informative posts on floating point arithmetic. Do you know why in C# double and float arithmetic 1.0/0.0 evaluates to Infinity, where it would seem more rigorous for division by zero to yield a NaN as occurs in several other circumstances under IEEE 754?

  3. nit: from the sentence: “The difference between DBL_MIN and double.MinValue is approximately the difference between 10^-308 and -10^308, between a very small positive number and a very large negative number.”

    -10^308 = -1^308 * 10^308 = 10^308 … -(10^308) intended perhaps?

  4. Sean T. McBeth

    Technical point of clarification, these values are constants in the System.Double struct from the mscorlib.dll version of the base .NET Class Library, i.e. they will be this way regardless of what language you are running on the CLR, be it F#, VB.NET, IronPython, etc., not just C#.

  5. @Eutactic: You could argue that 1/0 should return the limit as x->0+ of 1/x. That would justify returning infinity. I believe that’s in keeping with the spirit of the IEEE standard. NaNs are reserved for situations where it is impossible to argue for other values such as infinity or negative infinity. For example, sqrt(-1).

    @Chris: I’ve been getting a lot of traffic over the last couple days and sometimes the stylesheet doesn’t serve correctly.

    @Anonymous: I believe my notation is standard. Exponentiation has higher precedence than multiplication, so -a^b means -(a^b). If you want (-a)^b you need parentheses. Negation is typically interpreted the same as multiplication by -1 and so has the same precedence as multiplication.

    @Sean: Good point. Thanks for adding that.

  6. @John: You are correct, Anonymous is wrong. (-a^b) always means “the negation of a^b”. Which is how it should be, of course, since (-a)^b is always equal to either -(a^b) or (a^b) when b is a whole number, and so it would be silly to devote to it the short-notation real estate of parenthesisless (-a^b).

  7. 1/|x| -> infinity, as x -> 0 ;
    but, 1/x has different limits as x goes to 0+ or 0-
    Therefore, 1/x should be NaN.

  8. @Anonymous22.1: Yes, you make a good argument for why 1/0 should produce a NaN. I imagine people on the IEEE committee made that argument, but here is why I believe the committee decided on infinity rather than NaN.

    There are actually two representations of zero: +0 and -0. That may sound absurd, but it is useful. If a positive quantity underflows to zero, it becomes +0. And if a negative quantity underflows to zero, it becomes -0. You could think of +0 (respectively, -0) as the bit pattern for a positive (negative) number too small to represent. The standard says 1/+0 should be +infinity and 1/-0 should be -infinity. This makes sense if you interpret +/- 0 as the ghost of a number that underflowed leaving behind only its sign.

  9. Thanks for informative article, but the most important issue here is:
    How to replace the C++ numeric_limits::epsilon() in C# ??

    Is there any known workaround for this unfortunate implementation by Microsoft?

  10. Yikes. I’ve always been bugged by DBL_MIN (and it’s even worse in C++ as std::numeric_limits<double&rt;::min(), where it messes up generic constructs), but between that and “Epsilon,” I’m not sure which is more misleading!

    On the bright side, for IEEE-754 floating point types, the “other epsilon” is easily computed, as it is necessarily a power of 2.

  11. Also, just found this gem in the MSDN docs:

    On ARM systems, the value of the Epsilon constant is too small to be detected, so it equates to zero. You can define an alternative epsilon value that equals 2.2250738585072014E-308 instead.

    I take that to mean it’s defined as the smallest denormalized number even on platforms that don’t have denormalized floats? What could possibly be the use of such a constant? O_o

Comments are closed.