Bearbeiten: Also im Grunde ist das, was ich zu schreiben versuche, ein 1-Bit-Hash für double
.
Ich möchte eine double
zu true
oder false
mit einer 50/50-Chance zuordnen. Dafür habe ich Code geschrieben, der einige Zufallszahlen auswählt (nur als Beispiel möchte ich dies für Daten mit Regelmäßigkeiten verwenden und trotzdem ein 50/50-Ergebnis erhalten) , ihr letztes Bit überprüft und inkrementiert, y
ob es 1 ist oder n
ob es ist 0.
Dieser Code führt jedoch ständig zu 25% y
und 75% n
. Warum ist es nicht 50/50? Und warum so eine seltsame, aber unkomplizierte (1/3) Verteilung?
public class DoubleToBoolean {
@Test
public void test() {
int y = 0;
int n = 0;
Random r = new Random();
for (int i = 0; i < 1000000; i++) {
double randomValue = r.nextDouble();
long lastBit = Double.doubleToLongBits(randomValue) & 1;
if (lastBit == 1) {
y++;
} else {
n++;
}
}
System.out.println(y + " " + n);
}
}
Beispielausgabe:
250167 749833
java
random
double
bit-manipulation
probability
gvlasov
quelle
quelle
doubleValue % 1 > 0.5
, aber das wäre zu grobkörnig, da es in einigen Fällen sichtbare Regelmäßigkeiten einführen kann (alle Werte liegen im Bereich der Länge 1). Wenn das zu grobkörnig ist, sollten wir dann wahrscheinlich kleinere Bereiche ausprobieren, wie zdoubleValue % 1e-10 > 0.5e-10
. Nun ja. Und nur das letzte Bit als Hash von a zu nehmen,double
passiert, wenn Sie diesem Ansatz bis zum Ende mit dem geringstmöglichen Modulo folgen.(lastbit & 3) == 0
würde aber funktionieren, seltsam wie es ist.Antworten:
Weil nextDouble so funktioniert: ( Quelle )
next(x)
machtx
zufällige Bits.Warum ist das wichtig? Weil ungefähr die Hälfte der vom ersten Teil (vor der Division) erzeugten Zahlen kleiner als
1L << 52
ist und daher ihr Signifikand die 53 Bits, die er füllen könnte, nicht vollständig ausfüllt, was bedeutet, dass das niedrigstwertige Bit des Signifikanten für diese immer Null ist.Aufgrund der Aufmerksamkeit, die dies erhält, finden Sie hier eine zusätzliche Erklärung, wie ein
double
in Java (und vielen anderen Sprachen) wirklich aussieht und warum es in dieser Frage wichtig ist.Grundsätzlich
double
sieht a so aus: ( Quelle )Ein sehr wichtiges Detail, das in diesem Bild nicht sichtbar ist, ist, dass Zahlen 1 "normalisiert" werden, so dass der 53-Bit-Bruch mit einer 1 beginnt (indem der Exponent so gewählt wird, dass es so ist), dass 1 dann weggelassen wird. Aus diesem Grund zeigt das Bild 52 Bit für den Bruch (Signifikand), aber es enthält effektiv 53 Bit.
Die Normalisierung bedeutet, dass, wenn im Code für
nextDouble
das 53. Bit gesetzt ist, dieses Bit die implizite führende 1 ist und weggeht und die anderen 52 Bits buchstäblich auf den Signifikanten des Ergebnisses kopiert werdendouble
. Wenn dieses Bit jedoch nicht gesetzt ist, müssen die verbleibenden Bits nach links verschoben werden, bis es gesetzt wird.Im Durchschnitt fällt die Hälfte der generierten Zahlen in den Fall, in dem der Signifikand überhaupt nicht nach links verschoben wurde (und etwa die Hälfte davon hat eine 0 als niedrigstwertiges Bit), und die andere Hälfte ist um mindestens 1 verschoben (oder ist nur vollständig Null), so dass ihr niedrigstwertiges Bit immer 0 ist.
1: nicht immer, klar, es kann nicht für Null gemacht werden, die keine höchste 1 hat. Diese Zahlen werden als denormale oder subnormale Zahlen bezeichnet, siehe Wikipedia: denormale Zahl .
quelle
random.nextDouble()
ist normalerweise der "beste" Weg für das, wofür es gedacht ist, aber die meisten Leute versuchen nicht, einen 1-Bit-Hash aus ihrem zufälligen Double zu erzeugen. Suchen Sie eine gleichmäßige Verteilung, Resistenz gegen Kryptoanalyse oder was?next
mussint
, so dass es sowieso nur bis zu 32 Bit haben kannAus den Dokumenten :
Es heißt aber auch Folgendes (Hervorhebung von mir):
Dieser Hinweis ist mindestens seit Java 5 vorhanden (Dokumente für Java <= 1.4 befinden sich hinter einer Loginwall, zu faul zum Überprüfen). Dies ist interessant, da das Problem anscheinend auch in Java 8 noch besteht. Vielleicht wurde die "feste" Version nie getestet?
quelle
Dieses Ergebnis überrascht mich nicht, wenn man bedenkt, wie Gleitkommazahlen dargestellt werden. Nehmen wir an, wir hatten einen sehr kurzen Gleitkommatyp mit nur 4 Bit Genauigkeit. Wenn wir eine gleichmäßig verteilte Zufallszahl zwischen 0 und 1 erzeugen würden, gäbe es 16 mögliche Werte:
Wenn sie in der Maschine so aussahen, könnten Sie das Bit niedriger Ordnung testen, um eine 50/50-Verteilung zu erhalten. IEEE-Floats werden jedoch als Potenz von 2 mal einer Mantisse dargestellt; Ein Feld im Float ist die Potenz von 2 (plus einem festen Versatz). Die Potenz von 2 wird so gewählt, dass der "Mantissen" -Teil immer eine Zahl> = 1,0 und <2,0 ist. Dies bedeutet, dass die anderen Zahlen als
0.0000
die folgenden dargestellt werden:(Der
1
Wert vor dem Binärpunkt ist ein impliziter Wert. Bei 32- und 64-Bit-Floats wird tatsächlich kein Bit zugewiesen, um diesen Wert zu speichern1
.)Ein Blick auf das Obige sollte jedoch zeigen, warum Sie in 75% der Fälle Null erhalten, wenn Sie die Darstellung in Bits konvertieren und das niedrige Bit betrachten. Dies ist darauf zurückzuführen, dass alle Werte unter 0,5 (binär
0.1000
), was der Hälfte der möglichen Werte entspricht, ihre Mantissen verschoben haben und 0 im niedrigen Bit erscheinen. Die Situation ist im Wesentlichen dieselbe, wenn die Mantisse 52 Bits (ohne die implizierte 1)double
hat.(Wie @sneftel in einem Kommentar vorgeschlagen hat, könnten wir mehr als 16 mögliche Werte in die Verteilung aufnehmen, indem wir Folgendes generieren:
Aber ich bin mir nicht sicher, ob es die Art von Distribution ist, die die meisten Programmierer erwarten würden, also lohnt es sich wahrscheinlich nicht. Außerdem bringt es Ihnen nicht viel, wenn die Werte zur Erzeugung von Ganzzahlen verwendet werden, wie dies häufig bei zufälligen Gleitkommawerten der Fall ist.)
quelle