2016-04-26 发布初始版本php
2016-06-13 更新了非规则浮点数内容html
以前在写某个迭代算法的时候,发现算法在某些状况下会出错,后来调试过程当中发现,计算过程当中,某些理论上大于0的数值会在迭代过程当中变为0,最后计算过程当中出现了除0,致使结果出错。这篇文章的初始目的就是为了阐明为什么某些理论上大于0的数在实际计算中会变为0(下溢),后来顺便将不少人讨论过数据类型转换、运算精度也写进去了。在我看来这是一个很差阐述的话题,我从数字信号处理中的量化出发,试图给出一个较为直观的认识。文章可能还有一些问题,还请批评指正。算法
数字信号处理中的量化指将输入信号从一个大的集合映射到一个的小集合的过程。能够简单的、狭义的理解为将一个连续的量映射到离散的集合上的过程。以下图所示,红色曲线是输入信号,经过3比特量化获得的结果为蓝色曲线。svg
(By Hyacinth - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=30716342)测试
天然的,能够引出三个问题this
为什么要将输入信号量化编码
接收到的信号,譬如通讯过程当中的电磁波,通常将其视做模拟量(时间上连续,取值连续),为了存储、计算这些信号,须要经过采样、量化将其变为数字量。量化其实是出于两个考虑,其一是存储、其二是计算。未经量化的信号没法存储在存储器中,同时也没法进行计算。spa
量化对信号自己有何影响设计
量化过程当中,输入信号的集合每每是不可数的(或者集合有无穷元素),量化输出信号的集合是有限的。这也就意味着量化是一个不可逆的过程,这天然会对信号有影响。以下图所示,量化过程会带来量化噪声(偏差,即量化先后信号的差值),即量化后信号会有失真,没有额外先验知识的状况下,失真是没法恢复的。3d
By Gregory Maxwell - http://wiki.xiph.org/File:Dsat_011.png, CC BY 3.0, https://commons.wikimedia.org/w/index.php?curid=26171868
如何对输入信号进行量化 (注:此处仅讨论标量量化)
上面的两幅图中,量化是经过将输入范围划分为若干个区域,对于落入同一区域内的信号赋值为同一个(二进制)数。区域的划分是均匀的,这一量化关系能够表示为下图。(输入是连续值,对应输出是离散值)
不一样输入信号取值下,量化偏差是相同的,这种方式被称为均匀量化。可是不少状况下,咱们更关注相对的量化偏差(量化偏差/信号取值),即对于小信号而言,量化偏差较小,而大信号能够具备相对较大的量化偏差。这一状况下能够采用不一样的量化方式,以下图所示(注:这是我瞎编的,有对应的非均匀量化的标准)。
非均匀量化能够得到更高的信噪比,两种不一样的量化方式具备不一样的应用。除此以外还有其余量化方式,此处再也不说明。
尽管上节讨论的是数字信号处理中的量化,可是量化、或是相似量化的操做实际上出如今不少地方。譬如计算机的存储空间是有限的,32比特的存储空间仅仅只可以表示种不一样的可能。然而不少状况下,咱们所期待的运算是在实数域上进行的,而相似数字信号处理中的状况,计算机只能对量化后的信号进行存储和计算。
数据的存储,设计到数据类型,这里讨论两种:整型(integer)和浮点型 (float)。此处,相似量化的思路,咱们认为计算机将实数域上的输入,量化为整型/浮点型进行表示。
输入实数域数值,量化范围为
,量化方式为均匀量化,假设
为量化结果,则有
譬如,,则取
。对不一样的
,量化偏差是一个恒定的取值。
浮点和整型比稍微复杂一些,参考维基百科, 32比特浮点数的存储方式表示以下图。
对应浮点数取值可表示为(十进制)
大于0的浮点数依次为,然而大于1的浮点数依次为
,即量化间隔是不一样的,实际上,量化精度和数据大小的关系可表示为
即将一个实数域上的数存储为浮点表示,能够看做是一个非均匀量化的过程。
注1:本节中的量化,实际上应该是量化和编码两个过程,不只仅将数值量化了,同时采用相应的编码方式编码存储。
注2:数据类型的定义比本文描述要复杂,由于设计到0,无穷和非数的处理。
注3:不一样运行环境,对于float的定义不尽相同。
相似下面的代码在博客园讨论过不少次了,即
float a = (float) 10.375; float b = (float) 2.263; System.out.println(a+b);
这一代码在个人机器上运行结果输出为12.6380005。虽然鲜有人讨论关于(int) 10.375+ (int) 2.263 = 12这个式子,可是不管是整型仍是浮点类型,出现这个问题的原因都是同样的——咱们任意在实数域(或是有理数域)选择了两个数,然而计算机中存储的是量化以后的结果,不管对integer或是float都是这样。只是咱们习惯性的认为float强大到无所不能,可是它的表示能力依旧是有限的。
第一节中讨论了量化先后信号的变化,会带来量化噪声。同理,咱们给出的数,譬如10.375和2.263,利用浮点存储一样会带来噪声。而计算结果和理论值的差距就是这个噪声的直接体现。
咱们并不可以保证,所以计算看似出错了,实际上只是计算机按本身逻辑计算出现的偏差。
相似float→double,或是int→long此类的类型转换,或是量化区间增大了,或是量化精度提高了,转化过程不会引起任何问题,简单举例(示意图)。相似这样的量化关系,从int→long→int,或是float→double→float,转化不会进一步引入噪声。
然而以下的转化则否则,即int→float→int
int a = 200000002; float b = (float) a; int c = (int)b; System.out.println(c);
输出结果为200000000;转换过程的示意图可表示为
上溢(Arithmetic overflow),即运算结果超出了寄存器或存储空间所能存储或表示的范围。从量化的角度来看,能够认为是超过了量化范围,上溢通常很容易被发现,但有时也会被忽略。譬如,leetcode的第一题,一个有一些问题的代码也可以经过测试
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
public int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) { if (nums[j] == target - nums[i]) { return new int[] { i, j }; } } } throw new IllegalArgumentException("No two sum solution"); }
上面的代码是提供的解答方式,可是因为nums是int类型的,target也是int类型的,所以target-nums[i]可能会overflow,致使出现错误的结果。
相对而言,“下溢”就隐蔽不少了,下溢(Arithmetic underflow)很难发现,也很很差处理。这里的underflow不是指数据小于所能表示的最小值,这种状况,譬如-129再也不int8的表示范围,应该被归类到overflow,即“运算结果超出了寄存器或存储空间所能存储或表示的范围”。
浮点数设计过程当中,数据越大,量化精度越低,然而有一个例外,即0附近。32比特浮点数,和0最近的正常数为(不一样标准不一样),然而比
最接近的数为
。这意味着0附近的量化精度是相对较低的,相对较低的问题并不会带来过多的问题,可是一旦一个非0的数据因为足够小,被存储为0,则可能会带来一系列问题。所以标准中定义了Denormal number,但这依旧没法完全解决问题,只要一个数足够小,就会被下溢为0,而在迭代算法中,这种状况颇有可能会发生。若是不幸这个数被做为了除数,那么就会出现除0的状况,这可能致使错误 。譬如
public static void main(String[] args) { float a = Float.MIN_VALUE; float b = Float.MIN_VALUE/2; System.out.println(b>0); System.out.println(a/b); System.out.println(b/b); }
这段代码的运行结果为
false
Infinity
NaN
NaN,即“非数”和任意数值计算结果均为NaN,这是在计算过程当中不指望发生的。上面这段代码中下溢很明显,可是在不少迭代算法中,却很难判断下溢的产生,此时咱们须要根据状况采用不一样的处理方式防止下溢致使的错误,这再也不本文的讨论范围内。
更多的关于normal number和Denormal number的讨论可参见非规则浮点数和规则浮点数。
值得关注的问题是,谈论了那么多关于量化噪声的问题,那么,计算机的计算结果还靠谱吗?即
计算机的计算结果是否能保证绝对的准确性?
在必定条件下是能够的。回到第一节、第二节会发现讨论的前提是
数字信号处理领域,接收信号是模拟的,须要经过ADC采样量化,这时量化噪声是必然存在的。然而,计算机中的数据可能具备不一样的含义。譬如,采用变量a表示书本的页数。那么,书本的页数必然是一个不小于0的整数,且通常而言会是有限的。那么,此时若a采用整型存储,就可以精确的表示书本的页数,不引入任何偏差。
由此出发,对于不一样的应用,若是有一些先验知识,咱们有可能能够设计不一样的数据类型/结构,以及相应的计算方法,获得准确的计算结果。