2012-11-12 13 views
17

ho digitato questo in shell pitone:comportamento sconosciuto con carri e conversione di stringhe

>>> 0.1*0.1 
0.010000000000000002 

I prevede che 0,1 * 0,1 non è 0,01, perché so che 0.1 in base 10 è periodico in base 2.

>>> len(str(0.1*0.1)) 
4 

Mi aspettavo di ottenere 20 come ho visto 20 caratteri sopra. Perché ottengo 4?

>>> str(0.1*0.1) 
'0.01' 

Ok, questo spiega perché ho len mi dà 4, ma perché str ritorno '0.01'?

>>> repr(0.1*0.1) 
'0.010000000000000002' 

Perché str turno, ma non repr? (Ho letto this answer, ma vorrei sapere come hanno deciso quando str arrotonda un galleggiante e quando non lo fa)

>>> str(0.01) == str(0.0100000000001) 
False 
>>> str(0.01) == str(0.01000000000001) 
True 

Così sembra essere un problema con la precisione dei carri allegorici. Pensavo che Python usasse i float IEEE 754 single precicion. Così ho controllato in questo modo:

#include <stdint.h> 
#include <stdio.h> // printf 

union myUnion { 
    uint32_t i; // unsigned integer 32-bit type (on every machine) 
    float f; // a type you want to play with 
}; 

int main() { 
    union myUnion testVar; 
    testVar.f = 0.01000000000001f; 
    printf("%f\n", testVar.f); 

    testVar.f = 0.01000000000000002f; 
    printf("%f\n", testVar.f); 

    testVar.f = 0.01f*0.01f; 
    printf("%f\n", testVar.f); 
} 

ho ottenuto:

0.010000 
0.010000 
0.000100 

Python mi dà:

>>> 0.01000000000001 
0.010000000000009999 
>>> 0.01000000000000002 
0.010000000000000019 
>>> 0.01*0.01 
0.0001 

Perché Python mi danno questi risultati?

(. Io uso Python 2.6.5 Se siete a conoscenza di differenze nelle versioni di Python, vorrei anche essere interessati a loro.)

+1

