After seeing how double.Nan == double.NaN
is always false in C#, I became curious how the equality was implemented under the hood. So I used Resharper to decompile the Double struct, and here is what I found:
public struct Double : IComparable, IFormattable, IConvertible, IComparable<double>, IEquatable<double>
{
// stuff removed...
public const double NaN = double.NaN;
// more stuff removed...
}
This seems to indicate the the struct Double
declares a constant that is defined in terms of this special lower case double
, though I'd always thought that the two were completely synonymous. What's more, if I Go To Implementation on the lowercase double, Resharper simply scrolls me to the declaration at the top of the file. Similarly, jumping to implementation of the lowercase's NaN
just takes me to the constant declaration earlier in the line!
So I'm trying to understand this seemingly recursive definition. Is this just an artefact of the decompiler? Perhaps a limitation in Resharper? Or is this lowercase double actually a different beast altogether - representing something at a lower level from the CLR/CTS?
Where does NaN
really come from?
The Double value type represents a double-precision 64-bit number with values ranging from negative 1.79769313486232e308 to positive 1.79769313486232e308, as well as positive or negative zero, Double. PositiveInfinity, Double. NegativeInfinity, and Not-a-Number ( Double. NaN).
There is no difference. double is just an alias for System. Double in C#.
You can use double. TryParse() it will return false if it couldn't create a double.
The double is a fundamental data type built into the compiler and used to define numeric variables holding numbers with decimal points. C, C++, C# and many other programming languages recognize the double as a type. A double type can represent fractional as well as whole values.
Beware looking at decompiled code, especially if it is for something inbuilt. The actual IL here (for .NET 4.5, at least) is:
.field public static literal float64 NaN = float64(NaN)
{
.custom instance void __DynamicallyInvokableAttribute::.ctor()
}
i.e. this is handled directly in IL via the NaN
token.
However, because it is a const
(literal
in IL), it will get "burned into" the call site; anywhere else that uses double.NaN
will also be using float64(NaN)
. Similarly, example, if I do:
const int I = 2;
int i = I;
int j = 2;
both of these assignments will look identical in the final IL (they will both be ldc.i4.2
).
Because of this, most decompilers will recognise the IL pattern NaN
and represent it with the language's equivalent of double.NaN
. But that doesn't mean that the code is itself recursive; they probably just don't have a check for "but is it double.NaN itself?". Ultimately, this is simply a special case, where float64(NaN)
is a recognised value in IL.
Incidentally, reflector decompiles it as:
[__DynamicallyInvokable]
public const double NaN = (double) 1.0 / (double) 0.0;
That again doesn't meant that this is truth :p Merely that this is something which may have the same end result.
By far the best source you can get for .NET assemblies is the actual source code that was used to build them. Beats any decompiler for accuracy, the comments can be quite useful as well. Download the Reference Source.
You'll then also see that Double.NaN
isn't defined in IL as Marc assumed, it's actually in a C# source code file. The net/clr/bcl/system/double.cs
source code file shows the real declaration:
public const double NaN = (double)0.0 / (double)0.0;
Which takes advantage of the C# compiler evaluating constant expressions at compile time. Or to put it tongue-in-cheek, NaN is defined by the C++ compiler since that's the language that was used to write the C# compiler ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With