[RUBY] Was ist ein Gleitkomma?

Einführung

Kürzlich (November 2018), Togetter-Zusammenfassung [Es wurde vor 30 Jahren in BASIC geschrieben, dass "Wenn Sie 0,01 10.000 Mal addieren, sind es 100,003". Als ich es in der aktuellen Umgebung versuchte, war das Ergebnis dasselbe](https //togetter.com/li/1289806) wurde ein heißes Thema.

In einer ähnlichen Geschichte führt beispielsweise das 100-fache Hinzufügen von 0,01 in Ruby (x86_64-Linux) nicht zu genau 1, und es liegt ein geringfügiger Fehler vor (siehe das folgende Beispiel). Bei anderen Programmiersprachen ist die Situation ähnlich.

Ruby(x86_64-Linux)Bei 0.Wenn 01 100 Mal hinzugefügt wird


$ irb
irb(main):001:0> 100.times.reduce(0){|s,_| s+0.01 }
=> 1.0000000000000007

In der Zusammenfassung des Falles gibt es eine Meinung, dass "in dieser Ära ...", aber ** was ist Gleitkomma ** Für diejenigen, die es wissen, auch wenn es Atari Maes Wissen ist, sonst ** Es sieht aus wie bloße Bruchdaten **, und wenn Sie nicht gut darin sind, haben Sie oft den Eindruck, dass es eine Lücke in der Berechnung gibt!

… Ursprünglich denke ich, dass Sie Kenntnisse in Einführungsbüchern erwerben sollten (da es sich um eine Art von Daten handelt, die in den meisten Sprachen Standard sind), aber gibt es eine angemessene Erklärung? Ich denke auch, also habe ich beschlossen, einen groben Artikel zu schreiben.

Was ist ein Gleitkomma?

Festpunkt und Gleitkomma

Kurz gesagt, Gleitkomma ist kein Bruchteil, den sich die durchschnittliche Person vorstellen würde, sondern ** numerische Daten, die davon ausgehen, dass bei der Berechnung Fehler auftreten **. Um es anders herum auszudrücken: ** Wenn Sie Gleitkommawerte verwenden, beachten Sie immer, dass es sich um eine ungefähre Berechnung handelt, und berücksichtigen Sie die Genauigkeit des Ergebnisses **.

Sie fragen sich vielleicht, warum dies nicht der Fall ist, aber das ist das Schicksal des ** Rechnens mit endlichen Ressourcen ** und der Punkt, an dem Kompromisse eingegangen werden müssen. Ist der Unterschied.

Das Gegenteil von Gleitkomma ist ** Fixpunkt **, was allgemein als ganzzahlige Daten bezeichnet wird. Dies kann immer in Schritten von 1 behandelt werden. Solange Sie Ganzzahlen verarbeiten, tritt kein Fehler auf, aber ** der Bereich der Zahlen, die behandelt werden können, ist nicht sehr groß **.

Gleitkomma hingegen kann auf Kosten des Fehlers einen sehr großen Bereich von Zahlen ** verarbeiten. Von Zahlen auf Nanoebene nahe 0 bis hin zu Avocadro-Konstanten (kürzlich überarbeitete Definitionen) und Zahlen auf Raumskala sind ebenfalls verfügbar.

In den meisten Programmiersprachen ist es üblich, Bruchdaten in Gleitkommazahlen zu verarbeiten.

Warum Brüche in Gleitkommazahlen?

Das heißt, einige Leute mögen das denken. "Nein, ich brauche keinen solchen Nano oder Platz. Obwohl ich höchstens mit 0,01 umgehen kann, verzeihen Sie mir bitte alle Fehler."

In der Tat ist dieses Gefühl plausibel, wenn Sie sich nur mit den Brüchen befassen, die in Währungen und Steuersätzen erscheinen.

Aber wie viele Brüche sollte es dann als Computer abdecken? Und hier solltest du schon in der Grundschule gelernt haben. Zum Beispiel ist ** das Umfangsverhältnis ein unendlicher Bruch ** (3.14 ist nur eine Annäherung). Selbst wenn Sie das Umfangsverhältnis nicht herausbringen, ist die irrationale Zahl $ \ sqrt {2} $, die Mr. Pitagoras versuchte, die Existenz zu besiegeln, ebenfalls ein unendlicher Bruchteil Es folgt immer bei der Berechnung von Brüchen. Da es nicht als endlich ausgedrückt werden kann, muss es am Ende irgendwo abgeschnitten werden, und wir müssen die Berechnungsgenauigkeit und den Fehler berücksichtigen. Wie bei sogenannten wissenschaftlichen und technologischen Berechnungen ist dieses Problem in der Statistik, Grafikverarbeitung usw. unvermeidbar.

Das ist nur meine Vermutung, aber es scheint, dass ganze Zahlen Festkomma und Brüche Gleitkomma sind.

Wenn Sie immer noch Fehler hassen

Es kann jedoch vorkommen, dass Sie nicht über Fehler nachdenken möchten. In diesem Fall sollte es eine Sprache oder Bibliothek geben, die fehlerfrei mit Dezimalstellen umgehen kann.

Im älteren und berüchtigten COBOL ermöglicht das gepackte Dezimalformat beispielsweise fehlerfreie Berechnungen mit Brüchen innerhalb der angegebenen Anzahl von Ziffern. Es wurde nur gesagt, dass es für Papierkram war. In anderen Sprachen haben Sie beispielsweise die Möglichkeit, BigDecimal für Java, Dezimal für C # und BigDecimal für Ruby (oder Rational für Ganzzahlen geteilt durch Ganzzahlen) zu verwenden.

Wenn Sie beispielsweise den Verbrauchsteuersatz mit 8% multiplizieren möchten (obwohl Gleitkommazahlen nicht viel Fehler verursachen), können Sie ihn einfach durch eine Ganzzahlarithmetik ersetzen. Wie "(Betrag) * 8/100" anstelle von "(Betrag) * 0,08". Anstatt 1,50 $ als 1,50 $ auszudrücken, können Sie alternativ eine Einheit von 150 Cent festlegen, damit sie in den Bereich der ganzen Zahlen passt.

Der Punkt ist: ** Behandle es nicht einfach als Bruch im Programm, nur weil es ein Bruch in der realen Welt ist, denke zuerst **.

Fehlermechanismus

Binäre Darstellung

Andererseits fragen Sie sich möglicherweise: "Warum tritt ein Fehler mit einer nicht vorhandenen Zahl von 0,01 auf?"

Es ist immer noch leicht zu verstehen, dass das Hinzufügen einer sehr großen und einer kleinen Zahl, wie der folgenden, nicht in die Genauigkeit passte, die ausgedrückt werden kann. (In Ruby (x86_64-Linux) wird es als Gleitkomma mit doppelter Genauigkeit behandelt, sodass die Genauigkeit etwa 16 Dezimalstellen beträgt.)

Ein Beispiel für Ruby, das nicht in die Genauigkeit passt und einen Fehler verursacht


$ irb
irb(main):001:0> 100_000_000 + 0.000_000_01 #Passt in die Genauigkeit
=> 100000000.00000001
irb(main):002:0> 1000_000_000 + 0.000_000_001 #Passt nicht
=> 1000000000.0

Das 100-fache Hinzufügen von 0,01 zu 1 bedeutet jedoch nicht, dass es einen Größenunterschied gibt ...?

In diesem Fall von "100-maliges Hinzufügen von 0,01 führt nicht zu 1" behandelt der Computer Gleitkomma intern als Binärzahlen, und ** 0,01 wird zu einem unendlichen (kreisförmigen) Bruch in Binärzahlen **. Es ist die Ursache.

Beachten Sie, dass zwischen Dezimalbrüchen und Binärbrüchen wie folgt eine Entsprechung besteht: $ \begin{eqnarray} 0.5&(10)=&0.1&(2) \\\\ 0.25&(10)=&0.01&(2) \\\\ ~ &~&\vdots&~ \\\\ 0.015625&(10)=&0.000001&(2) \\\\ 0.0078125&(10)=&0.0000001&(2) \\\\ 0.00390625&(10)=&0.00000001&(2) \\\\ 0.001953125&(10)=&0.000000001&(2) \\\\ ~ &~&\vdots&~ \\\\ \end{eqnarray} $

Dann können Sie sehen (berechnen), dass 0,01 eine binäre Zirkulationsfraktion mit einem 20-stelligen Zyklus wie folgt ist. $ 0.01(10)=0.000000101000111101011100001010001111010111\cdots(2) $ Daher werden auf dem Computer die Ziffern in der Mitte abgeschnitten und gespeichert. Diese abgeschnittene Ziffer wirkt sich als Fehler aus, wenn die Berechnung wiederholt wird.

Umgekehrt ** binäre Schärfe ** (wird zu einem endlichen Bruch), ein Bruch, dessen Nenner eine Potenz von 2 ist (zum Beispiel $ \ frac 1 {128} = 0,0078125 (10) = 0,000000001 (2) $ ) Erzeugt keinen Fehler. ** Es ist notwendig, vorsichtig damit umzugehen, unter der Annahme, dass ein Fehler vorliegt, aber es ist nicht immer der Fall, dass ein Fehler auftritt **.

Selbst wenn es als abgeschnitten bezeichnet wird, bedeutet dies nicht unbedingt, dass das Ergebnis reduziert (abgeschnitten) wird, da die Bruchverarbeitung durch "Runden" durchgeführt wird. Die Richtlinie hängt vom Verarbeitungssystem ab, z. B. Aufrunden oder Annähern (Verarbeiten wie Aufrunden).

Demonstration von Fehlern und binärer Darstellung

Jetzt. Der Gleitkomma wird heute durch den Standard IEEE754 definiert.

Die wichtigste ist die doppelte Genauigkeit von 64 Bit, aber abgesehen davon ist die einfache Genauigkeit von 32 Bit, die 4-fache Genauigkeit von 128 Bit und das heutzutage beliebte maschinelle Lernen geringer als die Genauigkeit mit einfacher Genauigkeit (stattdessen hängt der Rechenaufwand von der Hardware ab). 16-Bit-Halbpräzision wird ebenfalls verwendet.

Für die interne Darstellung wird [Grundlegendes zu Gleitkommazahlen](/ tobira-code / items / 9a000264260a7ef8ca30) detailliert beschrieben, basiert jedoch in jedem Fall auf der folgenden binären Darstellung. $ (-1)^s\cdot 1.fff\cdots ff(2)\cdot 2^{e-bias} $

Also habe ich ein C-Programm erstellt, das anhand der internen Darstellung von Gleitkommazahlen untersucht, was als Binärzahl geschieht, und zeigt, wie beim Schreiben Fehler entstehen. Diesmal mit einfacher Genauigkeit (da es schwer zu erkennen ist, ob es viele Ziffern gibt). In der Umgebung von Windows10 / WSL + Ubuntu16 + gcc (x86_64), die ich ausprobiert habe, ist der Float-Typ äquivalent.

Hier erfahren Sie, wie es kompiliert und ausgeführt wird. Wenn Sie 100 als Eingabe angeben, wird die binäre Anzeige von $ \ frac 1 {100} = 0,01 $ ausgegeben, und dann wird der Status der Pinselberechnung ausgegeben. (Auf der rechten Seite gibt es auch eine binäre Anzeige, wenn die äquivalente Multiplikation berechnet wird.) Schließlich wird das Ergebnis der Addition auch als Dezimalzahl ausgegeben.

float.c Kompilieren und ausführen


$ gcc -std=c99 -o float float.c
$ ./float <<< 100
in single precision calculation:
 1.0/100 = 0.01(10) =  1.01000111101011100001010(2) * 2^-7

continuous binary column additions

  0.000000101000111101011100001010  0.000000101000111101011100001010 (1/100=0.01)
+ 0.000000101000111101011100001010
 ---------------------------------
  0.00000101000111101011100001010   0.00000101000111101011100001010 (2/100=0.02)
+ 0.000000101000111101011100001010
 ---------------------------------
  0.00000111101011100001010001111   0.00000111101011100001010001111 (3/100=0.03)
+ 0.000000101000111101011100001010
 ---------------------------------
  0.0000101000111101011100001010    0.0000101000111101011100001010 (4/100=0.04)
+ 0.000000101000111101011100001010
 ---------------------------------
  0.0000110011001100110011001100    0.0000110011001100110011001101 (5/100=0.05)
…(Unterlassung)
  0.111110101110000100111101        0.111110101110000101001000 (98/100=0.98)
+ 0.000000101000111101011100001010
 ---------------------------------
  0.111111010111000010011001        0.111111010111000010100100 (99/100=0.99)
+ 0.000000101000111101011100001010
 ---------------------------------
  0.111111111111111111110101        1.00000000000000000000000 (100/100=1)

 1.0/100+1.0/100+...+1.0/100(100times) = 0.99999934

Bei einfacher Genauigkeit beträgt die Genauigkeit 24 Binärziffern. Daher erscheint bei jeder Wiederholung der Addition die Differenz zwischen der Gesamtzahl der gehaltenen und der Anzahl der Additionen, und der Fehler des Cut-Offs wird akkumuliert. (Sie können sehen, dass bereits zum fünften Mal ein Fehler in der letzten Ziffer vorliegt.) Wenn 0,01 100-mal mit einer einzigen Genauigkeit hinzugefügt wird, beträgt dies 0,99999934, was etwas weniger als 1 ist.

Demo-Quelle

Unten finden Sie die Demo-Quelle (C99). Ich denke, es ist auch gut, es um ideone einzufügen und auszuführen.

float.c


#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

void test(int n);

int main(void) {
    int n=0;
    int ret=scanf("%d",&n);
    if ( ret!=1||n<=0 )
        n=100;
    test(n);
}

void binform_s1(char *dst,double d);
int binform_s2(char *dst,double d);

void test(int n) {
    const float t=1.0/n;

    char buf[256];
    binform_s1(buf,t);
    printf("in single precision calculation:\n"
           " 1.0/%d = %g(10) = %s\n\n",n,t,buf);

    float s=0;
    int c1=binform_s2(buf,t);
    char line[256];
    memset(line,'-',c1);
    line[c1]='\0';

    printf("continuous binary column additions\n\n");

    for ( int i=1; i<=n; i++ ) {
        s+=t;
        if ( i>1 )
            printf("+%s\n"
                   " %s\n",buf,line);
        char buf2[256],buf3[256];
        int c2=binform_s2(buf2,s);
        (void)binform_s2(buf3,(double)i/n);
        printf(" %s%*s %s (%d/%d=%g)\n",buf2,c1-c2,"",buf3,i,n,(double)i/n);
    }
    printf("\n"
           " 1.0/%d+1.0/%d+...+1.0/%d(%dtimes) = %.8f\n",n,n,n,n,s);
}

//Unten Hilfsfunktionen
//Ganzzahl Typ für Typ Punning(Bitfolge)Umstellung auf
static inline int32_t f2i(double d) {
    // type punning
    union {
        float f;
        int32_t i;
    } u = { .f=d };
    return u.i;
}

//Einfache Genauigkeit der binären Darstellungszeichenfolge(Mantisse*2^Index)Generieren Sie a
void binform_s1(char *dst,double d) {
    int32_t x=f2i(d);
    sprintf(dst,"%c1.%23s(2) * 2^%d",
            x>>31?'-':' ',"",((x>>23)&255)-127);
    for ( int i=22; i>=0; i-- )
        dst[25-i]='0'+(x>>i&1);
}

//Einfache Genauigkeit der binären Darstellungszeichenfolge( 1.xx ... oder 0.Generiere xx…)
int binform_s2(char *dst,double d) {
    int32_t x=f2i(d);
    int r=((x>>23)&255)-127;
    // support only small floats
    assert(r<=0);
    dst[0]=x>>31?'-':' ';
    memset(dst+1,'0',1-r);
    dst[2]='.';
    dst[r<0?2-r:1]='1';
    for ( int i=22; i>=0; i-- )
        dst[25-r-i]='0'+((x>>i)&1);
    dst[26-r]='\0';
    return 26-r;
}

Zusammenfassung

Schließlich ist es eine Zusammenfassung.

Recommended Posts

Was ist ein Gleitkomma?
Was ist ein Konstruktor?
Was ist ein Stream?
Was ist ein Servlet?
Was ist eine Wrapper-Klasse?
Was ist ein boolescher Typ?
Was ist ein aussagekräftiger Kommentar?
Was ist eine JAR-Datei?
Was ist eine Java-Sammlung?
Was ist ein Lambda-Ausdruck?
Was ist Fat⁉ enum?
Was ist ein Ausschnitt in der Programmierung?
Was ist ein Boolescher Spaltentyp?
Was ist eine Referenztypvariable?
Was ist ein Lambda-Ausdruck (Java)
Was ist ein zweidimensionales Ruby-Array?
Was ist Cubby?
Was ist null? ]]
Was ist eine Spring Boot-Originaldatei?
Was ist java
Was ist Schlüsselumhang?
Was ist Maven?
Was ist Jackson?
[Für Programmieranfänger] Was ist eine Methode?
Was ist Selbst
Was ist Jenkins?
Was ist ArgumentMatcher?
Was ist IM-Jonglieren?
Was ist eine Klasse in der Java-Sprache (1 /?)
Was ist params
Was ist SLF4J?
Was ist eine Klasse in der Java-Sprache (2 /?)
Was ist Fassade? ??
Was ist Java <>?
Was ist Gradle?
Was ist POJO?
Was ist java
Was ist centOS?
Was ist RubyGem?
[Rails] Was ist ein Punkt (.) Oder ein Doppelpunkt (:)?
Was ist before_action?
Was ist Docker?
Was ist Byte?
Was ist Tomcat?
Einführung in rekursive Funktionen: Was ist eine rekursive Funktion?
Was ist Maven Assembly?
Was ist Docker-Compose?
Was ist vue cli
Was ist eine Schnittstelle?
Was ist Rubys Selbst?
Was ist harte Codierung?
Was ist Rubys attr_accessor?
Was ist Java-Kapselung?
Was ist die Erlaubnis verweigert?
Was ist Instanzsteuerung?
Was ist ein Initialisierer?
Was ist Spring Tools 4?