Tại sao những con số này không bằng nhau?


273

Các mã sau đây rõ ràng là sai. Có vấn đề gì vậy?

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
Xem thêm stackoverflow.com/q/6874867stackoverflow.com/q/2769510 . Các R Inferno cũng là một đọc tuyệt vời.
Aaron rời Stack Overflow

1
Một câu hỏi bất khả tri về ngôn ngữ trên toàn trang web Q và A: Toán học dấu phẩy động có bị hỏng không?
Gregor Thomas

dplanet, tôi đã thêm một giải pháp cho tất cả các trường hợp so sánh ("<=", "> =", "=") trong số học chính xác kép dưới đây. Hy vọng nó giúp.
Erdogan CEVHER

Câu trả lời:


355

Lý do chung (ngôn ngữ bất khả tri)

Vì không phải tất cả các số đều có thể được biểu diễn chính xác trong số học dấu phẩy động của IEEE (tiêu chuẩn mà hầu hết tất cả các máy tính sử dụng để biểu diễn số thập phân và làm toán với chúng), nên bạn sẽ không nhận được những gì bạn mong đợi. Điều này đặc biệt đúng bởi vì một số giá trị đơn giản, số thập phân hữu hạn (chẳng hạn như 0,1 và 0,05) không được biểu thị chính xác trong máy tính và do đó, kết quả số học trên chúng có thể không cho kết quả giống với biểu diễn trực tiếp của " biết "trả lời.

Đây là một giới hạn nổi tiếng của số học máy tính và được thảo luận ở một số nơi:

So sánh vô hướng

Giải pháp tiêu chuẩn cho vấn đề này Rkhông phải là sử dụng ==, mà là all.equalchức năng. Hay đúng hơn, vì all.equalcung cấp rất nhiều chi tiết về sự khác biệt nếu có , isTRUE(all.equal(...)).

if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15")

sản lượng

i equals 0.15

Một số ví dụ khác về việc sử dụng all.equalthay vì ==(ví dụ cuối cùng được cho là cho thấy điều này sẽ thể hiện chính xác sự khác biệt).

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

Một số chi tiết khác, được sao chép trực tiếp từ một câu trả lời cho một câu hỏi tương tự :

Vấn đề bạn gặp phải là điểm nổi không thể biểu thị chính xác các phân số thập phân trong hầu hết các trường hợp, điều đó có nghĩa là bạn sẽ thường xuyên thấy rằng các kết quả khớp chính xác thất bại.

Trong khi R nằm nhẹ khi bạn nói:

1.1-0.2
#[1] 0.9
0.9
#[1] 0.9

Bạn có thể tìm hiểu những gì nó thực sự nghĩ trong thập phân:

sprintf("%.54f",1.1-0.2)
#[1] "0.900000000000000133226762955018784850835800170898437500"
sprintf("%.54f",0.9)
#[1] "0.900000000000000022204460492503130808472633361816406250"

Bạn có thể thấy những con số này là khác nhau, nhưng đại diện là một chút khó khăn. Nếu chúng ta nhìn vào chúng ở dạng nhị phân (tốt, hex, tương đương), chúng ta sẽ có một bức tranh rõ ràng hơn:

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"

Bạn có thể thấy rằng chúng khác nhau bởi 2^-53, điều này rất quan trọng bởi vì số này là sự khác biệt nhỏ nhất có thể biểu thị giữa hai số có giá trị gần bằng 1, vì đây là.

Chúng ta có thể tìm ra cho bất kỳ máy tính cụ thể nào con số có thể biểu thị nhỏ nhất này bằng cách tìm trong trường máy của 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"

Bạn có thể sử dụng thực tế này để tạo một hàm 'gần bằng nhau' để kiểm tra xem sự khác biệt gần với số đại diện nhỏ nhất trong dấu phẩy động. Trong thực tế điều này đã tồn tại : 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, ...)
#....

