有时在开发过程中,例如使用C代码,您可能会意外地在数组的最后一个元素之后建立索引,从而导致读取本质上是“随机”的内存块。我经常处理double
的数组,并且注意到发生这种情况时,“随机”内存产生的double
通常非常大,比1e+300
大。我不知道为什么会这样。
如果用于解释double
的64位确实是随机的,我希望double
的指数从0
均匀分布到308
(忽略指数的符号),使用科学(指数)表示法将浮点数布置在内存中的方式。当然,存储器中随机选择的位的值本身并不是随机分布的,而是与设置这些值的任何过程对应的有意义的状态。
[为了研究这种效果,我编写了以下Python 3脚本,该脚本绘制了从“随机”但未使用的内存中提取的真正随机生成的double
和double
的分布:
import random, struct
import numpy as np
import matplotlib.pyplot as plt
N = 10000
def random_floats(N=1):
return np.array(struct.unpack('d'*N, bytes(random.randrange(256) for _ in range(8*N))))
def exp_hist(a, label=None):
a = a[~np.isnan(a)]
a = a[~np.isinf(a)]
a = a[a != 0]
if len(a) == 0:
print('Zeros only!')
return
a = np.abs(np.log10(np.abs(a)))
plt.hist(a, range=(0, 350), density=True, alpha=0.8, label=label)
# Floats generated from uniformly random bits
a = random_floats(N)
exp_hist(a, 'random')
# Floats generated from memory content
a = np.empty(N)
exp_hist(a, 'memory')
plt.xlabel('exponent')
plt.legend()
plt.savefig('plot.png')
运行此脚本的典型结果如下:
真正随机生成的double
的指数实际上是均匀分布的。
根据内存内容解释的double
的指数非常小或非常大。实际上,很多未使用的内存被清零,导致很多0
值,这是有道理的。但是,正如我经常从弹跳内存访问中体验到的那样,1e+300
附近也会出现很多值。
我想解释大量的非常大的double
。
如果您想亲自尝试该脚本,请注意,可能需要多次运行它才能显示有趣的内容。可能发生的情况是,从内存内容中读取的每个数字都是0
,在这种情况下,它会告诉您。如果这种情况反复发生,请尝试降低N
(使用的double
的数量)。
您可能会在内存中找到许多不同的东西,但是其中有惊人的数目映射到非常大或非常小的浮点数,无穷大或NaN。在下文中,“ FP”表示IEEE 754 64位二进制浮点。
首先,由于已经在问题注释中进行了讨论,因此请考虑地址。 64位地址通常所有指数位为零(存储器的低端)或所有指数位为高(存储器的高端,通常是堆栈地址)。如果所有指数位都很高,则为无穷大或NaN,程序似乎会忽略它。如果所有指数位均为零,则它是次普通数或零。次普通数均小于2.3E-308,记作指数308。
现在考虑32位整数,这是另一种非常常见的数据形式。映射到有限FP的负32位二进制补码整数为-1048577或更小。像-42或-1这样的数字映射到NaN,程序会忽略它们。类似地,中值正整数的所有指数位都为零,因此映射到次正数,映射到直方图的大指数端。即使是很小的正常数也对应于令人惊讶的大整数。例如,1e-300的前32位具有整数值27,618,847。
对于指针和整数,对于具有相同值的所有指数位(全为零或全为1)存在强烈的偏见。全都是NaN或无穷大,程序不计算在内。全零是次正规的,被视为非常大的指数。