62

我相信 R 错误地用小数秒格式化 POSIXct 类型。我通过 R-bugs 提交了这个作为增强请求,并被“我们认为当前的行为是正确的 - 已删除错误”拒之门外。虽然我非常感谢他们已经完成并继续做的工作,但我想听取其他人对这个特定问题的看法,也许就如何更有效地表达这一点提出建议。

这是一个例子:

 > tt <- as.POSIXct('2011-10-11 07:49:36.3')
 > strftime(tt,'%Y-%m-%d %H:%M:%OS1')
 [1] "2011-10-11 07:49:36.2"

也就是说,tt 被创建为小数部分为 0.3 秒的 POSIXct 时间。当它以一位十进制数字打印时,显示的值为 0.2。我经常使用毫秒精度的时间戳,这让我很头疼,因为时间通常比实际值低一个档次。

这是正在发生的事情:POSIXct 是自纪元以来的浮点秒数。所有整数值都经过精确处理,但在以 2 为底的浮点数中,最接近 0.3 的值比 0.3 略小。strftime()for 格式的规定行为%OSn是向下舍入到请求的小数位数,因此显示的结果是 0.2。对于其他小数部分,浮点值略高于输入的值,并且显示给出了预期的结果:

 > tt <- as.POSIXct('2011-10-11 07:49:36.4')
 > strftime(tt,'%Y-%m-%d %H:%M:%OS1')
 [1] "2011-10-11 07:49:36.4"

开发人员的论点是,对于时间类型,我们应该始终向下舍入到要求的精度。例如,如果时间是 11:59:59.8,那么用格式打印它%H:%M应该给出“11:59”而不是“12:00”,并且%H:%M:%S 应该给出“11:59:59”而不是“12:00:00”。对于整数秒数和格式标志%S,我同意这一点,但我认为对于为秒的小数部分设计的格式标志,行为应该有所不同。我希望看到%OSn使用舍入到最近的行为,即使n = 0同时%S使用舍入,以便使用格式打印 11:59:59.8%H:%M:%OS0会给出“12:00:00”。这不会影响整数秒的任何内容,因为它们总是精确表示,但它会更自然地处理小数秒的舍入错误。

这就是在例如 C 中处理小数部分打印的方式,因为整数转换向下舍入:

 double x = 9.97;
 printf("%d\n",(int) x);   //  9
 printf("%.0f\n",x);       //  10
 printf("%.1f\n",x);       //  10.0
 printf("%.2f\n",x);       //  9.97

我对其他语言和环境中如何处理小数秒进行了快速调查,但似乎确实没有达成共识。大多数构造都是为整数秒而设计的,而小数部分是事后才想到的。在我看来,在这种情况下,R 开发人员做出的选择并非完全不合理,但实际上并不是最好的选择,并且与其他地方显示浮点数的约定不一致。

人们的想法是什么?R行为是否正确?是你自己设计的方式吗?

4

2 回答 2

36

一个潜在的问题是 POSIXct 表示不如 POSIXlt 表示精确,并且 POSIXct 表示在格式化之前被转换为 POSIXlt 表示。下面我们看到,如果我们的字符串直接转换为 POSIXlt 表示,它就会正确输出。

> as.POSIXct('2011-10-11 07:49:36.3')
[1] "2011-10-11 07:49:36.2 CDT"
> as.POSIXlt('2011-10-11 07:49:36.3')
[1] "2011-10-11 07:49:36.3"

我们还可以通过查看两种格式的二进制表示与通常的 0.3 表示之间的差异来看出这一点。

> t1 <- as.POSIXct('2011-10-11 07:49:36.3')
> as.numeric(t1 - round(unclass(t1))) - 0.3
[1] -4.768372e-08

> t2 <- as.POSIXlt('2011-10-11 07:49:36.3')
> as.numeric(t2$sec - round(unclass(t2$sec))) - 0.3
[1] -2.831069e-15

有趣的是,看起来这两种表示实际上都小于通常的 0.3 表示,但第二种表示要么足够接近,要么以与我在这里想象的不同的方式截断。鉴于此,我不会担心浮点表示的困难;它们可能仍然会发生,但如果我们小心使用哪种表示,它们有望被最小化。

罗伯特对四舍五入输出的渴望只是一个输出问题,可以通过多种方式解决。我的建议是这样的:

myformat.POSIXct <- function(x, digits=0) {
  x2 <- round(unclass(x), digits)
  attributes(x2) <- attributes(x)
  x <- as.POSIXlt(x2)
  x$sec <- round(x$sec, digits)
  format.POSIXlt(x, paste("%Y-%m-%d %H:%M:%OS",digits,sep=""))
}

这从一个 POSIXct 输入开始,然后首先四舍五入到所需的数字;然后它转换为 POSIXlt 并再次四舍五入。第一次舍入确保当我们处于分钟/小时/天边界时所有单位都适当增加;转换为更精确的表示后的第二次舍入。

> options(digits.secs=1)
> t1 <- as.POSIXct('2011-10-11 07:49:36.3')
> format(t1)
[1] "2011-10-11 07:49:36.2"
> myformat.POSIXct(t1,1)
[1] "2011-10-11 07:49:36.3"

