Pythonでの乱数の最終桁の分布


24

Pythonで0から9までのランダムな数字を生成するには、2つの明白な方法があります。0と1の間のランダムな浮動小数点数を生成し、10を掛け、切り捨てることができます。あるいは、このrandom.randint方法を使用することもできます。

import random

def random_digit_1():
    return int(10 * random.random())

def random_digit_2():
    return random.randint(0, 9)

0から1までの乱数を生成し、最後の桁を保持した場合にどうなるかについて知りました。分布が均一であるとは必ずしも思っていませんでしたが、結果はかなり驚くべきものでした。

from random import random, seed
from collections import Counter

seed(0)
counts = Counter(int(str(random())[-1]) for _ in range(1_000_000))
print(counts)

出力:

Counter({1: 84206,
         5: 130245,
         3: 119433,
         6: 129835,
         8: 101488,
         2: 100861,
         9: 84796,
         4: 129088,
         7: 120048})

以下にヒストグラムを示します。末尾のゼロは切り捨てられるため、0は表示されないことに注意してください。しかし、数字4、5、6が他の数字よりも一般的である理由を誰かが説明できますか?Python 3.6.10を使用しましたが、Python 3.8.0a4でも結果は同じでした。

ランダムフロートの最終桁の分布


4
これは、浮動小数点数の文字列表現がPythonで計算される方法に関係しています。docs.python.org/3/tutorial/floatingpoint.htmlを参照してください。最後の桁ではなく10桁(最初の10進数の後ろ)を使用した場合、より均一な結果が得られます。
デニス

1
浮動小数点をバイナリ表現で格納します(メモリもバイナリであるため)。str問題の原因となるbase-10に変換します。たとえば、1ビットの浮動小数b0 -> 1.0b1 -> 1.5です。「最後の桁」は常に0またはになり5ます。
Mateen Ulhaq

1
random.randrange(10)私見はさらに明白です。random.randintrandom.randrange内部で呼び出す)は、randomPythonで範囲がどのように機能するかを理解していない人のために、モジュールに後で追加されました。;)
PM 2リング

2
@ PM2Ring:インターフェースが間違っているとrandrange彼らが決定した後、実際には2番目に来randintました。
user2357112はMonicaをサポートします

@ user2357112supportsモニカああ、わかりました。私は修正された立場です。randrangeが1位であると確信していましたが、私の記憶は以前ほど良くありません。;)
PM 2リング

回答:


21

それは数字の「最後の数字」ではありません。それはの最後の数字だ文字列がstrあなたを与えた数を通過したとき。

strfloat を呼び出すと、Pythonはfloat、文字列を呼び出すと元のfloatが得られるのに十分な桁数を提供します。このため、末尾の1または9は、他の数字よりも必要になる可能性が低くなります。これは、末尾の1または9が、その数字を四捨五入することによって得られる値に非常に近いことを意味するためです。他の浮動小数点数が近くない可能性は十分にありfloat(str(original_float))ます。その場合、動作を犠牲にすることなく、その桁を破棄できます。

もし str、あなたに正確に引数を表すために十分な数字を与え、最後の桁はほとんど常にときを除いて、5になりrandom.random()フロートのみを表すことができます(その場合、最後の桁は0になり戻り0.0、進有理数を、そして最後の非ゼロの小数点以下の桁の非整数の二項有理数は常に5です。)出力も次のように非常に長くなります。

>>> import decimal, random
>>> print(decimal.Decimal(random.random()))
0.29711195452007921335990658917580731213092803955078125

それが理由の1つですstr

場合strあなたは正確に17(お互いからすべてのfloat値を区別するのに十分な、しかし、必要以上に、時にはそれ以上の数字)有効桁を与え、その後の効果は、あなたがしているシーイング消えてしまいます。末尾の数字(0を含む)の分布はほぼ均一になります。

(また、str時々科学表記で文字列を返すことを忘れていましたが、それは小さな影響です。random.random()。)


5

TL; DRあなたの例は実際には最後の数字を見ていません。有限の2進数で表される仮数の10進数に変換された最後の桁は、常に0または5


