その際に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)