Menu

R 语言数字:numeric、integer 与数学函数

在 R 中处理数字:numeric 与 integer 的区别、取整函数家族、sqrt 和 log、取模运算符 %%,以及特殊值 Inf 和 NaN。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

numeric 与 integer:R 的两种数字类型

R 用两种方式存储数字。numeric(以双精度浮点数存储)是默认类型:除非你另有说明,你输入的每个数字都是 double。integer 是一种单独的精确整数类型,要用 L 后缀来指定:

实际上你很少需要在意这一点:R 会悄悄地在两者之间转换,而且 is.numeric() 对两者都返回 TRUE(完整的类型系统见数据类型)。唯一能看出区别的地方是除法:/ 总是返回 double,即使是两个整数相除:

integer 主要作为计数函数(length()、seq_len()、nrow())的输出和索引值出现。需要做整数除法时,R 有一个专门的运算符,下文会讲到。

取整函数家族

R 提供了五种截取数字的方式,每种都有不同的含义:

  • round(x, digits):保留指定的小数位数(默认是 0)。
  • floor(x):向下取整到最近的整数,总是朝负无穷方向。
  • ceiling(x):向上取整,总是朝正无穷方向。
  • trunc(x):直接砍掉小数部分,总是朝零的方向。注意负数时的区别:trunc(-2.7) 是 -2,而 floor(-2.7) 是 -3。
  • signif(x, digits):保留指定的有效数字位数,而不是小数位数:signif(123456, 2) 是 120000。

还有一个著名的意外:根据 IEEE 754 标准,round() 对恰好是一半的数采用四舍六入五成双(银行家舍入):

它打印 0 2 2 4:每个一半都舍入到最近的偶数。这不是 bug,它能防止你对大量舍入后的值求和时产生系统性的向上偏差。如果报告需要课本式的四舍五入,可以加一个极小的偏移量,或者改在展示层做格式化。

常用数学函数

基础函数的用法和你猜的完全一样:

sqrt() 是平方根,abs() 是绝对值,^ 是乘方,exp(x) 是 e 的 x 次方,所以 exp(1) 就是欧拉数,约为 2.718282。

唯一容易让人栽跟头的是 log()。在 R 中,log() 是自然对数(以 e 为底),而不是以 10 为底:

第一行打印的大约是 4.60517,而不是习惯以 10 为底的人所期待的 2。要用这些底数时,请使用 log10() 和 log2(),或者显式传入 base =。(这一约定在统计学中是标准做法,自然对数就是默认的对数。)

取模 %% 与整数除法 %/%

有两个运算符负责余数运算:

%% 是取模(求余数):17 除以 5 等于 3 余 2。%/% 是整数除法:17 里能放下几个完整的 5。两者共同满足 x == (x %/% y) * y + (x %% y)。

%% 的经典用途是检查能否整除:

负数有一个细节:R 的 %% 结果的符号跟随除数(和 Python 一样,和 C 不同):

结果是 2,而不是 -1:R 回答的是“3 的某个倍数加上多少才能得到 -7?”,这样当除数为正时,结果总是在 0..2 之间。用来让索引循环回绕很方便;如果你是从 C 或 Java 转过来的,就会觉得意外。

特殊值:Inf、-Inf 与 NaN

R 的数字遵循 IEEE 754,所以有些运算会产生特殊值,而不是报错:

1/0 是 Inf(无穷大),-1/0 是 -Inf,而 0/0 这个真正无定义的量是 NaN,即“不是一个数”(not a number)。这个区别很重要:Inf 是一个答案(“比任何数都大”),NaN 则是没有答案。要用专门的函数检测它们,因为 == NaN 永远行不通:

注意最后一行:NaN 也算作 NA,所以 is.na() 能捕获它。这又是一个理由,说明 is.na() 为什么是检查“这个值能不能用?”的标准方式(详见缺失值)。

R 还原生支持读写科学计数法:2.5e3 就是 2500,非常小或非常大的数默认会以 e 记数法打印:

当报告需要普通的小数形式时,使用 format(x, scientific = FALSE)(或者 scipen 选项)。

浮点数的意外

所有以二进制存储小数的语言都有这个问题,R 也不例外:

结果是 FALSE,因为 0.1 + 0.2 实际上是 0.30000000000000004。0.1 和 0.2 都没有精确的二进制表示,微小的误差会累积起来。R 默认只显示 7 位有效数字,把这一点隐藏了起来,所以在某次 == 比较失败之前,你根本察觉不到这个问题。

规则是:永远不要用 == 比较计算出来的小数。 请使用 all.equal(),它会在合理的容差范围内进行比较:

之所以要用 isTRUE() 包起来,是因为值不相等时,all.equal() 返回的是一段描述差异的文字(而不是 FALSE)。对于需要精确的整数运算,integer 在约 21亿以内都是精确的,这也是计数代码使用整数类型的另一个原因。

要点回顾

  • 你输入的每个数字都是 double;42L 才是 integer,而且无论如何 / 都返回 double。
  • round() 采用四舍六入五成双;floor/ceiling/trunc/signif 各有不同的截取方式,要清楚你想要的是哪一个。
  • log() 是自然对数;其他底数用 log10()、log2() 或 base =。
  • %% 给出余数(符号跟随除数),%/% 给出整数商。
  • 1/0 是 Inf,0/0 是 NaN,而且 0.1 + 0.2 != 0.3:比较小数要用 all.equal(),永远不要用 ==。

下一篇:日常数据的另一半,字符串,以及 R 为构造、格式化和搜索字符串提供的函数。

常见问题

R 语言中 numeric 和 integer 有什么区别?

numeric(double,双精度)是你输入的任何数字在 R 中的默认类型:42 虽然看起来是整数,其实是 double。integer 是一种单独的存储类型,要用 L 后缀来指定:42L。普通除法总是返回 double,即使是两个整数相除;要做整数除法,请用 %/%。

R 语言中的 log() 是自然对数吗?

是的,R 中的 log(x) 是自然对数(以 e 为底),而不是以 10 为底。以 10 为底用 log10(),以 2 为底用 log2(),任意底数用 log(x, base = b)。log(100) 约为 4.605,而不是 2。

R 语言中 round() 是怎么取整的?

round(x, digits) 会四舍五入到指定的小数位数,但恰好是一半的数会采用“四舍六入五成双”(银行家舍入):round(2.5) 是 2,round(3.5) 是 4。这遵循 IEEE 754 标准,能在对舍入后的值求和时减少偏差,但会让期待课本式四舍五入的人感到意外。

R 语言中 0.1 + 0.2 为什么不等于 0.3?

double 是以二进制存储的,而 0.1、0.2 和 0.3 都没有精确的二进制表示,所以 0.1 + 0.2 实际上是 0.30000000000000004。永远不要用 == 比较计算出来的小数;请用 isTRUE(all.equal(x, y)),或者检查 abs(x - y) < 1e-9。

Coddy 编程语言插图

和 Coddy 一起学编程

开始