以下代码显然是错误的。有什么问题?
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
中对此的标准解决方案不是使用 ==
,而是使用 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] 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
直接从answer to a similar question复制的更多细节:
您遇到的问题是,在大多数情况下,浮点数不能精确地表示小数,这意味着您经常会发现精确匹配失败。
当您说:
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"
您会看到它们之间的区别在于
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)
==
不能给出“预期”的结果,并且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
采取的方法,该方法将自己记录为dplyr::near(a, b)
#[1] TRUE TRUE TRUE FALSE