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
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
Câu trả lời:
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:
Giải pháp tiêu chuẩn cho vấn đề này R
không phải là sử dụng ==
, mà là all.equal
chức năng. Hay đúng hơn, vì all.equal
cung 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.equal
thay 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 đó.
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.equal
so 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.equal
khô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.equal
cá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.numeric
và 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
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.equal
thay 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
all.equal
không trở lại FALSE
khi có sự khác biệt, vì vậy bạn cần phải bọc lại isTRUE
khi sử dụng nó trong một if
tuyên bố.
Đâ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")
all.equal(... tolerance)
tham số. all.equal(0.147, 0.15, tolerance=0.05)
là đúng.
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.5
có thể điều chỉnh. Tham số mặc định giống như mặc định cho all.equal()
.
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
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