18

これをpythonシェルに入力しました:

>>> 0.1*0.1
0.010000000000000002

基数 10 の 0.1 は基数 2 では周期的であることがわかっているため、0.1*0.1 は 0.01 ではないと予想しました。

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

上記の20文字を見たので、20になると予想しました。なぜ 4 を取得するのですか?

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

わかりました、これはlen私が 4 を与える理由を説明していますが、なぜ がstr返されるの'0.01'ですか?

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

str丸くなるのに丸まらないのはなぜreprですか?(私はこの回答を読みましたがstr、フロートを丸めるときと丸めないときをどのように決定したか知りたいです)

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

そのため、フロートの精度に問題があるようです。Python は IEEE 754 単精度浮動小数点数を使用すると思いました。だから私はこのようにそれをチェックしました:

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

私が得た:

0.010000
0.010000
0.000100

Pythonは私に与えます:

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

Python でこれらの結果が得られるのはなぜですか?

(私は Python 2.6.5 を使用しています。Python のバージョンの違いを知っていれば、それらにも興味があります。)

4

3 に答える 3

16

重要な要件reprは、往復する必要があることです。つまり、すべての場合にeval(repr(f)) == f与える必要があります。True

Python 2.x (2.7 より前) ではreprprintfwith formatを実行し、%.17g末尾のゼロを破棄することで機能します。これは、IEEE-754 によって (64 ビット浮動小数点の場合) 正しいことが保証されています。%.17g2.7 と 3.1 以降、Python はよりインテリジェントなアルゴリズムを使用して、不要なゼロ以外の終端数字または終端 9 を与える場合に短い表現を見つけることができます。3.1 の新機能を参照してください。そして発行1580

Python 2.7 でさえrepr(0.1 * 0.1)"0.010000000000000002". これは、0.1 * 0.1 == 0.01IEEE False-754 の解析と演算が使用されているためです。つまり、 に最も近い 64 ビット浮動小数点値を0.1それ自体で乗算すると、 に最も近い 64 ビット浮動小数点値ではない 64 ビット浮動小数点値が得られ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

reprstr(2.7 以前str/3.1 )の違いは、小数点以下 17 桁ではなく 12 桁のフォーマットであるということです。

于 2012-11-12T15:06:24.083 に答える
5

あなたの行動を確認できます

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'

今、ドキュメントはPython <2.7でそれを主張しています

の値は次のrepr(1.1)ように計算されましたformat(1.1, '.17g')

これは少し単純化したものです。


これはすべて、文字列の書式設定コードに関係していることに注意してください。メモリ内では、すべての Python float は C++ の double として格納されるだけなので、それらの間に違いはありません。

また、より良いものがあることを知っていても、float の全長の文字列を使用するのはちょっと不快です。実際、最新の Python では、float の書式設定に新しいアルゴリズムが使用され、スマートな方法で最短の表現が選択されます。


ソースコードでこれを調べるのにしばらく時間を費やしたので、興味がある場合に備えてここに詳細を含めます. このセクションはスキップできます。

ではfloatobject.c

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

    return PyString_FromString(buf);
}

を見てみましょうformat_float。NaN/inf の特殊なケースを省略すると、次のようになります。

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

私たちはそれを見ることができます

したがって、これは最初にいくつかの変数を初期化し、それvが整形式の float であることを確認します。次に、フォーマット文字列を準備します。

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

現在、PREC_REPR は の他の場所でfloatobject.c17 として定義されているため、これは に計算され"%.17g"ます。今、私たちは呼び出します

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

トンネルの終わりが見えてきたので、見上げると、それが内部PyOS_ascii_formatdで使用されていることがわかりました。snprintf

于 2012-11-12T14:34:01.367 に答える
1
于 2012-11-12T14:29:24.403 に答える