> t2 <- as.POSIXct('2011-10-11 23:59:59.999')
> format(t2)
[1] "2011-10-11 23:59:59.9"
> myformat.POSIXct(t2,0)
[1] "2011-10-12 00:00:00"
> myformat.POSIXct(t2,1)
[1] "2011-10-12 00:00:00.0"

最后一点:您知道标准最多允许两个闰秒吗?

> as.POSIXlt('2011-10-11 23:59:60.9')
[1] "2011-10-11 23:59:60.9"

好的,还有一件事。由于 OP 提交的错误(错误 14579),行为实际上在 5 月发生了变化;在此之前,它会循环小数秒。不幸的是,这意味着有时它可能会精确到一秒,这是不可能的。在错误报告中,当它应该滚动到下一分钟时,它上升到了 60。决定截断而不是舍入的一个原因是它是从 POSIXlt 表示中打印的,其中每个单元都是单独存储的。因此,滚动到下一分钟/小时/等比简单的舍入操作更困难。为了轻松四舍五入,有必要按照我的建议对 POSIXct 表示进行四舍五入,然后再转换回来。

于 2011-10-11T18:33:10.140 回答
20

我遇到了这个问题,所以开始寻找解决方案。@Aaron 的回答很好,但仍然会因大型约会而中断。

这是根据formator正确舍入秒数的代码option("digits.secs")

form <- function(x, format = "", tz= "", ...) {
  # From format.POSIXct
  if (!inherits(x, "POSIXct")) 
    stop("wrong class")
  if (missing(tz) && !is.null(tzone <- attr(x, "tzone"))) 
    tz <- tzone

  # Find the number of digits required based on the format string
  if (length(format) > 1)
    stop("length(format) > 1 not supported")

  m <- gregexpr("%OS[[:digit:]]?", format)[[1]]
  l <- attr(m, "match.length")
  if (l == 4) {
    d <- as.integer(substring(format, l+m-1, l+m-1))
  } else {
    d <- unlist(options("digits.secs"))
    if (is.null(d)) {
      d <- 0
    }
  }  


  secs.since.origin <- unclass(x)            # Seconds since origin
  secs <- round(secs.since.origin %% 60, d)  # Seconds within the minute
  mins <- floor(secs.since.origin / 60)      # Minutes since origin
  # Fix up overflow on seconds
  if (secs >= 60) {
    secs <- secs - 60
    mins <- mins + 1
  }

  # Represents the prior minute
  lt <- as.POSIXlt(60 * mins, tz=tz, origin=ISOdatetime(1970,1,1,0,0,0,tz="GMT"));
  lt$sec <- secs + 10^(-d-1)  # Add in the seconds, plus a fudge factor.
  format.POSIXlt(as.POSIXlt(lt), format, ...)
}

10^(-d-1) 的软糖因素来自这里: Aaron准确地从 character->POSIXct->character 转换为亚毫秒日期时间

一些例子:

f  <- "%Y-%m-%d %H:%M:%OS"
f3 <- "%Y-%m-%d %H:%M:%OS3"
f6 <- "%Y-%m-%d %H:%M:%OS6"

从一个几乎相同的问题:

x <- as.POSIXct("2012-12-14 15:42:04.577895")

> format(x, f6)
[1] "2012-12-14 15:42:04.577894"
> form(x, f6)
[1] "2012-12-14 15:42:04.577895"
> myformat.POSIXct(x, 6)
[1] "2012-12-14 15:42:04.577895"

从上面:

> format(t1)
[1] "2011-10-11 07:49:36.2"
> myformat.POSIXct(t1,1)
[1] "2011-10-11 07:49:36.3"
> form(t1)
[1] "2011-10-11 07:49:36.3"

> format(t2)
[1] "2011-10-11 23:59:59.9"
> myformat.POSIXct(t2,0)
[1] "2011-10-12 00:00:00"
> myformat.POSIXct(t2,1)
[1] "2011-10-12 00:00:00.0"

> form(t2)
[1] "2011-10-12"
> form(t2, f)
[1] "2011-10-12 00:00:00.0"

真正的乐趣来自于 2038 年的某些日期。我认为这是因为我们在尾数中失去了更多的精度。注意秒字段的值。

> t3 <- as.POSIXct('2038-12-14 15:42:04.577895')
> format(t3)
[1] "2038-12-14 15:42:05.5"
> myformat.POSIXct(t3, 1)
[1] "2038-12-14 15:42:05.6"
> form(t3)
[1] "2038-12-14 15:42:04.6"

这段代码似乎适用于我尝试过的其他边缘情况。format.POSIXctAaron 的答案之间和中的共同点是在秒字段完好无损myformat.POSIXct的情况下转换为 from POSIXctPOSIXlt

这表明该转换中存在错误。我没有使用任何不可用于as.POSIXlt().

更新

该错误存在src/main/datetime.c:434于静态函数localtime0中,但我不确定正确的修复方法:

第 433-434 行:

day = (int) floor(d/86400.0);
left = (int) (d - day * 86400.0 + 0.5);

四舍五入的额外0.5值是罪魁祸首。请注意,上面的亚秒值t3超过 0.5。 localtime0仅处理秒,并且在localtime0返回后添加亚秒。

localtime0如果出现的双精度值是整数值,则返回正确的结果。

于 2013-02-06T03:16:01.980 回答