为啥 TimeSpan.FromSeconds(double) 舍入到毫秒?

Posted

技术标签:

【中文标题】为啥 TimeSpan.FromSeconds(double) 舍入到毫秒?【英文标题】:Why does TimeSpan.FromSeconds(double) round to milliseconds?为什么 TimeSpan.FromSeconds(double) 舍入到毫秒? 【发布时间】:2011-06-08 00:37:19 【问题描述】:

TimeSpan.FromSeconds 采用双精度,并且可以表示低至 100 纳秒的值,但是这种方法莫名其妙地将时间四舍五入到整毫秒。

鉴于我刚刚花了半个小时来查明这种(记录在案的!)行为,知道为什么会出现这种情况会让我更容易忍受浪费的时间。

谁能说明为什么会实施这种看似适得其反的行为?

TimeSpan.FromSeconds(0.12345678).TotalSeconds
    // 0.123
TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond * 0.12345678)).TotalSeconds
    // 0.1234567

【问题讨论】:

很遗憾看到旧答案消失了。我怀疑我能想出更好的东西(我想 +1...) "[K]知道为什么会出现这种情况会让你更容易忍受浪费的时间。"将其视为沉没成本。 也被那个咬了。我的理论是它是 .net 1 中的一个错误,并且没有被更改,因为它会破坏现有程序。 IMO MS 至少应该更新智能感知描述以表明这些函数只有毫秒精度。 MS 错误条目:connect.microsoft.com/VisualStudio/feedback/details/653782/… 【参考方案1】:

正如您自己发现的那样,这是一个记录在案的功能。在documentation of TimeSpan中有描述:

参数

类型:System.Double

秒数,精确到最接近的毫秒

造成这种情况的原因可能是因为双精度完全不准确。在比较双打时进行一些四舍五入总是一个好主意,因为它可能只是比您预期的要大或小一点。当您尝试输入整毫秒时,这种行为实际上可能会为您提供一些意想不到的纳秒。我认为这就是他们选择将值四舍五入到整毫秒并丢弃较小数字的原因。

【讨论】:

“意外的纳秒”听起来像是一个合理的理论。在 my 书中仍然不能证明它是合理的,但这不是重点。 +1。 是的,但是对于大多数人来说,毫秒可能就足够了。聪明到可以使用纳秒的人可以使用TimeSpan.FromTicks(TimeSpan.TicksPerSecond * YourSecondsDouble)。 ;)【参考方案2】:

关于投机的权利..

    TimeSpan.MaxValue.TotalMilliseconds 等于 922337203685477。15 位数字。 double 精确到 15 位。 TimeSpan.FromSecondsTimeSpan.FromMinutes 等都经过转换为以double 表示的毫秒数(然后转为滴答声,然后转为TimeSpan,现在不感兴趣了)

因此,当您创建接近于TimeSpan.MaxValue(或MinValue)的TimeSpan 时,转换将精确到毫秒。 因此,可能“为什么”问题的回答是:始终保持相同精度。 需要进一步考虑的是,如果首先将值转换为以long 表示的刻度来完成转换,这项工作是否可以做得更好。

【讨论】:

根据文档,值首先转换为刻度。 @Golez:你在哪里看到这样的文档? The value parameter is converted to milliseconds, which is converted to ticks, and that number of ticks is used to intialize the new TimeSpan @Golez:没关系。我们都偶尔会犯错。【参考方案3】:

假设您是负责设计TimeSpan 类型的开发人员。您已经具备了所有基本功能;这一切似乎都很好。然后有一天,某个 Beta 测试人员出现并向您展示了这段代码:

double x = 100000000000000;
double y = 0.5;
TimeSpan t1 = TimeSpan.FromMilliseconds(x + y);
TimeSpan t2 = TimeSpan.FromMilliseconds(x) + TimeSpan.FromMilliseconds(y);
Console.WriteLine(t1 == t2);

为什么会输出 False 测试人员问你。即使您明白为什么会发生这种情况(将xy 加在一起时会丢失精度),但您必须承认,从客户的角度来看,它确实似乎有点奇怪。然后他把这个扔给你:

x = 10.0;
y = 0.5;
t1 = TimeSpan.FromMilliseconds(x + y);
t2 = TimeSpan.FromMilliseconds(x) + TimeSpan.FromMilliseconds(y);
Console.WriteLine(t1 == t2);

那个输出 True! 测试人员的怀疑是可以理解的。

此时您需要做出决定。您可以允许在从double 值构造的TimeSpan 值之间进行算术运算,以产生其精度超过double 类型本身的精度的结果——例如,100000000000000.5(16 个有效数字)——或者你可以,你知道,不允许

所以你决定,你知道吗,我会这样做,以便任何使用double 构造TimeSpan 的方法都将四舍五入到最接近的毫秒。这样,明确记录double 转换为 TimeSpan 是一个有损操作,如果客户在从 double 转换为TimeSpan 并希望得到准确的结果。

我不一定认为这是“正确”的决定;显然,这种方法本身会引起一些混乱。我只是说需要以一种或另一种方式做出决定,而这显然是决定的。

【讨论】:

你可以用这个论点来完全禁止双打......但是,我可以想象设计师在测试这个特定的类时过分关注绝对初学者......希望我们能确定如何这应运而生:)【参考方案4】:

我想解释是有的:TimeSpan structure incorrectly handles values close to min and max value

而且看起来它不会很快改变:-)

【讨论】:

【参考方案5】:

FromSeconds 使用私有方法 Interval

public static TimeSpan FromSeconds(double value)

    return Interval(value, 0x3e8);

0x3e8 == 1000

该 const 上的 Interval 方法 multiplay 值,然后转换为 long(见最后一行):

private static TimeSpan Interval(double value, int scale)

    if (double.IsNaN(value))
    
        throw new ArgumentException(Environment.GetResourceString("Arg_CannotBeNaN"));
    
    double num = value * scale; // Multiply!!!!
    double num2 = num + ((value >= 0.0) ? 0.5 : -0.5);
    if ((num2 > 922337203685477) || (num2 < -922337203685477))
    
        throw new OverflowException(Environment.GetResourceString("Overflow_TimeSpanTooLong"));
    
    return new TimeSpan(((long) num2) * 0x2710L); // Cast to long!!!

因此我们有 3 (x1000) 个符号的精度。 使用反射器进行调查

【讨论】:

我当然看了。 Reflector 总能回答如何,但不能回答why。这是否有助于您找出为什么转换为 long 发生在乘法之前?如果没有记录在案,我会认为这是一个错误。也许它仍然是一个比修复更容易记录的错误。底线是,这段代码没有回答“为什么”的问题。 @romkyns “一个错误比修复更容易记录!”我想你刚刚发现了微软的新口号! ;-) @Nicolas:作为一个单独的短语,这听起来很有趣。但我不认为我们在这里遇到了 unexpected 错误。这种设计可能是故意的。 这个答案根本没有解决问题。

以上是关于为啥 TimeSpan.FromSeconds(double) 舍入到毫秒?的主要内容,如果未能解决你的问题,请参考以下文章

vb.net 多线程调用另一窗口,假死现象,如何解决

C# 4.0:我可以使用 TimeSpan 作为具有默认值的可选参数吗?

C# convert.todouble问题

WPF的XAML怎么实现textbox里显示一个平移动画的实时横坐标?

为什么OperationCancelledException传递的是与CTSource不同的CancellationToken?

System.Threading.Timer 在 25-30 次后停止 [重复]