Наступний код очевидно помиляється. В чому проблема?
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
Наступний код очевидно помиляється. В чому проблема?
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 (стандарт, який використовують майже всі комп'ютери для представлення десяткових чисел і математики з ними), ви не завжди отримаєте те, що очікували. Це особливо вірно, оскільки деякі значення, які є простими, кінцевими десятковими знаками (наприклад, 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"
Ви можете бачити, що ці цифри різні, але представлення трохи громіздке. Якщо ми подивимось на них у двійковій формі (ну, шістнадцятковий, що еквівалентно), ми отримаємо більш чітку картину:
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
#....
#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
Додавши до коментаря Брайана (що є причиною), ви можете прийти до цього, скориставшись 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
За попередженням Джошуа тут є оновлений код (Спасибі Джошуа):
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
не повертається, FALSE
коли є відмінності, тому вам потрібно обернути це під isTRUE
час використання в if
операторі.
Це хакі, але швидко:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
all.equal(... tolerance)
параметром. all.equal(0.147, 0.15, tolerance=0.05)
правда.
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()
.
У мене була подібна проблема. Я використав наступне рішення.
@ Я знайшов цю роботу навколо рішення щодо неоднакових інтервалів скорочення. @ Я використав круглу функцію в Р. Установивши опцію на 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
Узагальнені порівняння ("<=", "> =", "=") у арифметиці з подвійним попередженням:
Порівняння 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