2012-11-12 22 views
17

Tôi đã gõ này vào vỏ python:hành vi kỳ lạ với phao nổi và chuyển đổi chuỗi

>>> 0.1*0.1 
0.010000000000000002 

tôi mong đợi rằng 0,1 * 0,1 không phải là 0,01, vì tôi biết rằng 0,1 trong cơ sở 10 là kỳ tại cơ sở 2.

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

Tôi dự kiến ​​nhận được 20 như tôi đã thấy 20 ký tự ở trên. Tại sao tôi nhận được 4?

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

Ok, điều này giải thích tại sao tôi len mang lại cho tôi 4, nhưng tại sao str trở '0.01'?

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

Tại sao str tròn nhưng repr không? (Tôi đã đọc this answer, nhưng tôi muốn biết làm thế nào họ đã quyết định khi str tròn một phao và khi nó không)

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

Vì vậy, nó có vẻ là một vấn đề với tính chính xác của phao nổi. Tôi nghĩ rằng Python sẽ sử dụng IEEE 754 nổi precicion đơn. Vì vậy, tôi đã kiểm tra nó như thế này:

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

tôi nhận:

0.010000 
0.010000 
0.000100 

Python mang lại cho tôi:

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

Tại sao Python cho tôi những kết quả này?

(. Tôi sử dụng Python 2.6.5 Nếu bạn biết về sự khác biệt trong phiên bản Python, tôi cũng sẽ quan tâm đến họ.)

+1

không chắc chắn lý do người dùng xóa câu trả lời của họ, nhưng thực sự hành vi được giải thích trong [hướng dẫn] (http://docs.python.org/2/tutorial/floatingpoint.html) – SilentGhost

+0

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

Trả lời

14

Yêu cầu quan trọng trên repr là yêu cầu phải thực hiện chuyến đi khứ hồi; nghĩa là, eval(repr(f)) == f phải cung cấp cho True trong mọi trường hợp.

Trong Python 2.x (trước 2.7) repr hoạt động bằng cách thực hiện printf với định dạng %.17g và loại bỏ dấu phân cách sau. Điều này được đảm bảo chính xác (đối với các phao 64 bit) của IEEE-754. Kể từ 2.7 và 3.1, Python sử dụng thuật toán thông minh hơn có thể tìm thấy các biểu diễn ngắn hơn trong một số trường hợp trong đó %.17g cung cấp các số không cần thiết của thiết bị đầu cuối hoặc các đầu cuối. Xem What's new in 3.1?issue 1580.

Ngay cả dưới Python 2.7, repr(0.1 * 0.1) cho "0.010000000000000002". Điều này là do 0.1 * 0.1 == 0.01False theo phân tích và số học IEEE-754; có nghĩa là, 64-bit giá trị dấu chấm động gần 0.1, khi nhân với chính nó, mang lại một phiên bản 64-bit giá trị dấu chấm động đó không phải là 64-bit giá trị dấu chấm động gần 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 

Sự khác biệt giữa reprstr (trước 2,7/3,1) là str định dạng có 12 chữ số thập phân thay vì 17, không có vòng tròn nhưng tạo ra kết quả dễ đọc hơn trong nhiều trường hợp.

+1

+1. Và trong Python> = 3.2, sự khác biệt giữa 'repr' và' str' cho phao trôi hoàn toàn. (Và về thời gian, quá :-) –

0

from python tutorial:

Trong các phiên bản trước Python 2.7 và Python 3.1, Python làm tròn giá trị này thành 17 chữ số có nghĩa, cho số ‘0.10000000000000001’. Trong các phiên bản hiện tại, Python sẽ hiển thị một giá trị dựa trên phần thập phân ngắn nhất làm tròn lại chính xác giá trị nhị phân thực, kết quả chỉ đơn giản là trong ‘0.1’.

5

Tôi có thể khẳng định hành vi của bạn

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' 

Bây giờ, các tài liệu claim rằng trong Python < 2,7

giá trị của repr(1.1) được tính như format(1.1, '.17g')

Đây là một chút đơn giản.


Lưu ý rằng đây là tất cả để làm với chuỗi định dạng mã - trong bộ nhớ, tất cả nổi Python chỉ là lưu trữ như C++ tăng gấp đôi, do đó sẽ không bao giờ có một sự khác biệt giữa chúng.

Ngoài ra, thật khó chịu khi làm việc với chuỗi có độ dài đầy đủ cho một phao ngay cả khi bạn biết rằng có một cái tốt hơn. Thật vậy, trong Pythons hiện đại một thuật toán mới được sử dụng cho định dạng float, chọn biểu diễn ngắn nhất một cách thông minh.


Tôi đã dành thời gian tìm kiếm mã nguồn này, vì vậy tôi sẽ bao gồm chi tiết ở đây trong trường hợp bạn quan tâm. Bạn có thể bỏ qua phần này.

Trong floatobject.c, chúng ta thấy

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

    return PyString_FromString(buf); 
} 

đó dẫn chúng ta nhìn vào format_float. Bỏ qua NaN/inf trường hợp đặc biệt, đó là:

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

Chúng ta có thể thấy rằng

Vì vậy, đây initialises đầu tiên một số biến và kiểm tra rằng v là một phao nổi hình thành. Sau đó nó chuẩn bị một chuỗi định dạng:

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

Bây giờ PREC_REPR được định nghĩa ở đâu đó trong floatobject.c như 17, vì vậy đây sẽ tính toán để "%.17g". Bây giờ chúng ta gọi là

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

Với sự kết thúc của đường hầm trong tầm nhìn, chúng ta nhìn lên PyOS_ascii_formatd và khám phá ra rằng nó sử dụng snprintf nội bộ.

+1

Tôi đồng ý rằng khiếu nại trong tài liệu Có gì mới cho Python là đơn giản hóa hành vi cũ. Có lẽ một mô tả tốt hơn có thể được tìm thấy trong [yêu cầu nâng cao] (http: //bugs.python.org/issue1580) dẫn đến thay đổi: "float repr() luôn tính toán 17 chữ số đầu tiên của đại diện thập phân của phao, và hiển thị tất cả chúng (loại bỏ số 0)". –

+0

Đây là liên kết dành cho [2.6.5 'PyOS_ascii_formatd'] (http://hg.python.org/cpython/file/99af4b44e7e4/Python/pystrtod.c#l375) và [' ensure_minimum_exponent_length'] (http: // hg .python.org/cpython/file/99af4b44e7e4/Python/pystrtod.C# l223), mặc dù tôi không biết nền tảng nào yêu cầu nền tảng nào phù hợp với C99. – eryksun