显式地将double转换为int,结果不同

时间:2011-10-27 18:32:12

标签: c# .net

我们正在使用基本上执行此操作的API

var t = TimeSpan.MaxValue;
int x = (int)t.TotalMilliseconds;

其中x将最终传递给System.Threading.WaitHandle.WaitOne(int)。

问题是当这个代码在我们的dev和staging环境中运行时,它不会抛出任何错误,但是当它在生产中运行时它会抛出:

Exception: System.ArgumentOutOfRangeException
Message: Number must be either non-negative and less than or equal to Int32.MaxValue or -1.
Parameter name: millisecondsTimeout

当我使用一个简单的控制台应用程序(x86和x64)测试时,x = -2147483648(int.MinValue)的结果,而当我在即时窗口中运行代码时,我得到x = 1566804069。

发生了什么事?

注意:暂存和生产都是从一个VM克隆的,因此它们之间没有差异

这是我们无法改变的代码!否则我不会问这个问题。

4 个答案:

答案 0 :(得分:4)

TimeSpan.MaxValue.TotalMilliseconds是一个等于922337203685477的double,它大于Int32.MaxValue(2147483647)。在这种情况下演员将做什么特定于实现(技术上它是未定义请参阅下面的@phoog评论)并且可能取决于CPU,这可能解释了你的差异看到。

在一种情况下,演员会导致System.Threading.WaitHandle.WaitOne(int)可接受的值,而在另一种情况下则不是。

这似乎是您正在使用的库中的错误。有一个WaitOne重载,它将TimeSpan作为参数,所以我不知道他们为什么不使用它。如果你不能改变图书馆,那你就不走运了。

答案 1 :(得分:4)

使用相同VM的唯一方法是,生产计算机上的CPU与登台计算机上的CPU不同 - 正如Gabe在问题的评论中询问的那样zdanhis answer中建议。

具体到底是怎么回事。对于支持SSE2的计算机,.NET使用cvttsd2si指令将double转换为int,其中overflow映射到0x80000000(Int.MinValue)。在没有SSE2支持的机器上,我只能查看Rotor sources,而在jithelpers.cpp中,它只是将double转换为int32 - 在VC10 C ++上没有SSE2,最终返回值低32位所以传递给wait的值应该是1566804069(0x5D638865),如你在即时窗口中看到的那样。

CPU是不同的,你修改代码的“修复”是将机器改为不支持SSE2的东西。请参阅SSE2维基百科条目以检查生产服务器的CPU与登台服务器。如果你很幸运,也许可以在服务器的BIOS(或VMs config / bios)中禁用它。

如果你大胆,你可以尝试修补IL来修复问题 - 代码真正想要的是-1作为超时,即“永远等待”。通过使用ilasm和ildasm你可能能够修复它没有源(我假设这是你不能改变它的原因)。我自己在测试程序上成功完成了这项工作 - ildasm test.exe /out=test.il将程序集转换为IL,编辑IL,最后编译ilasm test.il /exe以创建新程序集。下面是我的IL看起来以及如何修复它。

// bad code
// var t = TimeSpan.MaxValue;
IL_0008:  call       instance float64System.TimeSpan::get_TotalMilliseconds()

// int x = (int)t.TotalMilliseconds;
IL_000D:  conv.i4    // This is the line that becomes cvttsd2si when jitted
IL_000E:  stloc.2

// wh.WaitOne(x);
IL_000F:  ldloc.0
IL_0010:  ldloc.2
IL_0011:  callvirt   instance bool System.Threading.WaitHandle::WaitOne(int32)

修复是在调用Wait one

之前用-1重新加载x(这里的位置2)
// fixed code
// var t = TimeSpan.MaxValue;
IL_0008:  call       instance float64System.TimeSpan::get_TotalMilliseconds()

// int x = (int)t.TotalMilliseconds;
IL_000D:  conv.i4    // This is the line that becomes cvttsd2si when jitted
IL_000E:  stloc.2

// x = -1; // Fix by forcing x to -1 (infinite timeout)
          ldc.i4.m1  // push a -1
          stloc.2    // pop and store it in 'x'

// wh.WaitOne(x);
IL_000F:  ldloc.0
IL_0010:  ldloc.2
IL_0011:  callvirt   instance bool System.Threading.WaitHandle::WaitOne(int32)

请注意,在这种情况下,'x'是本地#2 - 方法顶部的IL将为您提供正确的#,因此stloc.2中的2需要更改为已分配的#x ,在我的例子中,在调用WaitOne标签IL_0010之前,它应该与ldloc指令中的#匹配。

答案 2 :(得分:0)

您的转换溢出了为什么您为不同的系统获得不同的结果。 请改用长

  var t = TimeSpan.MaxValue;
  long x = (long)t.TotalMilliseconds;

答案 3 :(得分:0)

TimeSpan.MaxValue与Int64.MaxValue相同,这是一个太大的值,无法传递给WaitOne()。如果要传递大值,只需使用Int32.MaxValue。