Чому ці числа не рівні?


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/6874867 та stackoverflow.com/q/2769510 . R Inferno також ще один великий читати.
Аарон покинув Стек Переповнення

1
Q та відповідь на загально сайтовій мові: Чи порушена математика з плаваючою комою?
Грегор Томас

dplanet, я додав рішення для всіх випадків порівняння ("<=", "> =", "=") в арифметиці з подвійною точністю нижче. Сподіваюся, це допомагає.
Ердоган ЦЕВЕР

Відповіді:


355

Загальна (мовна агностична) причина

Оскільки не всі числа можуть бути представлені точно в арифметиці з плаваючою точкою 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

R - вільне програмне середовище для статистичних обчислень ??
kittygirl

41

Додавши до коментаря Брайана (що є причиною), ви можете прийти до цього, скориставшись 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

17
all.equalне повертається, FALSEколи є відмінності, тому вам потрібно обернути це під isTRUEчас використання в ifоператорі.
Джошуа Ульріх

12

Це хакі, але швидко:

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

У мене була подібна проблема. Я використав наступне рішення.

@ Я знайшов цю роботу навколо рішення щодо неоднакових інтервалів скорочення. @ Я використав круглу функцію в Р. Установивши опцію на 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.