这真的很奇怪。我正在追踪这个错误:
Negating the minimum value of a twos complement number is invalid.
...结果证明这是由于这样的代码:
var valueFromUser = "470259123000000";
var doubleValue = Convert.ToDouble(valueFromUser, CultureInfo.InvariantCulture);
Math.Abs((int)doubleValue);
确实,当我在 LINQPad 中运行它时:
(int)Convert.ToDouble("470259123000000", CultureInfo.InvariantCulture)
...它给了我:
-2147483648
但是,这里的另一位开发人员说他得到了完全不同的东西(不是在 LINQPad 中):
-1141206336
当我尝试仅对常量本身进行强制转换时:
(int)470259123000000.0
...由于需要unchecked
,我得到一个编译错误。还有这个:
unchecked((int)470259123000000.0)
...像其他开发人员一样评估为 -1141206336
。所以我想也许 Convert
创建了一个与常量略有不同的值。不,这评估为 True
:
Convert.ToDouble("470259123000000", CultureInfo.InvariantCulture) == 470259123000000.0
这到底是怎么回事?为什么评估这些看似相同的表达式会产生如此不同的结果?
更新:
找到一个提示。 4.70259123E14
和-1141206336
的十六进制表示是:
0x42FABB2BBFA92C00
0xBBFA92C0
所以我猜其中一个转换是将位直接插入 int
。所以 -2147483648
是更大的谜团。
我不确定根本原因,但它看起来像是一个编译器错误,因为使用 Roslyn 编译的程序为两个表达式提供了相同的值 (-2147483648)。
允许编译器在编译时评估常量表达式。所有带有未检查表达式的转换都是由编译器完成的,但在其他情况下,它们是由 CLR 在运行时完成的,因此它们总是有可能使用略有不同的规则。正如您所观察到的,编译器似乎以不同于运行时的方式截断以将值放入 32 位整数。您可以在底层 IL 中看到程序只是加载常量值 (0xbbfa92c0) 来代替未经检查的表达式。
using System;
using System.Globalization;
public class Program
{
public static void Main(string[] args)
{
var n = unchecked((int)470259123000000.0);
Console.WriteLine(n);
n = (int)Convert.ToDouble("470259123000000", CultureInfo.InvariantCulture);
Console.WriteLine(n);
}
}
从 .NET 4.5 编译器反编译的 IL:
.method public hidebysig static void Main(string[] args) cil managed
{
//
.maxstack 2
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldc.i4 0xbbfa92c0
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call void [mscorlib]System.Console::WriteLine(int32)
IL_000d: nop
IL_000e: ldstr "470259123000000"
IL_0013: call class [mscorlib]System.Globalization.CultureInfo [mscorlib]System.Globalization.CultureInfo::get_InvariantCulture()
IL_0018: call float64 [mscorlib]System.Convert::ToDouble(string,
class [mscorlib]System.IFormatProvider)
IL_001d: conv.i4
IL_001e: stloc.0
IL_001f: ldloc.0
IL_0020: call void [mscorlib]System.Console::WriteLine(int32)
IL_0025: nop
IL_0026: ret
} // end of method Program::Main