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
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
Antworten:
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:
Die Standardlösung hierfür R
ist nicht die Verwendung ==
, sondern die all.equal
Funktion. Oder besser gesagt, da all.equal
es 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.equal
anstelle 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.
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.equal
die 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.equal
fü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.equal
noch mehr Funktionsaufrufe einzuschließen, können Sie alternativ einfach die relevanten Interna replizieren all.equal.numeric
und 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
Wenn Sie zu Brians Kommentar hinzufügen (was der Grund ist), können Sie dies überwinden, indem Sie all.equal
stattdessen 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
all.equal
Wird nicht zurückgegeben, FALSE
wenn es Unterschiede gibt. Sie müssen es daher umschließen, isTRUE
wenn Sie es in einer if
Anweisung verwenden.
Das ist hackisch, aber schnell:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
all.equal(... tolerance)
Parameter jedoch verwenden. all.equal(0.147, 0.15, tolerance=0.05)
ist wahr.
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()
.
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
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