Warum sind diese Zahlen nicht gleich?

273

Der folgende Code ist offensichtlich falsch. Was ist das Problem?

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
dplanet
quelle
7
Siehe auch stackoverflow.com/q/6874867 und stackoverflow.com/q/2769510 . Das R Inferno ist auch eine andere gute Lektüre.
Aaron verließ Stack Overflow am
1
Eine ortsweite sprachunabhängige Frage und Antwort: Ist die Gleitkomma-Mathematik gebrochen?
Gregor Thomas
dplanet, ich habe eine Lösung für alle Vergleichsfälle ("<=", "> =", "=") in doppelter Genauigkeit hinzugefügt. Ich hoffe es hilft.
Erdogan CEVHER

Antworten:

355

Allgemeiner (sprachunabhängiger) Grund

Da nicht alle Zahlen in der IEEE-Gleitkomma-Arithmetik genau dargestellt werden können (der Standard, den fast alle Computer verwenden, um Dezimalzahlen darzustellen und damit zu rechnen), erhalten Sie nicht immer das, was Sie erwartet haben. Dies gilt insbesondere, weil einige Werte, die einfache, endliche Dezimalstellen sind (wie 0,1 und 0,05), im Computer nicht genau dargestellt werden und die Ergebnisse der Arithmetik auf ihnen möglicherweise kein Ergebnis ergeben, das mit einer direkten Darstellung des " bekannte "Antwort.

Dies ist eine bekannte Einschränkung der Computerarithmetik und wird an mehreren Stellen erörtert:

Skalare vergleichen

Die Standardlösung hierfür Rist nicht die Verwendung ==, sondern die all.equalFunktion. Oder besser gesagt, da all.equales viele Details über die Unterschiede gibt, falls es welche gibt , isTRUE(all.equal(...)).

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

ergibt

i equals 0.15

Einige weitere Beispiele für die Verwendung von all.equalanstelle von ==(das letzte Beispiel soll zeigen, dass dies die Unterschiede korrekt zeigt).

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

Einige weitere Details, die direkt aus einer Antwort auf eine ähnliche Frage kopiert wurden :

Das Problem, auf das Sie gestoßen sind, ist, dass Gleitkomma in den meisten Fällen keine exakten Dezimalbrüche darstellen kann. Dies bedeutet, dass Sie häufig feststellen, dass exakte Übereinstimmungen fehlschlagen.

während R leicht liegt, wenn Sie sagen:

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

Sie können herausfinden, was es wirklich in Dezimalzahl denkt:

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

Sie können sehen, dass diese Zahlen unterschiedlich sind, aber die Darstellung ist etwas unhandlich. Wenn wir sie binär betrachten (nun, hex, was äquivalent ist), erhalten wir ein klareres Bild:

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"

Sie können sehen, dass sie sich um unterscheiden 2^-53, was wichtig ist, da diese Zahl der kleinste darstellbare Unterschied zwischen zwei Zahlen ist, deren Wert nahe bei 1 liegt.

Wir können für jeden Computer herausfinden, was diese kleinste darstellbare Zahl ist, indem wir in Rs Maschinenfeld schauen :

 ?.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"

Sie können diese Tatsache verwenden, um eine Funktion "nahezu gleich" zu erstellen, mit der überprüft wird, ob die Differenz nahe an der kleinsten darstellbaren Zahl im Gleitkomma liegt. In der Tat existiert dies bereits : 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, ...)
#....

Die Funktion all.equal überprüft also tatsächlich, ob der Unterschied zwischen den Zahlen die Quadratwurzel des kleinsten Unterschieds zwischen zwei Mantissen ist.

Dieser Algorithmus ist in der Nähe von extrem kleinen Zahlen, die Denormals genannt werden, etwas witzig, aber darüber müssen Sie sich keine Sorgen machen.

Vektoren vergleichen

Die obige Diskussion ging von einem Vergleich zweier Einzelwerte aus. In R gibt es keine Skalare, nur Vektoren und implizite Vektorisierung ist eine Stärke der Sprache. Für den elementweisen Vergleich des Werts von Vektoren gelten die vorherigen Prinzipien, die Implementierung unterscheidet sich jedoch geringfügig. ==wird vektorisiert (führt einen elementweisen Vergleich durch), während all.equaldie gesamten Vektoren als eine Einheit verglichen werden.

Verwenden Sie die vorherigen Beispiele

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)

==gibt nicht das "erwartete" Ergebnis und all.equalführt nicht elementweise durch

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

Vielmehr muss eine Version verwendet werden, die die beiden Vektoren durchläuft

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

Wenn eine funktionale Version davon gewünscht wird, kann sie geschrieben werden

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

was als gerecht bezeichnet werden kann

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

Anstatt all.equalnoch mehr Funktionsaufrufe einzuschließen, können Sie alternativ einfach die relevanten Interna replizieren all.equal.numericund die implizite Vektorisierung verwenden:

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

Dies ist der Ansatz von dplyr::near, der sich selbst als dokumentiert

Dies ist eine sichere Methode zum Vergleichen, wenn zwei Vektoren von Gleitkommazahlen (paarweise) gleich sind. Dies ist sicherer als die Verwendung ==, da eine Toleranz eingebaut ist

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE
Brian Diggs
quelle
R ist eine freie Software-Umgebung für statistische Berechnungen?
Kittygirl vor
41

Wenn Sie zu Brians Kommentar hinzufügen (was der Grund ist), können Sie dies überwinden, indem Sie all.equalstattdessen Folgendes verwenden:

# 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

Per Joshuas Warnung ist hier der aktualisierte Code (Danke 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
Tyler Rinker
quelle
17
all.equalWird nicht zurückgegeben, FALSEwenn es Unterschiede gibt. Sie müssen es daher umschließen, isTRUEwenn Sie es in einer ifAnweisung verwenden.
Joshua Ulrich
12

Das ist hackisch, aber schnell:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
Hillary Sanders
quelle
2
Sie können den all.equal(... tolerance)Parameter jedoch verwenden. all.equal(0.147, 0.15, tolerance=0.05)ist wahr.
smci
10

dplyr::near()ist eine Option zum Testen, ob zwei Vektoren von Gleitkommazahlen gleich sind. Dies ist das Beispiel aus den Dokumenten :

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

Die Funktion verfügt über einen eingebauten Toleranzparameter tol = .Machine$double.eps^0.5, der angepasst werden kann. Der Standardparameter ist der gleiche wie der Standardparameter für all.equal().

sbha
quelle
0

Ich hatte ein ähnliches Problem. Ich habe die folgende Lösung verwendet.

@ Ich fand diese Lösung um ungleiche Schnittintervalle herum. @ Ich habe die Rundungsfunktion in R verwendet. Durch Setzen der Option auf 2 Stellen wurde das Problem nicht gelöst.

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

Ausgabe ungleicher Schnittintervalle basierend auf Optionen (Ziffern = 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 ))
)

Ausgabe gleicher Schnittintervalle basierend auf der Rundungsfunktion:

      [,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
Elias EstatisticsEU
quelle
0

Verallgemeinerte Vergleiche ("<=", "> =", "=") in doppelter Präzisionsarithmetik:

Vergleichen von 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

Vergleichen von 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

Vergleich von 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
Erdogan CEVHER
quelle