你有没有注意到,在 R 中 base::round(2.5) 和 base::round(1.5) 的返回值都是 2?是不是觉得很奇怪?至少我在学校里只学过一种舍入规则,就是“四舍五入”。意思是小数部分达到五就进位,四则舍去。
通过阅读 base::round() 的文档,我们可以了解到有一个叫做 “IEEE 754” 的标准,而 base::round() 使用的舍入规则叫做 “go to the even digit”(银行家舍入法,即向最近的偶数舍入)。
Note that for rounding off a 5, the IEC 60559 standard (see also ‘IEEE 754’) is expected to be used, ‘go to the even digit’.
搜索 “IEEE 754” 会找到维基百科页面,其中列出了五种舍入规则。“To nearest, ties away from zero”(四舍五入)正是我们所熟悉的那一种。遗憾的是,由于未知原因,base::round() 选择了第一种规则 “to nearest, ties to even”(银行家舍入)。
| 模式 / 示例值 | 11.5 | 12.5 | −11.5 | −12.5 |
|---|---|---|---|---|
| to nearest, ties to even | 12 | 12 | −12.0 | −12.0 |
| to nearest, ties away from zero | 12 | 13 | −12.0 | −13.0 |
| toward 0 | 11 | 12 | −11.0 | −12.0 |
| toward +∞ | 12 | 13 | −11.0 | −12.0 |
| toward −∞ | 11 | 12 | −12.0 | −13.0 |
总之,剩下唯一重要的问题就是:如何在 R 中实现 human_round()? 虽然大多数情况下用户不会注意到这种“奇怪”的舍入规则,但我可以想象,一旦被问到就很难解释清楚。所以我们最好有一个解决方案。下面来分享一下。
R 版本(推荐)
这里的原理是,计算机只能以有限精度表示小数,也就是说,如果两个数的差小于某个常数,我们就可以认为它们相等。通常我们选择 .Machine$double.eps^0.5 作为这个常数(为什么是这个数?我是从 dplyr::near() 那里偷来的 :P)。
human_round_r <- function(x, digits = 0) {
eps <- .Machine$double.eps^0.5
flag_pos <- !is.na(x) & (x > 0)
x[flag_pos] <- x[flag_pos] + eps
flag_neg <- !is.na(x) & (x < 0)
x[flag_neg] <- x[flag_neg] - eps
round(x, digits = digits)
}
human_round_r(c(2.5, 1.5, -1.5, -2.5, 1.5 - .Machine$double.eps^0.5))
[1] 3 2 -2 -3 2
没错,对于像 1.5 - .Machine$double.eps^0.5 这样的极端情况,它可能不会如预期那样工作,但在实际应用中遇到一个恰好等于这个值的数的概率接近于零。而且,基于上面提到的精度问题,我认为 1.5 - .Machine$double.eps^0.5 和 1.5 本质上是一样的。不过,如果你坚持要一个“完美”的版本,请使用下面的 Rcpp 版本。
Rcpp 版本
这个版本利用了 C++ 中的 std::round() 使用的正是我们熟悉的舍入规则这一特点。
Rcpp::cppFunction("
NumericVector human_round_cpp(const NumericVector x, const int digits = 0)
{
const double multiplier = std::pow(10, digits);
NumericVector y(x.size());
std::transform(x.begin(), x.end(), y.begin(),
[multiplier](const double x) {
if (!R_FINITE(x)) return x;
return std::round(x * multiplier) / multiplier;
});
return y;
}")
human_round_cpp(c(2.5, 1.5, -1.5, -2.5, 1.5 - .Machine$double.eps^0.5))
[1] 3 2 -2 -3 1