下面的代码明显不对,但是到底哪里不对?
i <- 0.1
i <- i + 0.05
i## [1] 0.15if(i==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")## i does not equal 0.15
下面的代码明显不对,但是到底哪里不对?
i <- 0.1
i <- i + 0.05
i## [1] 0.15if(i==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")## i does not equal 0.15
一般情况下是编程语言问题
由于不是所有的数字都可以用 IEEE 浮点算术表示(几乎所有的计算机都使用浮点算术表示十进制数并进行数学运算) ,所以你不会总是得到想要的结果。这是绝对的,因为有些值是简单的,有限的小数(如0.1和0.05)没有在计算机中准确地表示,所以对它们的算术结果可能不会给出与“已知”答案的直接表示相同的结果。
比较标量
在R 中,标准的解决方案不是使用 ==,而是使用 all.equal 函数。 或者更确切地说,因为 all.equal 给出了很多关于差异的细节(如果有的isTRUE(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 而不是== 的例子(最后一个例子应该显示这将正确地显示差异)。
0.1+0.05==0.15#[1] FALSE
isTRUE(all.equal(0.1+0.05, 0.15))#[1] TRUE1-0.1-0.1-0.1==0.7#[1] FALSE
isTRUE(all.equal(1-0.1-0.1-0.1, 0.7))#[1] TRUE0.3/0.1 == 3#[1] FALSE
isTRUE(all.equal(0.3/0.1, 3))#[1] TRUE0.1+0.1==0.15#[1] FALSE
isTRUE(all.equal(0.1+0.1, 0.15))#[1] FALSE
您遇到的问题是,在大多数情况下,浮点数不能精确地表示十进制分数,这意味着您经常会发现精确匹配失败。
写下面代码:
1.1-0.2#[1] 0.90.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"
你可以看到它们相差 2^-53,这很重要,因为这个数是两个值接近1的数之间最小的可表示的差值,就像这样。
通过查看R的 machine 字段,我们可以找出任意给定计算机的最小可表示数是多少:
?.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)
== 不会给出 "expected" 结果,而且 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
If a functional version of this is desired, it can be written
如果需要一个函数式版本,可以这样写
elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})
elementwise.all.equal(a, b)#[1] TRUE TRUE TRUE FALSE
或者,您可以复制 all.equal.numeric 的相关内部代码,并使用隐式向量化,而不是在更多的函数调用 all.equal:
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