下面的代码显然是错误的。有什么问题吗
i<;-0.1
我<;-i+0.05
我
## [1] 0.15
如果(i==0.15)cat(“i=0.15”),否则cat(“i不等于0.15”)
##i不等于0.15
一般(语言不可知)原因
由于并不是所有的数字都可以用IEEE浮点算法(几乎所有计算机都使用该标准来表示十进制数字并对其进行运算)精确表示,因此您不会总是得到您所期望的结果。这一点尤其正确,因为一些简单、有限小数(如0.1和0.05)的值在计算机中没有精确表示,因此对它们的算术结果可能不会给出与“已知”答案的直接表示相同的结果
这是计算机算法的一个众所周知的局限性,在几个地方进行了讨论:
- R FAQ有专门的问题:R FAQ 7.31
- Patrick Burns的《R地狱》将第一个“圆圈”用于解决这个问题(从第9页开始)
- David Goldberg,“每一位计算机科学家都应该知道的浮点运算”,ACM计算调查23,1(1991-03),5-48 doi>10.1145/103162.103163(也可提供修订版)
- 浮点指南-每个程序员都应该知道的关于浮点运算的知识
- 0.300000000004.com比较不同编程语言的浮点运算
- 几个堆栈溢出问题,包括
- 为什么浮点数不准确
- 为什么可以';十进制数不能用二进制精确表示吗
- 浮点数学坏了吗
- “标准副本”;浮点不准确;(关于这个问题的标准答案的元讨论)
比较标量
R中的标准解决方案不是使用=,而是使用all.equal函数。或者更确切地说,由于all.equal提供了大量关于差异的详细信息(如果有),因此isTRUE(all.equal(…))
if(isTRUE(all.equal(i,0.15)))cat(“i等于0.15”)else cat(“i不等于0.15”)
屈服
i等于0.15
更多使用all.equal而不是=的示例(最后一个示例应该表明这将正确显示差异)
0.1+0.05==0.15
#[1] 假的
isTRUE(全部相等(0.1+0.05,0.15))
#[1] 真的
1-0.1-0.1-0.1==0.7
#[1] 假的
isTRUE(全部相等(1-0.1-0.1-0.1,0.7))
#[1] 真的
0.3/0.1 == 3
#[1] 假的
isTRUE(全部相等(0.3/0.1,3))
#[1] 真的
0.1+0.1==0.15
#[1] 假的
isTRUE(全部相等(0.1+0.1,0.15))
#[1] 假的
更多细节,直接从一个类似问题的答案中复制:
您遇到的问题是,在大多数情况下,浮点不能精确地表示小数,这意味着您经常会发现精确匹配失败
而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.CCCC DP-1”
sprintf(“%a”,1.1-0.2)
#[1] “0x1.CCCC EP-1”
sprintf(“%a”,1.1-0.2-0.9)
#[1] “0x1p-53”
您可以看到它们之间的差异是2^-53,这一点很重要,因为这个数字是两个值接近1的数字之间的最小可表示差异,如下所示
通过查看R的机器字段,我们可以找出任何给定计算机的最小可表示数:
?。机器
#....
#double.eps最小正浮点数x
#这样1+x!=1.它等于基^ulp.digits,如果
#基数为2或四舍五入为0;否则就是
#(基本^ulp.位)/2。通常为2.220446e-16。
#....
.Machine$double.eps
#[1] 2.220446e-16
sprintf(“%a”,.Machine$double.eps)
#[1] “0x1p-52”
您可以使用这个事实创建一个“几乎相等”函数,该函数检查差值是否接近浮点中可表示的最小值。事实上,这已经存在:all.equal
?全部相等
#....
#all.equal(x,y)是一个用于比较R对象x和y的实用程序,用于测试“近似相等”。
#....
#全部相等(目标、当前、,
#公差=.Machine$double.eps^0.5,
#比例=空,检查。属性=真,…)
#....
因此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] 假假假假
全部相等(a,b)
#[1] “平均相对差异:0.01234568”
isTRUE(全部相等(a,b))
#[1] 假的
相反,必须使用在两个向量上循环的版本
mapply(函数(x,y){isTRUE(all.equal(x,y))},a,b)
#[1] 真假
如果需要功能版本,可以编写
elementwise.all.equal<;-向量化(函数(x,y){isTRUE(all.equal(x,y))})
这可以称为公正
elementwise.all.equal(a,b)
#[1] 真假
或者,不必在更多函数调用中包装all.equal,您只需复制all.equal.numeric的相关内部结构并使用隐式矢量化:
公差=.Machine$double.eps^0.5
#这是all.equal中使用的默认公差,
#但您可以选择不同的公差来满足您的需要
abs(a-b)<;容忍
#[1] 真假
这是dplyr::near所采用的方法,它将自己记录为
如果两个浮点数向量(成对)相等,这是一种安全的比较方法。这比使用
==更安全,因为它具有内置公差
dplyr::near(a,b)
#[1] 真假