TimeSpan FromMilliseconds 奇怪的实现?
我最近在 .NET TimeSpan
实现中遇到了一些奇怪的行为。
TimeSpan test = TimeSpan.FromMilliseconds(0.5);
double ms = test.TotalMilliseconds; // Returns 0
FromMilliseconds
采用双精度作为参数。然而,该值似乎是内部四舍五入的。
如果我实例化一个具有 5000 个刻度 (0.5 毫秒) 的新 TimeSpan
,则 TotalMilliseconds
的值是正确的。
查看反射器中的 TimeSpan
实现,可以发现输入实际上被转换为 long。
为什么 Microsoft 设计 FromMilliseconds
方法来采用双精度参数而不是 long(因为对于此实现,双精度值是无用的)?
I recently encountered some weird behaviour in the .NET TimeSpan
implementation.
TimeSpan test = TimeSpan.FromMilliseconds(0.5);
double ms = test.TotalMilliseconds; // Returns 0
FromMilliseconds
takes a double as parameter. However, it seems the value is rounded internally.
If I instantiate a new TimeSpan
with 5000 ticks (.5 ms), the value of TotalMilliseconds
is correct.
Looking at the TimeSpan
implementation in reflector reveals that the input is in fact casted to a long.
Why did Microsoft design the FromMilliseconds
method to take a double a parameter instead of a long (since a double value is useless given this implementation)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
第一个考虑因素是想知道为什么他们选择 double 作为返回值。使用long将是一个显而易见的选择。虽然已经有了一个非常好的属性,那就是“长”,但 Ticks 的单位是 100 纳秒,这一点是明确的。但他们选择了 double,可能是为了返回小数值。
然而,这产生了一个新问题,这个问题可能直到后来才被发现。双精度数只能存储 15 个有效数字。一个TimeSpan可以存储10,000年。 非常希望从 TimeSpan 转换为毫秒,然后再转换回 TimeSpan 并获得相同的值。
这对于双打来说是不可能的。计算一下:10,000 年大约为 10000 x 365.4 x 24 x 3600 x 1000 = 315,705,600,000,000 毫秒。数出 15 位数字(最好是双精度数),您将得到恰好一毫秒作为仍然可以存储而没有舍入误差的最小单位。任何额外的数字都将是随机噪声。
设计人员(测试人员?)进退两难,在从 TimeSpan 转换为毫秒时,必须在对值进行四舍五入之间做出选择。或者稍后在从毫秒到时间跨度时执行此操作。他们选择尽早这样做,这是一个勇敢的决定。
通过使用 Ticks 属性并乘以 1E-4 得到毫秒来解决您的问题。
The first consideration is wondering why they selected a double as the return value. Using long would have been an obvious choice. Although there already is a perfectly good property that is long, Ticks is unambiguous with a unit of 100 nanoseconds. But they picked double, probably with the intention to return a fractional value.
That however created a new problem, one that was possibly only discovered later. A double can store only 15 significant digits. A TimeSpan can store 10,000 years. It is very desirable to convert from TimeSpan to milliseconds, then back to TimeSpan and get the same value.
That isn't possible with a double. Doing the math: 10,000 years is roughly 10000 x 365.4 x 24 x 3600 x 1000 = 315,705,600,000,000 milliseconds. Count off 15 digits, best a double can do, and you get exactly one millisecond as the smallest unit that can still be stored without round-off error. Any extra digits will be random noise.
Stuck between a rock and a hard place, the designers (testers?) had to choose between rounding the value when converting from TimeSpan to milliseconds. Or to do it later when going from milliseconds to TimeSpan. They chose to do it early, a courageous decision.
Solve your problem by using the Ticks property and multiplying by 1E-4 to get milliseconds.
显然,这是设计使然。 文档说了这么多:
This is by design, obviously. The documentation says as much:
接受双精度是一个合乎逻辑的设计。您可以有几分之一毫秒。
内部发生的是实现设计。即使(CLI 的)当前所有实现都首先围绕它,将来也不一定如此。
Accepting a double is a logical design. You can have fractions of milliseconds.
What's happening internally is an implementation design. Even if all current implementations (of the CLI) round it first that doesn't have to be the case in the future.
您的代码的问题实际上是第一行,您在其中调用
FromMilliseconds
。如前所述,文档中的注释说明如下:事实上,这种说法既不正确,也不符合逻辑。按相反的顺序:
刻度被定义为“一百纳秒”。根据这个定义,文档应该写成:
<块引用>
因此,值只会被认为精确到最接近的
毫秒滴答声,或百万分之一秒。由于错误或疏忽,在初始化新的 TimeSpan 实例之前,value 参数不会直接转换为刻度。这可以在 TimeSpan 的参考源中看到,其中 < code>millis 值在转换为刻度之前进行舍入,而不是之后。如果要保留最大精度,这行代码应该如下所示(并且将删除之前 3 行 0.5 毫秒的调整):
摘要:
各种
TimeSpan.From*
的文档,除FromTicks
之外,应更新为声明参数四舍五入到最接近的毫秒(不包括对刻度的引用)。The problem with your code is actually the first line, where you call
FromMilliseconds
. As noted previously, the remarks in the documentation state the following:In reality, this statement is neither correct nor logically sound. In reverse order:
Ticks are defined as "one hundred nanoseconds". By this definition, the documentation should have been written as:
Due to a bug or oversight, the value parameter is not converted directly to ticks prior to initializing the new TimeSpan instance. This can be seen in the reference source for TimeSpan, where the
millis
value is rounded prior to its conversion to ticks, rather than after. If maximum precision were to be preserved, this line of code should have read as follows (and the adjustment by 0.5 milliseconds 3 lines earlier would be removed):Summary:
The documentation for the various
TimeSpan.From*
, with the exception ofFromTicks
, should be updated to state that the argument is rounded to the nearest millisecond (without including the reference to ticks).或者,您可以这样做:
--sarcasm
TimeSpan 将毫秒的两倍转换为刻度,因此“显然”您可以拥有小于 1ms 粒度的 TimeSpan。
-/讽刺
——这根本不明显......
为什么这不在 .FromMilliseconds 方法内完成,我无法理解。
Or, you could do:
--sarcasm
TimeSpan converts the double of milliseconds to ticks so "OBVIOUSLY" you can have a TimeSpan with less than a 1ms granularity.
-/sarcasm
-- this isn't obvious at all...
why this isn't done inside the .FromMilliseconds method is beyond me.