てけとーぶろぐ。

ソフトウェアの開発と、お絵かきと、雑記と。

Pythonで.NET FrameworkのSystem.Randomと同じ乱数生成

C#で書いたとあるプログラムをPythonに移植した。

その際にC#の、というか.NET FrameworkのSystem.Randomを使っているところについても C#Pythonで、同じシードに対しては同じ乱数列が生成される必要があった。

System.Randomの実装って分かるのか…? と思っていたら、.NET Frameworkってソースコード公開されているんですね。

https://referencesource.microsoft.com/#mscorlib/system/random.cs にコードがあったので必要な部分だけPythonにしました。

# https://referencesource.microsoft.com/#mscorlib/system/random.cs
# からの移植
class DotnetRandom:
    INT32_MAX_VALUE = 2147483647
    INT32_MIN_VALUE = -2147483648

    def __init__(self, seed):
        self.MBIG = self.INT32_MAX_VALUE
        self.MSEED = 161803398
        self.MZ = 0

        self.inext = 0
        self.inextp = 0
        self.seed_array = [0 for i in range(56)]

        ii = 0
        mj = 0
        mk = 0

        # Initialize our Seed array.
        # This algorithm comes from Numerical Recipes in C (2nd Ed.)
        if seed == self.INT32_MIN_VALUE:
            subtraction = self.INT32_MAX_VALUE
        else:
            subtraction = abs(seed)
        mj = self.MSEED - subtraction
        self.seed_array[55] = mj
        mk = 1
        # Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position.
        for i in range(1, 55):
            ii = (21 * i) % 55
            self.seed_array[ii] = mk
            mk = mj - mk
            if mk < 0:
                mk += self.MBIG
            mj = self.seed_array[ii]
        for k in range(1, 5):
            for i in range(1, 56):
                self.seed_array[i] -= self.seed_array[1 + (i + 30) % 55]
                if self.seed_array[i] < 0:
                    self.seed_array[i] += self.MBIG

        self.inext = 0
        self.inextp = 21
        seed = 1

    def sample(self):
        # Including this division at the end gives us significantly improved
        # random number distribution.
        return self.internal_sample() * (1.0 / self.MBIG)

    def internal_sample(self):
        ret_val = 0
        loc_i_next = self.inext
        loc_i_nextp = self.inextp

        loc_i_next += 1
        if loc_i_next >= 56:
            loc_i_next = 1
        loc_i_nextp += 1
        if loc_i_nextp >= 56:
            loc_i_nextp = 1

        ret_val = self.seed_array[loc_i_next] - self.seed_array[loc_i_nextp]

        if ret_val == self.MBIG:
            ret_val -= 1
        if ret_val < 0:
            ret_val += self.MBIG

        self.seed_array[loc_i_next] = ret_val

        self.inext = loc_i_next
        self.inextp = loc_i_nextp

        return ret_val

    def next(self, max_value):
        if max_value < 0:
            raise ValueError("max_value")

        return int(self.sample() * max_value)