Vì vậy, hàm all.equal thực sự đang kiểm tra xem sự khác biệt giữa các số là căn bậc hai của sự khác biệt nhỏ nhất giữa hai mantissas.

Thuật toán này có một chút buồn cười gần những con số cực kỳ nhỏ gọi là biến dạng, nhưng bạn không cần phải lo lắng về điều đó.

So sánh vectơ

Các cuộc thảo luận ở trên giả định so sánh hai giá trị đơn. Trong R, không có vô hướng, chỉ có vectơ và vectơ ngầm là một thế mạnh của ngôn ngữ. Để so sánh giá trị của vectơ phần tử khôn ngoan, các nguyên tắc trước giữ, nhưng việc thực hiện hơi khác nhau. ==được vector hóa (thực hiện so sánh phần tử) trong khi all.equalso sánh toàn bộ các vectơ như một thực thể duy nhất.

Sử dụng các ví dụ trước

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)

==không đưa ra kết quả "mong đợi" và all.equalkhông thực hiện yếu tố khôn ngoan

a==b
#[1] FALSE FALSE FALSE FALSE
all.equal(a,b)
#[1] "Mean relative difference: 0.01234568"
isTRUE(all.equal(a,b))
#[1] FALSE

Thay vào đó, một phiên bản vòng lặp trên hai vectơ phải được sử dụng

mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b)
#[1]  TRUE  TRUE  TRUE FALSE

Nếu một phiên bản chức năng này là mong muốn, nó có thể được viết

elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})

có thể được gọi là chỉ

elementwise.all.equal(a, b)
#[1]  TRUE  TRUE  TRUE FALSE

Ngoài ra, thay vì gói all.equalcác lệnh gọi hàm nhiều hơn, bạn chỉ có thể sao chép các phần bên trong có liên quan all.equal.numericvà sử dụng vector hóa ẩn:

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

Đây là cách tiếp cận được thực hiện bởi dplyr::near, tài liệu này là

Đây là một cách an toàn để so sánh nếu hai vectơ số dấu phẩy động (cặp) bằng nhau. Điều này an toàn hơn so với sử dụng ==, vì nó có sẵn dung sai

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE

R là một môi trường phần mềm miễn phí cho tính toán thống kê ??
kittygirl

41

Thêm vào nhận xét của Brian (đó là lý do) bạn có thể vượt qua điều này bằng cách sử dụng all.equalthay thế:

# 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

Cảnh báo của Joshua ở đây là mã được cập nhật (Cảm ơn 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.equalkhông trở lại FALSEkhi có sự khác biệt, vì vậy bạn cần phải bọc lại isTRUEkhi sử dụng nó trong một iftuyên bố.
Joshua Ulrich

12

Đây là hackish, nhưng nhanh chóng:

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

2
Nhưng bạn có thể sử dụng all.equal(... tolerance)tham số. all.equal(0.147, 0.15, tolerance=0.05)là đúng.
smci

10

dplyr::near()là một tùy chọn để kiểm tra nếu hai vectơ số dấu phẩy động bằng nhau. Đây là ví dụ từ các tài liệu :

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

Hàm có tham số dung sai tích hợp: tol = .Machine$double.eps^0.5có thể điều chỉnh. Tham số mặc định giống như mặc định cho all.equal().


0

Tôi đã có một vấn đề tương tự. Tôi đã sử dụng giải pháp sau đây.

@ Tôi tìm thấy công việc này xung quanh giải pháp về khoảng thời gian cắt không đều. @ Tôi đã sử dụng hàm tròn trong R. Bằng cách đặt tùy chọn thành 2 chữ số, không giải quyết được vấn đề.

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

đầu ra của các khoảng cắt không bằng nhau dựa trên các tùy chọn (chữ số = 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 ))
)

đầu ra của các khoảng cắt bằng nhau dựa trên hàm tròn:

      [,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

So sánh tổng quát ("<=", "> =", "=") trong số học tiền tố kép:

So sánh 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

So sánh 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

So sánh 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.