我认为R错误地使用小数秒格式化POSIXct类型.我通过R-bugs提交了这个作为增强请求的内容,并且"我们认为当前的行为是正确的 - 删除了bug".虽然我非常感谢他们已经完成并将继续做的工作,但我想让其他人对这个特定问题采取行动,并且可能就如何更有效地提出要点提出建议.
这是一个例子:
> 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"
Run Code Online (Sandbox Code Playgroud)
也就是说,tt创建为POSIXct时间,小数部分.3秒.当使用一个十进制数字打印时,显示的值为.2.我使用毫秒级精度的时间戳工作很多,这让我很烦恼,因为时间通常比实际值低一个等级.
以下是发生的事情:POSIXct是自纪元以来的浮点秒数.精确处理所有整数值,但在base-2浮点中,与.3最接近的值略小于.3.strftime()
格式的所述行为%OSn
是向下舍入到请求的小数位数,因此显示的结果为.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"
Run Code Online (Sandbox Code Playgroud)
开发人员的论点是,对于时间类型,我们应该总是向下舍入到请求的精度.例如,如果时间是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
Run Code Online (Sandbox Code Playgroud)
我做了一个关于如何在其他语言和环境中处理小数秒的快速调查,并且似乎确实没有达成共识.大多数构造设计为整数秒,而小数部分是事后想法.在我看来,在这种情况下,R开发人员做出的选择并非完全不合理,但实际上并不是最好的选择,并且与其他地方用于显示浮点数的约定不一致.
人们的想法是什么?R行为是否正确?这是你自己设计它的方式吗?
Aar*_*ica 35
一个潜在的问题是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"
Run Code Online (Sandbox Code Playgroud)
我们还可以通过查看两种格式的二进制表示与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
Run Code Online (Sandbox Code Playgroud)
有趣的是,看起来两个表示实际上都比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=""))
}
Run Code Online (Sandbox Code Playgroud)
这从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"
Run Code Online (Sandbox Code Playgroud)
最后一个:你知道标准允许最多两个闰秒吗?
> as.POSIXlt('2011-10-11 23:59:60.9')
[1] "2011-10-11 23:59:60.9"
Run Code Online (Sandbox Code Playgroud)
好的,还有一件事.由于OP提交的错误,该行为实际上在5月发生了变化(错误14579); 在那之前,它确实是小数秒.不幸的是,这意味着有时它可以绕到一秒钟是不可能的; 在错误报告中,当它应该转到下一分钟时,它上升到60.决定截断而不是舍入的一个原因是它是从POSIXlt表示打印的,其中每个单元是分开存储的.因此,滚动到下一分钟/小时/等比仅仅简单的舍入操作更困难.要轻松舍入,有必要在POSIXct表示中进行舍入,然后按照我的建议转换回来.
Mat*_*erg 19
我遇到了这个问题,所以开始寻找解决方案.@Aaron的答案很好,但是仍然可以打破大日期.
根据format
或,以下是正确舍入秒数的代码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, ...)
}
Run Code Online (Sandbox Code Playgroud)
软件因子10 ^( - d-1)来自这里:准确地从字符 - > POSIXct->字符转换为亚毫秒的亚毫秒日期时间.
一些例子:
f <- "%Y-%m-%d %H:%M:%OS"
f3 <- "%Y-%m-%d %H:%M:%OS3"
f6 <- "%Y-%m-%d %H:%M:%OS6"
Run Code Online (Sandbox Code Playgroud)
从几乎相同的问题:
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"
Run Code Online (Sandbox Code Playgroud)
从上面:
> 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"
Run Code Online (Sandbox Code Playgroud)
真正的乐趣在于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"
Run Code Online (Sandbox Code Playgroud)
这段代码似乎适用于我尝试过的其他边缘情况.Aaron 之间format.POSIXct
和之间的共同点myformat.POSIXct
是转换为从秒POSIXct
到POSIXlt
完整的秒数字段.
这指向该转换中的错误.我没有使用任何不可用的数据as.POSIXlt()
.
更新
该bug存在于src/main/datetime.c:434
静态函数中localtime0
,但我还不确定是否正确修复:
第433-434行:
day = (int) floor(d/86400.0);
left = (int) (d - day * 86400.0 + 0.5);
Run Code Online (Sandbox Code Playgroud)
0.5
四舍五入的额外因素是罪魁祸首.请注意,t3
上面的亚秒值超过.5. localtime0
仅处理秒数,并在localtime0
返回后添加子秒.
localtime0
如果double表示为整数值,则返回正确的结果.