下面的代码显然是错误的。有什么问题吗?

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浮点算术(几乎所有计算机都使用浮点算术来表示十进制数字并对其进行数学运算的标准)精确表示,因此您不会总是得到您所期望的结果。这一点尤其正确,因为一些简单的有限小数(如0.1和0.05)在计算机中不能准确表示,因此对它们进行算术运算的结果可能与直接表示“已知”答案的结果不相同。

这是计算机运算的一个众所周知的限制,在几个地方都有讨论:

The R FAQ has question devoted to it: R FAQ 7.31 The R Inferno by Patrick Burns devotes the first "Circle" to this problem (starting on page 9) David Goldberg, "What Every Computer Scientist Should Know About Floating-point Arithmetic," ACM Computing Surveys 23, 1 (1991-03), 5-48 doi>10.1145/103162.103163 (revision also available) The Floating-Point Guide - What Every Programmer Should Know About Floating-Point Arithmetic 0.30000000000000004.com compares floating point arithmetic across programming languages Several Stack Overflow questions including Why are floating point numbers inaccurate? Why can't decimal numbers be represented exactly in binary? Is floating point math broken? Canonical duplicate for "floating point is inaccurate" (a meta discussion about a canonical answer for this issue)

比较标量

在R中,这个问题的标准解决方案不是使用==,而是使用all。相同的功能。或者更确切地说,因为所有。equal(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

更多细节,直接从一个类似问题的答案中复制过来:

您遇到的问题是,在大多数情况下浮点数不能精确地表示小数,这意味着您将经常发现精确匹配失败。

而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"

你可以看到它们的差异是2^-53,这很重要,因为这个数字是两个值接近1的数字之间可表示的最小差异,就像这个一样。

对于任何给定的计算机,我们都可以通过查看R的机器域来找出这个最小的可表示数:

 ?.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, ...)
#....

就是这样。相等函数实际上是检查数字之间的差值是否等于两个尾数之间最小差值的平方根。

这个算法在叫做非正态数的极小的数字附近有点有趣,但你不用担心。

比较向量

上面的讨论假设比较两个单一的值。在R语言中,没有标量,只有向量,隐式向量化是该语言的一个优点。对于比较vector元素的值,前面的原则是成立的,但实现略有不同。==是向量化的(按元素进行比较),而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)

==不能给出“预期的”结果。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.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

测试向量中值的出现情况

如果应用于浮点值,%中的标准R函数%也会遇到同样的问题。例如:

x = seq(0.85, 0.95, 0.01)
# [1] 0.85 0.86 0.87 0.88 0.89 0.90 0.91 0.92 0.93 0.94 0.95
0.92 %in% x
# [1] FALSE

我们可以定义一个新的中缀操作符,以允许在比较中有一个公差,如下所示:

`%.in%` = function(a, b, eps = sqrt(.Machine$double.eps)) {
  any(abs(b-a) <= eps)
}

0.92 %.in% x
# [1] TRUE

加上Brian的评论(这就是原因),你可以通过使用all来克服这个问题。平等:

# i <- 0.1
# i <- i + 0.05
# i
#if(all.equal(i, .15)) cat("i equals 0.15\n") else cat("i does not equal 0.15\n")
#i equals 0.15

根据Joshua的警告,这里是更新的代码(谢谢Joshua):

 i <- 0.1
 i <- i + 0.05
 i
if(isTRUE(all.equal(i, .15))) { #code was getting sloppy &went to multiple lines
    cat("i equals 0.15\n") 
} else {
    cat("i does not equal 0.15\n")
}
#i equals 0.15

这有点粗俗,但很快:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")

Dplyr::near()是一个用于测试两个浮点数向量是否相等的选项。下面是文档中的例子:

sqrt(2) ^ 2 == 2
#> [1] FALSE
library(dplyr)
near(sqrt(2) ^ 2, 2)
#> [1] TRUE

该函数有一个内置的公差参数:tol = . machine $double.eps^0.5,可以调整。默认参数与all.equal()的默认参数相同。

我也遇到过类似的问题。我使用了以下解决方案。

@我发现这个工作围绕解决不平等的切割间隔。@我 通过将选项设置为2位,did使用了r中的舍入函数 没有解决问题。

options(digits = 2)
cbind(
  seq(      from = 1, to = 9, by = 1 ), 
  cut( seq( from = 1, to = 9, by = 1),          c( 0, 3, 6, 9 ) ),
  seq(      from = 0.1, to = 0.9, by = 0.1 ), 
  cut( seq( from = 0.1, to = 0.9, by = 0.1),    c( 0, 0.3, 0.6, 0.9 )),
  seq(      from = 0.01, to = 0.09, by = 0.01 ), 
  cut( seq( from = 0.01, to = 0.09, by = 0.01),    c( 0, 0.03, 0.06, 0.09 ))
)

基于选项(数字= 2)的不平等切割间隔输出:

  [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1  0.1    1 0.01    1
 [2,]    2    1  0.2    1 0.02    1
 [3,]    3    1  0.3    2 0.03    1
 [4,]    4    2  0.4    2 0.04    2
 [5,]    5    2  0.5    2 0.05    2
 [6,]    6    2  0.6    2 0.06    3
 [7,]    7    3  0.7    3 0.07    3
 [8,]    8    3  0.8    3 0.08    3
 [9,]    9    3  0.9    3 0.09    3


options(digits = 200)
cbind(
  seq(      from = 1, to = 9, by = 1 ), 
  cut( round(seq( from = 1, to = 9, by = 1), 2),          c( 0, 3, 6, 9 ) ),
  seq(      from = 0.1, to = 0.9, by = 0.1 ), 
  cut( round(seq( from = 0.1, to = 0.9, by = 0.1), 2),    c( 0, 0.3, 0.6, 0.9 )),
  seq(      from = 0.01, to = 0.09, by = 0.01 ), 
  cut( round(seq( from = 0.01, to = 0.09, by = 0.01), 2),    c( 0, 0.03, 0.06, 0.09 ))
)

基于圆函数的等切间隔输出:

      [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1  0.1    1 0.01    1
 [2,]    2    1  0.2    1 0.02    1
 [3,]    3    1  0.3    1 0.03    1
 [4,]    4    2  0.4    2 0.04    2
 [5,]    5    2  0.5    2 0.05    2
 [6,]    6    2  0.6    2 0.06    2
 [7,]    7    3  0.7    3 0.07    3
 [8,]    8    3  0.8    3 0.08    3
 [9,]    9    3  0.9    3 0.09    3

双精度算术中的广义比较("<=",">=","="):

比较a <= b:

IsSmallerOrEqual <- function(a,b) {   
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" && (a<b | all.equal(a, b))) { return(TRUE)
 } else if (a < b) { return(TRUE)
     } else { return(FALSE) }
}

IsSmallerOrEqual(abs(-2-(-2.2)), 0.2) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.3) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.1) # FALSE
IsSmallerOrEqual(3,3); IsSmallerOrEqual(3,4); IsSmallerOrEqual(4,3) 
# TRUE; TRUE; FALSE

比较>= b:

IsBiggerOrEqual <- function(a,b) {
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" && (a>b | all.equal(a, b))) { return(TRUE)
 } else if (a > b) { return(TRUE)
     } else { return(FALSE) }
}
IsBiggerOrEqual(3,3); IsBiggerOrEqual(4,3); IsBiggerOrEqual(3,4) 
# TRUE; TRUE; FALSE

比较a = b:

IsEqual <- function(a,b) {
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" ) { return(TRUE)
 } else { return(FALSE) }
}

IsEqual(0.1+0.05,0.15) # TRUE