2012-11-12 19 views
17

mam wpisane to w python shell:Dziwne zachowanie z pływaków i konwersji ciąg

>>> 0.1*0.1 
0.010000000000000002 

Spodziewałem się, że 0,1 * 0,1 nie jest 0,01, bo wiem, że 0,1 w podstawy 10 jest okresowa w bazie 2.

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

Spodziewałem się uzyskać 20, ponieważ widziałem 20 znaków powyżej. Dlaczego otrzymuję 4?

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

Ok, to wyjaśnia dlaczego len daje mi 4, ale dlaczego str powrót '0.01'?

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

Dlaczego str rundę, ale nie repr? (Czytałem this answer, ale chciałbym wiedzieć, jak oni zdecydowali gdy str zaokrągla pływaka, a kiedy nie)

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

Więc wydaje się być problem z dokładnością pływaków. Myślałem, że Python użyje pojedynczych prefiksów IEEE 754. Więc sprawdziłem to tak:

#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); 
} 

mam:

0.010000 
0.010000 
0.000100 

Python daje mi:

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

Dlaczego Python dać mi te wyniki?

(. Używam Python 2.6.5 Jeśli znasz różnice w wersjach Pythona, chciałbym być także zainteresowany w nich).

+1

Nie wiem, dlaczego użytkownicy usunęli swoje odpowiedzi, ale zachowanie jest wyjaśnione w [tutorialu] (http://docs.python.org/2/tutorial/floatingpoint.html) – SilentGhost

+0

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

Odpowiedz

14

Najważniejszym wymogiem na repr jest to, że powinien on odbywać się w obie strony; to znaczy, eval(repr(f)) == f powinien podawać True we wszystkich przypadkach.

W języku Python 2.x (przed 2.7) repr działa poprzez wykonanie printf z formatem %.17g i odrzuca końcowe zera. Gwarantuje to poprawność (w przypadku przepływności 64-bitowych) przez IEEE-754. Od wersji 2.7 i 3.1, Python używa bardziej inteligentnego algorytmu, który może znaleźć krótsze reprezentacje w niektórych przypadkach, gdzie %.17g podaje niepotrzebne niezerowe cyfry końcowe lub dziewiątki terminala. Zobacz What's new in 3.1? i issue 1580.

Nawet w Pythonie 2.7, repr(0.1 * 0.1) podaje "0.010000000000000002". Dzieje się tak, ponieważ 0.1 * 0.1 == 0.01 jest False w IEEE-754 parsowania i arytmetyczne; to znaczy najbliższa 64-bitowa wartość zmiennoprzecinkową 0.1, pomnożona przez siebie, uzyskuje się 64-bitową wartość zmiennoprzecinkową, która nie jest najbliższa 64-bitowa wartość zmiennoprzecinkową 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 

Różnica między repr i str (przed-2.7/3.1) to formaty str z 12 miejscami po przecinku w przeciwieństwie do 17, które nie są okrągłe, ale w wielu przypadkach dają bardziej czytelne wyniki.

+1

+1. W Pythonie> = 3,2 różnica między 'repr' a' str' dla floats jest znikoma. (A także o czasie :-) –

0

from python tutorial:

W wersjach przed Pythonie 2.7 i Python 3.1, Python zaokrąglał tę wartość do 17 cyfr znaczących, podając ‘0.10000000000000001’. W aktualnych wersjach Python wyświetla wartość opartą na najkrótszym ułamku dziesiętnym, który wraca poprawnie do prawdziwej wartości binarnej, w wyniku czego po prostu w ‘0.1’.

5

mogę potwierdzić swoje zachowanie

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' 

Teraz docs claim że w Pythonie < 2,7

wartość repr(1.1) został obliczony jako format(1.1, '.17g')

Jest niewielkie uproszczenie.


Należy zauważyć, że w tym wszystkim do czynienia z ciągiem formatowania kodu - w pamięci, wszystkie pływaki Pythona są po prostu przechowywane jako podwaja C++, więc nigdy nie będzie różnicy między nimi.

Ponadto, nieprzyjemne jest pracować z ciągiem o pełnej długości, nawet jeśli wiesz, że jest lepszy. Rzeczywiście, w nowoczesnych Pythonach zastosowano nowy algorytm do formatowania float, który wybiera najkrótszą reprezentację w inteligentny sposób.


spędziłem patrząc to w kodzie źródłowym, więc będę zawierać szczegóły tutaj w przypadku jesteś zainteresowany. Możesz pominąć tę sekcję.

W floatobject.c widzimy

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

    return PyString_FromString(buf); 
} 

, która prowadzi nas do spojrzenia na format_float. Pomijając Nan/Inf szczególne przypadki, to jest:

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

Widzimy, że

więc to pierwsze Zainicjowanie niektórych zmiennych i sprawdza, że ​​v jest dobrze uformowane pływak. Następnie przygotowuje ciąg formatu:

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

Teraz PREC_REPR jest zdefiniowana w innym miejscu floatobject.c jako 17, więc ten oblicza się "%.17g". Teraz nazywamy

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

Przy końcu tunelu widać, patrzymy w górę PyOS_ascii_formatd i odkryć, że używa snprintf wewnętrznie.

+1

Zgadzam się, że roszczenie w dokumencie What's New dla Pythona jest uproszczeniem starego zachowania. Być może lepszy opis można znaleźć w [prośbie o ulepszenie] (http: //bugs.python.org/issue1580), które doprowadziły do ​​zmiany: "Bieżące float repr() zawsze oblicza 17 pierwszych cyfr reprezentacji dziesiętnej float i wyświetla je wszystkie (odrzucając zer końcowych)." –

+0

Oto link do [2.6.5 'PyOS_ascii_formatd'] (http://hg.python.org/cpython/file/99af4b44e7e4/Python/pystrtod.c#l375) i [' ensure_minimum_exponent_length'] (http: // hg .python.org/cpython/file/99af4b44e7e4/Python/pystrtod.C# l223), chociaż nie wiem, które platformy wymagają zgodności z C99. – eryksun