見てcpython/floatobject.cください:

static PyObject *
float_repr(PyFloatObject *v)
{
    PyObject *result;
    char *buf;

    buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
                                'r', 0,
                                Py_DTSF_ADD_DOT_0,
                                NULL);

    // ...
}

そして今cpython/pystrtod.c

char * PyOS_double_to_string(double val,
                                         char format_code,
                                         int precision,
                                         int flags,
                                         int *type)
{
    char format[32];
    Py_ssize_t bufsize;
    char *buf;
    int t, exp;
    int upper = 0;

    /* Validate format_code, and map upper and lower case */
    switch (format_code) {
    // ...
    case 'r':          /* repr format */
        /* Supplied precision is unused, must be 0. */
        if (precision != 0) {
            PyErr_BadInternalCall();
            return NULL;
        }
        /* The repr() precision (17 significant decimal digits) is the
           minimal number that is guaranteed to have enough precision
           so that if the number is read back in the exact same binary
           value is recreated.  This is true for IEEE floating point
           by design, and also happens to work for all other modern
           hardware. */
        precision = 17;
        format_code = 'g';
        break;
    // ...
}

ウィキペディアこれを確認します:

53ビットの仮数精度により、有効桁数が15から17桁の精度になります(2 -53≈1.11 ×10 -16)。有効桁数が最大15桁の10進文字列をIEEE 754倍精度表現に変換し、同じ桁数の10進文字列に戻すと、最終結果は元の文字列と一致するはずです。IEEE 754倍精度数が少なくとも17桁の有効数字を持つ10進数文字列に変換され、その後倍精度表現に変換される場合、最終結果は元の数と一致する必要があります。

したがって、str(またはrepr)を使用する場合、10進数では17桁の有効数字のみを表します。つまり、浮動小数点数の一部が切り捨てられます。実際、正確な表現を得るには、53桁の有効桁数が必要です。これは次のようにして確認できます。

>>> counts = Counter(
...     len(f"{random():.99f}".lstrip("0.").rstrip("0"))
...     for _ in range(1000000)
... )
>>> counts
Counter({53: 449833,
         52: 270000,
         51: 139796,
         50: 70341,
         49: 35030,
         48: 17507,
         47: 8610,
         46: 4405,
         45: 2231,
         44: 1120,
         43: 583,
         42: 272,
         41: 155,
         40: 60,
         39: 25,
         38: 13,
         37: 6,
         36: 5,
         35: 4,
         34: 3,
         32: 1})
>>> max(counts)
53

最大精度を使用して、「最後の桁」を見つける適切な方法を次に示します。

>>> counts = Counter(
...     int(f"{random():.53f}".lstrip("0.").rstrip("0")[-1])
...     for _ in range(1000000)
... )
>>> counts
Counter({5: 1000000})

注:としては、user2357112によって指摘さを見て、正しい実装があるPyOS_double_to_stringformat_float_short、彼らはより多くの教育学面白いですので、私は、現在のものを残しておきます。


「したがって、str(またはrepr)を使用する場合、10進数では17桁の有効数字のみを表します。」-17が最大です。それが実際に固定された17桁である場合、質問の効果は表示されません。問題の影響は、丸めに丸めるだけの十分な桁数の丸めのstr(some_float)使用によるものです。
user2357112はモニカ

1
あなたはの間違った実装を見ていますPyOS_double_to_string。その実装はこれ
ます

最初のコメントについて:前述のように、浮動小数点数(編集:指数0)の正確な表現には53桁の有効数字が必要ですが、17で十分float(str(x)) == xです。ほとんどの場合、この答えは、正しい結果が5sである(そしてありそうもない0)ため、質問で行われた仮定(「正確な表現の最後の桁」)が間違っていることを示すだけでした。
Mateen Ulhaq

53桁の10進数では不十分です。さらに多くの例を示します。
user2357112はモニカ

@ user2357112supportsMonica申し訳ありませんが、私は指数0を意味していました(これは、間隔[0、1]内の均一性を保証するために必要です。)
Mateen Ulhaq
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.