r - 为什么这些数字不相等?

标签 r floating-point floating-accuracy r-faq

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

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常见问题解答专门针对此问题:R FAQ 7.31
The R Inferno by Patrick Burns将第一个“圆圈”用于解决此问题(从第9页开始)
大卫·戈德堡(David Goldberg),“每位计算机科学家都应了解的浮点算法”,ACM计算调查23,1(1991-03),5-48 doi>10.1145/103162.103163revision also available
The Floating-Point Guide - What Every Programmer Should Know About Floating-Point Arithmetic
0.30000000000000004.com比较跨编程语言的浮点算法
几个堆栈溢出问题,包括


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"(有关此问题的规范答案的元讨论)



标量比较

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

关于r - 为什么这些数字不相等?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57296960/

相关文章:

r - 配置 R 控制台,使其在一个命令失败时停止执行命令列表

r - R 中使用 lm 的可变长度不同

r - 矩阵列中匹配值的计数

javascript - 优化javascript代码以使用整数运算

c++ - 将 float 组保存到二进制文件并读回时出现问题 (C++)

keras - keras Metrices 中的 binary_accuracy,将一个样本预测为正例和负例的阈值是多少

R: gridExtra - 如何将摘要绘制为表格?

c# - 字节数组到浮点转换 C#

python - float 和 decimal.Decimal 的小数位问题

math - float 学有问题吗?