存储数字范围的最有效方法是什么?


29

这个问题是关于存储一个范围需要多少位的。换一种说法,对于给定的位数,可以存储的最大范围是多少?如何存储?

假设我们要存储一个介于0-255之间的子范围。

例如45-74。

我们可以将上面的示例存储为两个无符号字节,但是让我感到震惊的是,那里必须有一些信息冗余。我们知道第二个值大于第一个值,因此在第一个值大的情况下,第二个值需要较少的位,在第二个值大的情况下,第一个需要较少的位。

我怀疑任何压缩技术都会产生少量结果,因此最好提出一个问题:“一个字节可以存储的最大范围是多少?”。此值应大于通过分别存储两个数字可获得的值。

有没有执行这种操作的标准算法?


您还必须存储范围的开始吗?
伊万

@Ewan我不太喜欢。在上面的示例中,45是开始(最小值),74是结束(最大值),并且两者都必须存储。
rghome

2
所以一个可以存储任何范围的类型需要多少空间的问题。或可以存储45-74的类型需要多少空间?
伊万

1
虽然考虑这当然是件好事,但我希望您不要在实际应用中这样做。原因是实际应用程序的复杂性如此之大,以至于我们必须接受不到100%的优化代码。...这就是存在编译器的原因。
NoChance

3
@rghome,我同意,即使最简单的要求也会产生数百行代码。每个都容易出错。就个人而言,我会为硬件买单,而不是增加软件的复杂性。
NoChance

Answers:


58

只需计算可能的范围数。有256个范围的下限为0(0-0、0-1,... 0-254、0-255),255个范围的下限为1 ...,最后是1个范围的下限为255(255- 255)。因此总数为(256 + 255 + ... + 1)= 257 * 128 = 32,896。由于这比2 15 = 32,768 略高,因此您仍然至少需要16位(2字节)来存储此信息。

通常,对于从0到n-1的数字,可能范围的数目为n *(n + 1)/ 2。如果n为22或更小,则小于256:n = 22给出22 * 23/2 = 253种可能性。因此,一个字节足以满足0-21的子范围。

解决该问题的另一种方法是:存储一对介于0到n-1之间的整数与存储一个0-(n-1)的子范围外加一个用于确定是否为第一个数字的几乎相同。低于或高于第二个。(差异来自两个整数相等的情况,但是随着n变大,这种机会会越来越小。)这就是为什么使用这种技术只能节省大约一位的原因,这可能是很少使用它的主要原因。


谢谢。n个范围所需的位数为log(n)/ log2。将其全部输入到Wolfram Alpha中,可以得到以下与Excel兼容的公式,用于计算给定位数的子范围的最大值:= INT((SQRT(POWER(2,N + 3)+ 1)-1)/ 2 )
rghome

9
TLDR是您获得了大约一半的收益,因此通常不值得进行压缩。
rghome

是的,对于大的N来说往往会一点点,但这确实不值得麻烦。
Glorfindel

仅供参考,方程式中的N + 3看起来很奇怪,但是方程式的2的幂之一来自于二次方程式的4ac部分。
rghome

1
顺便说一句,您的计数使所有未计数组合都代表的空范围有所折扣。所以,n * (n + 1) / 2 + 1!微小的变化。
重复数据删除器

17

对于如此少量的位,如Glorfindel所指出的那样,保存许多位是不可行的。但是,如果您使用的域多了一些位,则可以通过使用起始值和增量对范围进行编码,从而在平均情况下节省大量成本。

假设域是整数,即32位。使用幼稚的方法,您需要64位(开始,结束)来存储范围。

如果我们切换到(start,delta)的编码,则可以从中构造范围的结尾。我们知道在最坏的情况下,起始值为0,增量为32位。

2 ^ 5是32,因此我们将增量的长度编码为五位(无零长度,始终加1),编码变为(开始,长度,增量)。在最坏的情况下,这需要花费32 * 2 + 5位,因此需要69位。因此,在最坏的情况下,如果所有范围都很长,则天真编码会更糟。

在最佳情况下,它的成本为32 + 5 + 1 = 38位。

这意味着,如果您必须对很多范围进行编码,而每个范围仅覆盖您网域的一小部分,那么使用这种编码方式最终平均会占用较少的空间。起始位置的分配方式无关紧要,因为起始位置将始终占用32位,但是范围长度的分配方式也很重要。如果长度越小,压缩效果越好,则覆盖整个域长度的范围越多,这种编码的效果就越差。

但是,如果您有许多范围围绕相似的起点分组(例如,因为您从传感器获取值),则可以实现更大的节省。您可以对起始值应用相同的技术,并使用偏差来抵消起始值。

假设您有10000个范围。范围围绕某个值分组。您可以使用32位对偏差进行编码。

使用幼稚的方法,您将需要32 * 2 * 10000 = 640 000位来存储所有这些范围。

对偏置进行编码需要32位,而在最佳情况下对每个范围进行编码则需要5 +1 + 5 +1 = 12位,总共120000 + 32 = 120032位。在最坏的情况下,您需要5 + 32 + 5 + 32位,即74位,总共需要740 032位。

