When is a System.Double not a double?

2020-07-03 03:35发布

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?

标签: c# clr
2条回答
放我归山
2楼-- · 2020-07-03 04:03

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.

查看更多
3楼-- · 2020-07-03 04:06

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

查看更多
登录 后发表回答