【问题标题】:TimeSpan FromMilliseconds strange implementation?TimeSpan FromMilliseconds 奇怪的实现?
【发布时间】:2011-07-23 22:54:07
【问题描述】:

我最近在 .NET TimeSpan 实现中遇到了一些奇怪的行为。

TimeSpan test = TimeSpan.FromMilliseconds(0.5);
double ms = test.TotalMilliseconds; // Returns 0

FromMilliseconds 采用 double 作为参数。但是,该值似乎在内部进行了四舍五入。

如果我用 5000 个滴答(0.5 毫秒)实例化一个新的 TimeSpan,则 TotalMilliseconds 的值是正确的。

查看反射器中的TimeSpan 实现会发现输入实际上被转换为长整数。

为什么微软将FromMilliseconds 方法设计为采用双精度参数而不是长参数(因为在此实现中双精度值无用)?

【问题讨论】:

  • 听起来是个糟糕的设计。 @CodeNaked 的回答指出它已记录在案,但这仅意味着他们记录了不良行为。我和你在一起:这是一个错误。如果您将其写在 Connect 中,请在此处发布链接,以便人们投票。

标签: c# .net timespan


【解决方案1】:

首先要考虑的是为什么他们选择一个 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 转换为毫秒时,设计师(测试人员?)不得不在四舍五入之间做出选择。或者在从毫秒到 TimeSpan 时稍后再做。他们选择尽早做,一个勇敢的决定。

通过使用 Ticks 属性并乘以 1E-4 得到毫秒来解决您的问题。

【讨论】:

  • 谢谢,这回答了问题!
  • 我仍然不明白他们为什么不简单地以保持精度的方式实现它。比如:=> this((long)milliseconds) + TimeSpan.FromTicks(1000*(milliseconds - ((long)milliseconds)))
【解决方案2】:

显然,这是设计使然。 documentation 说了这么多:

value参数转换为 滴答声,滴答声的数量是 用于初始化新的 TimeSpan。 因此,价值只会 被认为是最接近的 毫秒。

【讨论】:

  • 这些值显然被转换为刻度。但是,这是在将提供的值转换为 long 之后完成的。
  • @DEHAAS - 没错。无论您使用什么“From”方法,它的精度都限制在最接近的毫秒内。他们只是通过将小数值转换为 long 来切断小数值。
  • @DEHAAS - 他们可能选择了 double 作为参数类型,因此它将“匹配”其他 From 方法。您可以执行 FromSeconds(0.5) 来获得 5000 毫秒。从逻辑上讲,对 FromMilliseconds 使用 double 也是有意义的,即使它可以使用 int/long 来完成。
  • @CodeNaked 我意识到是这样的。我只是想知道为什么微软选择了这样的实现。尤其是在选择这种设计时,为什么 FromMilliseconds 方法采用 double 而不是 long,因为 long 变量中保存的任何附加信息总是会丢失。
  • @CodeNaked 感谢您指出这一点,这非常令人困惑。我在使用 FromMilliseconds 时遇到过这种奇怪的行为,但我不知道其他 From 方法的行为方式相同。
【解决方案3】:

接受双份是合乎逻辑的设计。你可以有几分之一毫秒。

内部发生的是实施设计。即使所有当前的(CLI 的)实现都首先围绕它,将来也不必如此。

【讨论】:

  • doule 值的使用肯定是因为。但是,它似乎会对 TimeSpan 实现进行这种“破坏性”更改。
【解决方案4】:

您的代码的问题实际上是第一行,您调用FromMilliseconds。如前所述,文档中的备注说明如下:

value 参数被转换为刻度,该刻度数用于初始化新的 TimeSpan。因此,value 只会被认为精确到最接近的毫秒。

实际上,这种说法既不正确也不符合逻辑。倒序:

  • 滴答声被定义为“一百纳秒”。根据这个定义,文档应该写成:

    因此,将只被视为精确到最接近的毫秒滴答声,或百万分之一秒

  • 由于错误或疏忽,在初始化新的 TimeSpan 实例之前,value 参数不会直接转换为刻度。这可以在in the reference source for TimeSpan 中看到,其中millis 的值在之前 被舍入到其转换为刻度,而不是之后。如果要保留最大精度,这行代码应该如下所示(并且将删除前 3 行 0.5 毫秒的调整):

    return new TimeSpan((long)(millis * TicksPerMillisecond));
    

总结:

应更新各种TimeSpan.From* 的文档,FromTicks 除外,以声明参数四舍五入到最接近的毫秒(不包括对刻度的引用)。

【讨论】:

    【解决方案5】:

    或者,你可以这样做:

    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 方法中完成,我无法理解。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-19
      • 1970-01-01
      • 2015-09-06
      • 1970-01-01
      • 1970-01-01
      • 2017-08-08
      • 2013-05-28
      • 1970-01-01
      相关资源
      最近更新 更多