这意味着,对于需要32位编码的域上的10000个值,我们得到

  • 最佳情况下具有智能增量编码的120032位
  • 始终使用天真的开始,结束编码的640 000位(无最佳或最差情况)
  • 740 032位,在最坏的情况下使用智能增量编码

如果您将朴素的编码作为基准,则意味着最多可节省81.25%或节省15.625%。

根据您的价值分配方式,这些节省是可观的。了解您的业务领域!知道要编码的内容。

作为扩展,您还可以更改偏差。如果您分析数据并确定值组,则可以将数据分类到存储桶中,并根据其自身的偏见分别对每个存储桶进行编码。这意味着您不仅可以将这种技术应用于围绕单个起始值分组的范围,而且还可以应用于针对多个值分组的范围。

如果您的起点分布均匀,那么这种编码实际上就不能很好地工作。

这种编码显然很难索引。您不能简单地读取第x个值。它几乎只能顺序读取。这在某些情况下是适当的,例如,通过网络或大容量存储(例如,在磁带或HDD上)进行流传输。

评估数据,将其分组并选择正确的偏差可能是一项艰巨的工作,可能需要进行一些微调才能获得最佳结果。


8

此类问题是克劳德·香农(Claude Shannon)的开创性论文《通信的数学理论》的主题,该论文引入了“位”一词,或多或少地发明了数据压缩。

一般的想法是,用于编码范围的位数与该范围出现的概率成反比。例如,假设范围45-74出现的时间约为1/4。您可能会说序列00对应于45-74。要对范围45-74进行编码,请输出“ 00”并在那里停止。

我们还假设范围99-100和140-155各自出现的时间约为1/8。您可以使用3位序列对每个编码。只要不以“ 00”开头,任何3位都将起作用,“ 00”已经为45-74范围保留。

00: 45-74
010: 99-100
101: 140-155

您可以按照这种方式继续操作,直到每个可能的范围都有编码。最小范围可能需要超过100位。但这没关系,因为它很少出现。

算法来找到最佳的编码。我不会在这里解释它们,但是您可以通过访问上面的链接或搜索“信息理论”,“ Shannon-fano编码”或“ Huffman编码”找到更多信息。

正如其他人指出的那样,最好存储起始编号以及起始编号和终止编号之间的差。您应该对起点使用一种编码,对差异采用另一种编码,因为它们具有不同的概率分布(我猜后者更加冗余)。正如polygnome所建议的,最佳算法取决于您的域。


1
是的,业务领域非常重要。我们实际上考虑过使用Huffmann编码作为开始日期的偏差,但是在对实际数据进行了一些统计分析之后,最终还是决定使用它。为偏差和增量使用相同编码的简单性比在顶部添加Huffmann更为重要,此外,您还需要发送整个Huffmann树。记住Huffmann编码是一个好主意。
Polygnome

1

要扩展@Glorfindel的答案:

由于n→∞,(n-1)→n。因此,Ω(范围)→n²/ 2和log(Ω(范围))→(2n-1)。由于天真的编码占用2n位,因此渐近最大压缩仅节省1位。


1

有一个类似的答案,但是要实现最佳压缩,您需要:

  1. 最佳熵编码方法(从算术编码中读取,并在本质上等效(压缩率相同,速度更快,但也更难掌握)ANS
  2. 有关数据分布的尽可能多的信息。至关重要的是,这不仅涉及“猜测”一个数字出现的频率,而且您经常可以肯定地排除某些可能性。例如,您可以根据定义有效间隔的方式排除负数大小的间隔(可能为0)。如果您一次有多个间隔要编码,则可以对它们进行排序,例如按宽度减小或增加开始/结束值的顺序,并排除很多值(例如,如果通过减小宽度保证顺序,则使用前一个间隔的宽度为100,下一个的起始值为47,则只需要考虑最终值的最大可能性为147。

重要的是,数字2表示您要以最有意义的值(按位编码)排在第一位的方式对事物进行编码。例如,虽然我建议对“ as-is”排序列表进行编码,但通常将其编码为“二叉树”会更聪明-即,如果它们按宽度排序,并且您有len元素,则从编码元素开始len/2。假设宽度为w。现在,您知道它前面的所有元素的宽度都在[0,w]某处,并且它后面的所有元素的宽度都在[w,您接受的最大谷值]中。递归重复(将每个一半的列表再细分为一半,依此类推),直到覆盖了所有len元素为止(除非已修复,否则将需要编码len首先,因此您无需打扰结尾标记)。如果“您接受的最大值”确实是打开的,则首先对实际出现在数据中的最大值(即最后一个元素)进行编码,然后再进行二进制分区可能是明智的。同样,最先提供信息的是每位。

另外,如果您首先对间隔的宽度进行编码,并且知道要处理的最大可能值,那么显然您可以排除所有可能导致其溢出的起始值……您就明白了。转换和排序数据的方式使您可以在解码时尽可能多地推断出其余数据,并且最佳的熵编码算法将确保您不会浪费对“已经知道”的信息进行编码的位。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.