我最近在.net timepan实现中遇到了一些奇怪的行为。
TimeSpan test = TimeSpan.FromMilliseconds(0.5);
double ms = test.TotalMilliseconds; // Returns 0
FromMilliseconds采用double作为参数。但是,似乎内部的值是四舍五入的。
如果我使用5000个滴答(.5毫秒)实例化新的时间跨度,则TotalMilliseconds的值是正确的。
查看反射器中的TimeSpan实现会发现输入实际上已转换为long。
为什么Microsoft将FromMilliseconds方法设计为使用double参数而不是long(因为根据此实现,double值是无用的)?
答案 0 :(得分:24)
首先考虑的是他们为什么选择 double 作为返回值。使用 long 将是一个明显的选择。虽然已经有一个很长的完美的属性,但Ticks是明确的,单位为100纳秒。但他们选择了两倍,可能是为了返回一个小数值。
然而,这产生了一个新问题,一个可能只是稍后才发现的问题。双倍只能存储15位有效数字。 TimeSpan可以存储10,000年。 非常希望从TimeSpan转换为毫秒,然后再转换回TimeSpan并获得相同的值。
双重不可能。算一算:10000年约为10000 x 365.4 x 24 x 3600 x 1000 = 315,705,600,000,000毫秒。计算15位数,最好是双倍数,你可以完全一毫秒作为仍然可以存储而没有舍入误差的最小单位。任何额外的数字都是随机噪音。
在摇滚和硬地之间,设计师(测试人员?)在从TimeSpan转换为毫秒时必须选择舍入值。或者稍后从毫秒到TimeSpan。他们选择早做,这是一个勇敢的决定。
使用Ticks属性并乘以1E-4来解决问题,以获得毫秒数。
答案 1 :(得分:3)
这显然是设计的。 documentation同样说:
value参数转换为 滴答滴答,滴答数是 用于初始化新的TimeSpan。 因此,价值只会是 认为精确到最近 毫秒。强>
答案 2 :(得分:2)
接受双重是一种逻辑设计。你可以有几分之一毫秒。
内部发生的是实施设计。即使所有当前(CLI的实现)的实现都是先将其舍入,但未来也不一定如此。
答案 3 :(得分:1)
您的代码问题实际上是第一行,您可以在其中调用FromMilliseconds
。如前所述,文档中的备注说明如下:
value 参数转换为刻度,并且该刻度数用于初始化新的TimeSpan。因此, value 仅被认为是精确到最接近的毫秒。
实际上,这种说法既不正确也不合逻辑。以相反的顺序:
蜱被定义为“一百纳秒”。根据这个定义,文档应该写成:
因此, value 只会被认为是精确到最近的
毫秒刻度,或者是千万分之一秒。
由于存在错误或疏忽,在初始化新TimeSpan实例之前,值参数不会直接转换为刻度。这可以看作in the reference source for TimeSpan,其中millis
值在之前舍入为其转换为滴答,而不是之后。如果要保留最大精度,那么这行代码应该如下所示(并且将删除之前的0.5毫秒3行的调整):
return new TimeSpan((long)(millis * TicksPerMillisecond));
应更新各种TimeSpan.From*
的文档({1}}除外),以说明参数舍入到最接近的毫秒(不包括对刻度的引用)。
答案 4 :(得分:0)
或者,你可以这样做:
double x = 0.4;
TimeSpan t = TimeSpan.FromTicks((long)(TimeSpan.TicksPerMillisecond * x)); // where x can be a double
double ms = t.TotalMilliseconds; //return 0.4
- 讽刺
TimeSpan将双倍的毫秒转换为滴答,以便" 显然"你可以拥有一个粒度小于1毫秒的TimeSpan。
- /讽刺
- 这根本不是很明显...... 为什么在.FromMilliseconds方法中没有做到这一点超出了我的范围。