为什么TimeSpan.FromSeconds(double)会四舍五入到毫秒?

37

TimeSpan.FromSeconds接受一个double类型的参数,可以表示100纳秒的时间值,但是这个方法不可理喻地将时间四舍五入为整数毫秒。

既然我刚刚花了半小时才确定这一(已记录在案的!)行为,如果知道为什么会这样做,那么就更容易忍受浪费的时间了。

有人能够解释为什么实现了这种看似逆向操作的行为吗?

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

很遗憾看到旧答案消失了。我怀疑我能否想出更好的答案(而且我想+1它...) - Peter
了解为什么会出现这种情况将使我们更容易忍受浪费的时间。把它视为已经发生的成本。 - jason
1
我也被这个问题困扰过。我的理论是这是 .net 1 中的一个 bug,但由于这会破坏现有程序,所以没有进行更改。在我看来,微软至少应该更新智能感知描述,以表明这些函数只具有毫秒精度。 - CodesInChaos
1
微软错误报告入口:https://connect.microsoft.com/VisualStudio/feedback/details/653782/timespan-frommilliseconds-incorrect-parsing - jbe
5个回答

13

正如您自己发现的那样,这是一个已记录的功能。它在 TimeSpan文档 中有描述:

参数

value 类型:System.Double

秒数,精确到最近毫秒

这么做的原因可能是因为double不是很准确。当比较double时,总是建议进行四舍五入,因为它可能比您预期的略微大或略微小。这种行为实际上可能会在您尝试放入整个毫秒时提供一些意外的纳秒。我认为这就是他们选择将值舍入到整毫秒并丢弃较小数字的原因。


“意外的纳秒”听起来像是一个可行的理论。虽然在我的看法中并不能完全证明,但这并不是重点。+1。 - Roman Starkov
是的,但毫秒对大多数人来说可能已经足够了。那些聪明到可以使用纳秒的人可以使用 TimeSpan.FromTicks(TimeSpan.TicksPerSecond * YourSecondsDouble)。;) - GolezTrol

7

关于规范的猜想...

  1. TimeSpan.MaxValue.TotalMilliseconds相当于922337203685477,这是一个15位数的数字。
  2. double精确到15位数字。
  3. TimeSpan.FromSecondsTimeSpan.FromMinutes等都会转换为以double表示的毫秒数(然后转换为滴答声,然后转换为现在不重要的TimeSpan)。

因此,当您创建接近于TimeSpan.MaxValue(或MinValue)的TimeSpan时,转换将仅精确到毫秒
所以问题"why"可能答案是:始终具有相同的准确度
进一步需要考虑的事情是,如果首先将值转换为以long表示的滴答声,是否可以更好地完成工作。


根据文档,值首先会转换为ticks。 - GolezTrol
@Golez:没关系,我们都会偶尔犯错。 - alpha-mouse

6

想象一下,你是负责设计TimeSpan类型的开发人员。你已经完成了所有基本功能,一切似乎都运行得很好。然后有一天,一个测试人员出现了,并向你展示了这段代码:

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!的结果让测试人员 understandably skeptical。
此时,你需要做出决定。要么允许对从double值构造的TimeSpan值进行算术运算,以产生精度超过double类型本身的准确性的结果,例如100000000000000.5(16位有效数字); 要么不允许这样做。
所以你决定,你知道什么,我只需使使用double构造TimeSpan的任何方法四舍五入到最近的毫秒即可。这样,明确记录了将double转换为TimeSpan是一种有损操作,从而使客户在将double转换为TimeSpan并希望获得准确结果后看到奇怪的行为时得到解释。
我并不一定认为这是“正确”的决定;显然,这种方法本身会引起某些混乱。我只是说需要做出一种决定,无论是这样还是那样,这似乎就是已经做出的决定。

你可以使用这个论点来完全禁止使用双精度浮点数... 但我可以想象设计师在测试这个特定类时对绝对初学者付出了过多的关注... 希望我们能确定它是如何出现的 :) - Roman Starkov

1

0

FromSeconds使用私有方法Interval

public static TimeSpan FromSeconds(double value)
{
    return Interval(value, 0x3e8);
}

0x3e8 == 1000

使用间隔方法将该常量乘以值,然后转换为长整型(请参见最后一行):

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)个符号。使用反射器进行调查

我当然查看了。反射器总是可以回答“如何”,但不能回答“为什么”。这有助于你找出为什么在乘法之前进行长整型转换吗?如果没有记录,我会认为这是一个错误。也许这仍然是一个更容易记录而不是修复的错误。底线是,这段代码无法回答“为什么”的问题。 - Roman Starkov
1
@romkyns:“说明一个错误比修复一个错误更容易!”我想你刚刚找到了微软的新口号!;-) - Nicolas Buduroi
@Nicolas:作为一个独立的短语,这听起来很有趣。但我不认为我们在这里遇到了一个“意外”的错误。这个设计可能是有意的。 - alpha-mouse
2
这个回答根本没有解决问题。 - Timwi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接