下面的代码显然是错误的。有什么问题?
i <- 0.1
i <- i + 0.05
i
## [1] 0.15
if(i==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
## i does not equal 0.15
最佳答案
一般(语言不可知)原因
由于并非所有数字都可以在 IEEE floating point arithmetic(几乎所有计算机用来表示十进制数并用它们进行数学运算的标准)中精确表示,因此您不会总是得到您期望的结果。尤其如此,因为一些简单的有限小数值(例如 0.1 和 0.05)在计算机中无法准确表示,因此对它们的算术结果可能不会给出与直接表示相同的结果“已知”的回答。
这是计算机算术的一个众所周知的限制,并在几个地方讨论过:
比较标量
R
中对此的标准解决方案不是使用 Is floating point math broken? ,而是使用 Canonical duplicate for "floating point is inaccurate" 函数。或者更确切地说,因为 ==
提供了很多关于差异的细节(如果有的话), all.equal
。if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15")
产量
i equals 0.15
使用
all.equal
而不是 isTRUE(all.equal(...))
的更多示例(最后一个示例应该表明这将正确显示差异)。0.1+0.05==0.15
#[1] FALSE
isTRUE(all.equal(0.1+0.05, 0.15))
#[1] TRUE
1-0.1-0.1-0.1==0.7
#[1] FALSE
isTRUE(all.equal(1-0.1-0.1-0.1, 0.7))
#[1] TRUE
0.3/0.1 == 3
#[1] FALSE
isTRUE(all.equal(0.3/0.1, 3))
#[1] TRUE
0.1+0.1==0.15
#[1] FALSE
isTRUE(all.equal(0.1+0.1, 0.15))
#[1] FALSE
更多细节,直接从
all.equal
复制:您遇到的问题是浮点数在大多数情况下无法准确表示小数,这意味着您会经常发现完全匹配失败。
而当你说的时候,R 有点说谎:
1.1-0.2
#[1] 0.9
0.9
#[1] 0.9
你可以用十进制找出它真正的想法:
sprintf("%.54f",1.1-0.2)
#[1] "0.900000000000000133226762955018784850835800170898437500"
sprintf("%.54f",0.9)
#[1] "0.900000000000000022204460492503130808472633361816406250"
您可以看到这些数字是不同的,但表示方式有点笨拙。如果我们以二进制(嗯,十六进制,等效)查看它们,我们会得到更清晰的图片:
sprintf("%a",0.9)
#[1] "0x1.ccccccccccccdp-1"
sprintf("%a",1.1-0.2)
#[1] "0x1.ccccccccccccep-1"
sprintf("%a",1.1-0.2-0.9)
#[1] "0x1p-53"
您可以看到它们的不同之处在于
==
,这很重要,因为此数字是值接近 1 的两个数字之间可表示的最小差值,就像这样。我们可以通过查看 R 的
2^-53
字段来找出任何给定计算机的最小可表示数是多少: ?.Machine
#....
#double.eps the smallest positive floating-point number x
#such that 1 + x != 1. It equals base^ulp.digits if either
#base is 2 or rounding is 0; otherwise, it is
#(base^ulp.digits) / 2. Normally 2.220446e-16.
#....
.Machine$double.eps
#[1] 2.220446e-16
sprintf("%a",.Machine$double.eps)
#[1] "0x1p-52"
您可以使用这个事实来创建一个“几乎等于”的函数,该函数检查差异是否接近浮点中的最小可表示数。事实上,这已经存在:
all.equal
。?all.equal
#....
#all.equal(x,y) is a utility to compare R objects x and y testing ‘near equality’.
#....
#all.equal(target, current,
# tolerance = .Machine$double.eps ^ 0.5,
# scale = NULL, check.attributes = TRUE, ...)
#....
所以 all.equal 函数实际上是检查数字之间的差值是两个尾数之间最小差值的平方根。
这个算法在被称为非正规数的极小数附近有点有趣,但您不必担心这一点。
比较向量
上面的讨论假设了两个单一值的比较。在 R 中,没有标量,只有向量,隐式向量化是语言的优势。对于逐个比较向量的值,前面的原则是成立的,但实现略有不同。
==
被向量化(进行元素比较),而 all.equal
将整个向量作为单个实体进行比较。使用前面的例子
a <- c(0.1+0.05, 1-0.1-0.1-0.1, 0.3/0.1, 0.1+0.1)
b <- c(0.15, 0.7, 3, 0.15)
==
没有给出“预期”的结果,all.equal
也没有按元素执行a==b
#[1] FALSE FALSE FALSE FALSE
all.equal(a,b)
#[1] "Mean relative difference: 0.01234568"
isTRUE(all.equal(a,b))
#[1] FALSE
相反,必须使用循环两个向量的版本
mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b)
#[1] TRUE TRUE TRUE FALSE
如果需要此功能的版本,则可以编写
elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})
这可以称为
elementwise.all.equal(a, b)
#[1] TRUE TRUE TRUE FALSE
或者,您可以复制
all.equal
的相关内部结构并使用隐式向量化,而不是将 all.equal.numeric
包装在更多函数调用中:tolerance = .Machine$double.eps^0.5
# this is the default tolerance used in all.equal,
# but you can pick a different tolerance to match your needs
abs(a - b) < tolerance
#[1] TRUE TRUE TRUE FALSE
这是
dplyr::near
所采用的方法,它将自身记录为This is a safe way of comparing if two vectors of floating point numbers are (pairwise) equal. This is safer than using
==
, because it has a built in tolerance
dplyr::near(a, b)
#[1] TRUE TRUE TRUE FALSE
关于r - 为什么这些数字不相等?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9508518/