non so perché gli utenti hanno cancellato le loro risposte, ma in realtà il comportamento è spiegato in [tutorial] (http://docs.python.org/2/tutorial/floatingpoint.html) – SilentGhost

+0

'float ('4.1') * 100 == 409.99999999999994' – Hugo

risposta

14

Il requisito fondamentale su repr è che dovrebbe andare su andata e ritorno; ovvero, eval(repr(f)) == f dovrebbe dare True in tutti i casi.

In Python 2.x (prima di 2.7) repr funziona eseguendo un printf con il formato %.17g e scartando gli zeri finali. Questo è garantito corretto (per i float a 64 bit) da IEEE-754. A partire da 2.7 e 3.1, Python utilizza un algoritmo più intelligente in grado di trovare rappresentazioni più brevi in ​​alcuni casi in cui lo %.17g fornisce cifre terminali non nulle e terminali non necessari. Vedi What's new in 3.1? e issue 1580.

Anche in Python 2.7, repr(0.1 * 0.1)"0.010000000000000002". Questo perché 0.1 * 0.1 == 0.01 è False sotto analisi IEEE-754 e aritmetica; cioè, il valore a virgola mobile a 64 bit più vicina a 0.1, moltiplicato per se stesso, produce un valore a virgola mobile a 64 bit che non è il valore in virgola mobile più vicino a 64 bit per 0.01:

>>> 0.1.hex() 
'0x1.999999999999ap-4' 
>>> (0.1 * 0.1).hex() 
'0x1.47ae147ae147cp-7' 
>>> 0.01.hex() 
'0x1.47ae147ae147bp-7' 
       ^1 ulp difference 

La differenza tra repr e str (pre-2.7/3.1) è quella dei formati str con 12 posizioni decimali rispetto a 17, che non è round-trippabile ma produce risultati più leggibili in molti casi.

+1

+1. E in Python> = 3.2, la differenza tra 'repr' e' str' per float è andata completamente. (E anche il tempo :-) –

0

from python tutorial:

Nelle versioni precedenti a Python 2.7 e Python 3.1, Python ha arrotondato questo valore a 17 cifre significative, dando ‘0.10000000000000001’. Nelle versioni correnti, Python visualizza un valore basato sulla frazione decimale più breve che arrotonda correttamente al valore binario vero, risultante semplicemente in ‘0.1’.

5

posso confermare il tuo comportamento

ActivePython 2.6.4.10 (ActiveState Software Inc.) based on 
Python 2.6.4 (r264:75706, Jan 22 2010, 17:24:21) [MSC v.1500 64 bit (AMD64)] on win32 
Type "help", "copyright", "credits" or "license" for more information. 
>>> repr(0.1) 
'0.10000000000000001' 
>>> repr(0.01) 
'0.01' 

Ora, la documentazione claim che in Python < 2,7

il valore di repr(1.1) è stata calcolata come format(1.1, '.17g')

Questo è una leggera semplificazione.


Si noti che questo è tutto a che fare con la stringa formattazione codice - in memoria, tutti i carri Python sono solo memorizzati come raddoppia C++, quindi non è mai andare a essere una differenza tra di loro.

Inoltre, è piuttosto spiacevole lavorare con la corda a lunghezza intera per un galleggiante anche se si sa che ce n'è uno migliore. Infatti, nei Python moderni viene utilizzato un nuovo algoritmo per la formattazione mobile, che seleziona la rappresentazione più corta in modo intelligente.


Ho passato un po 'di tempo a cercare questo nel codice sorgente, quindi includerò i dettagli qui nel caso siate interessati. Puoi saltare questa sezione.

In floatobject.c, vediamo

static PyObject * 
float_repr(PyFloatObject *v) 
{ 
    char buf[100]; 
    format_float(buf, sizeof(buf), v, PREC_REPR); 

    return PyString_FromString(buf); 
} 

che ci porta a guardare format_float. Tralasciando i/casi speciali inf Nan, che è:

format_float(char *buf, size_t buflen, PyFloatObject *v, int precision) 
{ 
    register char *cp; 
    char format[32]; 
    int i; 

    /* Subroutine for float_repr and float_print. 
     We want float numbers to be recognizable as such, 
     i.e., they should contain a decimal point or an exponent. 
     However, %g may print the number as an integer; 
     in such cases, we append ".0" to the string. */ 

    assert(PyFloat_Check(v)); 
    PyOS_snprintf(format, 32, "%%.%ig", precision); 
    PyOS_ascii_formatd(buf, buflen, format, v->ob_fval); 
    cp = buf; 
    if (*cp == '-') 
     cp++; 
    for (; *cp != '\0'; cp++) { 
     /* Any non-digit means it's not an integer; 
      this takes care of NAN and INF as well. */ 
     if (!isdigit(Py_CHARMASK(*cp))) 
      break; 
    } 
    if (*cp == '\0') { 
     *cp++ = '.'; 
     *cp++ = '0'; 
     *cp++ = '\0'; 
     return; 
    } 

    <some NaN/inf stuff> 
} 

possiamo vedere che

Quindi questo primo inizializza alcune variabili e controlla che v è un galleggiante ben formato. Si prepara quindi una stringa di formato:

PyOS_snprintf(format, 32, "%%.%ig", precision); 

Ora PREC_REPR è definito altrove in floatobject.c come 17, quindi questo calcola a "%.17g". Ora noi chiamiamo

PyOS_ascii_formatd(buf, buflen, format, v->ob_fval); 

Con la fine del tunnel in vista, guardiamo in alto PyOS_ascii_formatd e scopriamo che utilizza snprintf internamente.

+1

Concordo sul fatto che il reclamo nel documento What's New per Python sia una semplificazione del vecchio comportamento. Forse una migliore descrizione può essere trovata nella [richiesta di miglioramento] (http: //bugs.python.org/issue1580) che portano alla modifica: "L'attuale float repr() calcola sempre le prime 17 cifre della rappresentazione decimale di un oggetto flottante e le visualizza tutte (scartando gli zeri finali da zero )." –

+0

Ecco il link per [2.6.5 'PyOS_ascii_formatd'] (http://hg.python.org/cpython/file/99af4b44e7e4/Python/pystrtod.c#l375) e [' ensure_minimum_exponent_length'] (http: // hg .python.org/cpython/file/99af4b44e7e4/Python/pystrtod.C# l223), anche se non so quali piattaforme richiedano che quest'ultima sia conforme a C99. – eryksun

Problemi correlati