이 숫자가 왜 다른가요?


273

다음 코드는 분명히 잘못되었습니다. 뭐가 문제 야?

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

7
stackoverflow.com/q/6874867stackoverflow.com/q/2769510 도 참조하십시오 . R 지옥은 또 다른 좋은 읽기입니다.
Aaron은 Stack Overflow를

1
사이트 전체 언어에 구애받지 않는 Q 및 A : 부동 소수점 수학이 손상 되었습니까?
Gregor Thomas

dplanet, 아래의 배정 밀도 산술로 모든 비교 사례 ( "<=", "> =", "=")에 대한 솔루션을 추가했습니다. 도움이 되길 바랍니다.
Erdogan CEVHER

답변:


355

일반적인 (언어에 구애받지 않는) 이유

모든 숫자를 IEEE 부동 소수점 산술로 정확하게 표현할 수있는 것은 아니기 때문에 (거의 모든 컴퓨터가 10 진수를 나타내고 수학으로 수행하는 표준)으로 것은 아니므로 항상 예상 한 값을 얻지는 못할 수 있습니다. 이것은 단순하고 유한 한 소수 (예 : 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

더 자세한 내용 은 비슷한 질문에 대한 답변 에서 직접 복사되었습니다 .

발생하는 문제는 대부분의 경우 부동 소수점이 소수점 이하 자릿수를 정확하게 표현할 수 없다는 것입니다. 즉, 정확한 일치가 실패하는 경우가 많습니다.

당신이 말할 때 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"

이 숫자가 다르다는 것을 알 수 있지만 표현은 다루기 힘듭니다. 우리가 그것들을 이진법으로 보면 (음, 16 진법),보다 명확한 그림을 얻을 수 있습니다 :

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 field 에서이 가장 작은 숫자가 무엇인지 알아낼 수 있습니다 :

 ?.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 함수는 실제로 숫자 사이의 차이가 두 가수 사이의 가장 작은 차이의 제곱근인지 확인합니다.

이 알고리즘은 비정규 (denormals)라고하는 극소수 근처에서는 약간 재미 있지만 걱정할 필요는 없습니다.

벡터 비교

위의 논의는 두 개의 단일 값의 비교를 가정했습니다. 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은 통계 컴퓨팅을위한 자유 소프트웨어 환경입니다 ??
kittygirl

41

Brian의 의견에 추가하면 (이유가 있음) all.equal대신 다음 을 사용하여이 문제를 극복 할 수 있습니다 .

# 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

17
all.equalFALSE차이점이있을 때는 반환되지 않으므로 명령문 isTRUE에서 사용할 때 랩핑해야 if합니다.
Joshua Ulrich

12

이것은 hackish이지만 빠릅니다.

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

2
그러나 all.equal(... tolerance)매개 변수를 사용할 수 있습니다 . all.equal(0.147, 0.15, tolerance=0.05)사실이다.
smci

10

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().


0

나는 비슷한 문제가 있었다. 나는 다음 해결책을 사용했다.

@ 불균일 한 컷 간격에 대한 해결책을 찾았습니다. @ R에서 반올림 기능을 사용했습니다. 옵션을 2 자리로 설정해도 문제가 해결되지 않았습니다.

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

0

이중 전치 산술의 일반화 된 비교 ( "<=", "> =", "=") :

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

a> = 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.