浮点精度是可变的还是不变的?

我不断得到浮点数( floatdouble floatlong double float )是否只有一个精度值,或精度值是否可变的混合答案。

浮点与双精度的一个主题似乎意味着浮点精度是绝对的。

然而,另一个名为float和double的区别说,

一般来说,double有15到16个十进制数字的精度

另有消息称,

floattypes的variables通常具有 7位有效数字的精度

doubletypes的variables通常具有大约 16位有效数字的精度

如果我正在处理敏感的代码,那么当我的值不准确时,我不喜欢引用上述近似值。 所以让我们直接logging下来。 浮点精度是可变的还是不变的,为什么?

精度是固定的,对于双精度来说正好是53个二进制数字 (如果我们排除了隐含的前导1,则为52)。 这出来大约十五位十进制数字


OP要求我详细说明为什么只有53个二进制数字意味着“大约”15个十进制数字。

为了直观地理解这一点,让我们考虑一个不太精确的浮点格式:而不是像双精度数字那样的52位尾数,我们将使用4位尾数。

因此,每个数字将如下所示:(-1) s ×2 yyy ×1.xxxx(其中s是符号位, yyy是指数, 1.xxxx是标准化尾数)。 对于直接讨论,我们只关注尾数而不是符号或指数。

下面是所有xxxx1.xxxx样子(所有四舍五入都是半到平的,就像默认的浮点舍入模式一样):

  xxxx | 1.xxxx | value | 2dd | 3dd --------+----------+----------+-------+-------- 0000 | 1.0000 | 1.0 | 1.0 | 1.00 0001 | 1.0001 | 1.0625 | 1.1 | 1.06 0010 | 1.0010 | 1.125 | 1.1 | 1.12 0011 | 1.0011 | 1.1875 | 1.2 | 1.19 0100 | 1.0100 | 1.25 | 1.2 | 1.25 0101 | 1.0101 | 1.3125 | 1.3 | 1.31 0110 | 1.0110 | 1.375 | 1.4 | 1.38 0111 | 1.0111 | 1.4375 | 1.4 | 1.44 1000 | 1.1000 | 1.5 | 1.5 | 1.50 1001 | 1.1001 | 1.5625 | 1.6 | 1.56 1010 | 1.1010 | 1.625 | 1.6 | 1.62 1011 | 1.1011 | 1.6875 | 1.7 | 1.69 1100 | 1.1100 | 1.75 | 1.8 | 1.75 1101 | 1.1101 | 1.8125 | 1.8 | 1.81 1110 | 1.1110 | 1.875 | 1.9 | 1.88 1111 | 1.1111 | 1.9375 | 1.9 | 1.94 

你说有多less个小数位? 你可以说2,在两位十进制数字范围内的每个值都被覆盖,尽pipe不是唯一的; 或者你可以说3,它涵盖了所有的唯一值,但不提供三位十进制数字范围内的所有值的范围。

为了说明起见,我们会说它有两个十进制数字:小数精度将是可以表示这些十进制数字的所有值的位数。


那么,那么如果我们将所有数字减半(所以我们使用yyy = -1)会发生什么?

  xxxx | 1.xxxx | value | 1dd | 2dd --------+----------+-----------+-------+-------- 0000 | 1.0000 | 0.5 | 0.5 | 0.50 0001 | 1.0001 | 0.53125 | 0.5 | 0.53 0010 | 1.0010 | 0.5625 | 0.6 | 0.56 0011 | 1.0011 | 0.59375 | 0.6 | 0.59 0100 | 1.0100 | 0.625 | 0.6 | 0.62 0101 | 1.0101 | 0.65625 | 0.7 | 0.66 0110 | 1.0110 | 0.6875 | 0.7 | 0.69 0111 | 1.0111 | 0.71875 | 0.7 | 0.72 1000 | 1.1000 | 0.75 | 0.8 | 0.75 1001 | 1.1001 | 0.78125 | 0.8 | 0.78 1010 | 1.1010 | 0.8125 | 0.8 | 0.81 1011 | 1.1011 | 0.84375 | 0.8 | 0.84 1100 | 1.1100 | 0.875 | 0.9 | 0.88 1101 | 1.1101 | 0.90625 | 0.9 | 0.91 1110 | 1.1110 | 0.9375 | 0.9 | 0.94 1111 | 1.1111 | 0.96875 | 1. | 0.97 

按照与之前相同的标准,我们现在处理1个十进制数字。 所以你可以看到,根据指数,你可以有更多或更less的十进制数字,因为二进制和十进制浮点数不会互相干净地映射

相同的参数适用于双精度浮点数(使用52位尾数),只有在这种情况下,您将得到15或16位十进制数,具体取决于指数。

所有现代计算机都使用二进制浮点algorithm。 这意味着我们有一个二进制尾数,其中单精度通常为24位,双精度为53位,扩展精度为64位。 (在x86处理器上可以使用扩展精度,但不能在ARM或其他types的处理器上使用。)

24,53和64比特的尾数意味着对于在2k2k + 1之间的浮点数,下一个较大的数目分别是2k- 23,2k-522k-63 。 这是决议。 每个浮点操作的舍入误差至多是其中的一半。

那么怎么翻译成十进制数呢? 这取决于

取k = 0和1≤x<2。分辨率分别为2 -23,2-52和2 -63 ,分别约为1.19×10 -7,2.2 ×10 -16和1.08×10 -19 。 这比7位,16位和19位小数less一点。 然后取k = 3和
8≤x<16.两个浮点数之间的差异现在是8倍大。 对于8≤x<10,你可以分别得到6以上,15以下,18以上的小数。 但是对于10≤x<16,你还会得到一位小数!

如果x只是小于2 k + 1且只有10 n以上 ,例如1000≤x <1024,那么得到的小数位数最多。如果x只是一个位高于2k并且小于10n ,例如1 / 1024≤x < 1/1000 。 相同的二进制精度可以产生小数精度,变化高达1.3位或10 (2×10)。

当然,你可以阅读“ 每个计算机科学家应该知道的有关浮点运算的知识” 。

使用其硬件协处理器(最初是8087)的80×86代码提供三级精度:32位,64位和80位。 那些非常严格遵循1985年的IEEE-754标准 。最近的标准规定了一个128位的格式 。 浮点格式具有24,53,65和113个尾数位,它们对应于精度的7.22,15.95,19.57和34.02十进制数字。

公式是mantissa_bits / log_2 10,其中10的日志基数是3.321928095。

虽然任何特定实现的精度不会改变,但浮点值转换为十进制时可能会出现这种情况。 请注意,值0.1不具有确切的二进制表示forms。 这是一个重复的位模式(0.0001100110011001100110011001100 …),就像我们习惯的小数0.3333333333333近似1/3。

许多语言通常不支持80位格式。 一些C编译器可能会提供long double ,使用80位浮点数或128位浮点数。 唉,它也可能使用64位浮点数,具体取决于实现。

NPU有80位寄存器,并使用完整的80位结果执行所有操作。 在NPU堆栈内计算的代码可以从这个额外的精度中受益。 不幸的是,糟糕的代码生成或编写得不好的代码可能会通过将其存储在32位或64位variables中来截断或舍入中间计算。

浮点精度是可变的还是不变的,为什么?

通常情况下,给定任何数字在相同的2的范围内,浮点精度是不变的 – 一个固定的值。 绝对精度随着每2次幂的变化而变化。 在整个FP范围内,精度大致相对于幅度。 以小数精度表示这种相对二进制精度会引起DBL_DIGDBL_DECIMAL_DIG十进制数字之间的摆动 – 通常为15到17。


什么是精确度? 用FP,讨论相对精度是最有意义的。

浮点数的forms如下:

符号*有意义* pow(基数,指数)

他们有一个对数分布。 在100.0和3000.0之间(范围是30x) 大约有许多不同的浮点数,因为在2.0和60.0之间。 无论底层存储表示如何,情况都是如此。

1.23456789e100具有与1.23456789e-100大致相同的相对精度。


大多数计算机实现double64作为二进制 。 这种格式具有53位二进制精度。

((2.0-1.0)/ pow(2,52))中1.0和2.0之间的n数字具有相同的绝对精度。
((128.0-64.0)/ pow(2,52))中64.0和128.0之间的数字(也是n )具有相同的绝对精度。

即使是两个幂数组之间,也具有相同的绝对精度。

在FP编号的整个正常范围内,这接近于统一的相对精度。

当这些数字以十进制表示时,精度抖动 :数字1.0到2.0的绝对精度比数字2.0到4.0多1个位。 比4.0到8.0等多2位

C提供了DBL_DIGDBL_DECIMAL_DIG和它们的floatlong double对应。 DBL_DIG表示最小相对小数精度。 DBL_DECIMAL_DIG可以被认为是最大的相对小数精度。

通常情况下,这意味着给定的double精度将有15到17个十进制数字。

考虑1.0和它的下一个可表示的double ,这些数字直到第十七个十进制数字才改变。 接下来的doublepow(2,-52)或大约2.2204e-16

 /* 1 234567890123456789 */ 1.000000000000000000... 1.000000000000000222... 

现在考虑"8.521812787393891"及其下一个可表示的数字作为hex的hex数字。 这两个string,转换为double相同的 8.521812787393891142073699...即使他们在第16位数字不同。 说这个double有十六位的精度是过分的。

 /* 1 234567890123456789 */ 8.521812787393891 8.521812787393891142073699... 8.521812787393892 

不,它是可变的。 起点是非常微弱的IEEE-754标准,它只是将存储在内存中的浮点数的格式确定下来。 您可以指望单精度的7位精度,双精度的15位数。

但是这个标准的一个主要缺陷是它没有指定如何执行计算。 而且还有麻烦,特别是Intel 8087浮点处理器已经给程序员带来了许多不眠之夜。 该芯片的一个重大devise缺陷是它存储的位数多于存储器格式的浮点值。 80位而不是32位或64位。deviseselect背后的理论是,这允许中间计算更精确并且导致更小的舍入误差。

听起来好像是个好主意,但是在实践中却不尽如人意。 一个编译器编写器将尽可能长的生成代码,以保留FPU中存储的中间值。 编码速度很重要,将值存回内存是很昂贵的。 麻烦的是,他经常需要存储数据,FPU中寄存器的数量是有限的,代码可能会越过函数的边界。 在这一点上值被截断,失去了很多的精度。 对源代码的小改动现在可以产生截然不同的值。 而且,程序的非优化构build与优化构build产生不同的结果。 以完全不可知的方式,您必须查看机器代码才能知道为什么结果是不同的。

英特尔重新devise了处理器来解决这个问题,SSE指令集计算的内存格式与位数相同。 但是,要慢慢着手,重新devise编译器的代码生成器和优化器是一项重要的投资。 三大C ++编译器全部切换。 但是,例如,.NET框架中的x86抖动仍然会生成FPU代码。


那么就存在系统性的误差,精确度就是转换和计算不可避免的副作用。 首先转换,人类以10为基数工作,但处理器使用基数2.我们使用的很好的整数(如0.1)不能在处理器上转换为漂亮的整数。 作为10的幂的和,0.1是完美的,但是没有产生相同的值的2的幂的有限和。 转换它会产生无限数量的1和0,就像你不能完全写下10/3一样。所以它需要被截断以适应处理器,并产生一个从+/- 0.5位偏移的值十进制值。

而计算产生错误。 乘法或除法将结果中的位数加倍,将其四舍五入以使其回到所存储的值中会产生+/- 0.5位的误差。 减法是最危险的操作,可能会导致很多有效数字的损失。 如果你说,计算1.234567f – 1.234566f那么结果只剩下1个有效数字。 这是一个垃圾的结果。 总结具有几乎相同的值的数字之间的差异在数值algorithm中是非常常见的。

获得过多的系统性错误最终是math模型中的一个缺陷。 举个例子,你永远不想使用高斯消元法,这对于精度来说是非常不友善的。 而且总是考虑一种替代方法,LU分解是一个很好的方法。 然而,math家参与build立模型并且说明结果的预期精度并不是那么普遍。 像Numerical Recipes这样的常见书也没有对精度给予足够的重视,尽pipe它通过提出更好的方法来间接地引导你远离不好的模型。 最后,程序员经常会遇到问题。 那么,这是很容易的,任何人都可以做到这一点,我会是一个很好的工作:)

浮点variables的types定义了值的范围和可以表示多less个小数位(!)。 由于小数和二进制小数之间没有整数关系,小数部分实际上是一个近似值。

第二:另一个问题是执行精度的算术运算。 试想一下1.0/3.0或PI。 这样的值不能用有限的数字来表示 – 既不是十进制也不是二进制。 所以值必须四舍五入以适应给定的空间。 可用的小数位越多,精确度越高。

现在考虑应用多个这样的操作,例如PI / 3.0。 这将需要两次:PI这样不是确切的,结果也不是。 这会松动两次精度,如果再次表示变差。

所以,回到floatdoublefloat已经按照标准(C11,附录F,也是其余的)less了可用位,所以roundig将不如double 。 只要想想有一个小数2位小数(m.ff,称之为浮动),一个与四(m.ffff,称之为双)。 如果所有计算都使用double,那么即使浮点数结果足够,您可以执行更多的操作,直到结果只有2个正确的小数位。

请注意,在某些(embedded式)CPU(如ARM Cortex-M4F)中,硬件FPU仅支持folat(单精度),因此双algorithm将更加昂贵。 其他MCU根本没有硬件浮点计算器,所以必须模拟我的软件(非常昂贵)。 在大多数GPU上,浮点运算比双运算要便宜得多,有时甚至超过10倍。

存储有一个精确的二进制数字计数,其他答案的解释。

有一点要知道,CPU可以在内部以不同的精度运行操作,比如80位。 这意味着像这样的代码可以触发:

 void Kaboom( float a, float b, float c ) // same is true for other floating point types. { float sum1 = a+b+c; float sum2 = a+b; sum2 += c; // let's assume that the compiler did not keep sum2 in a register and the value was write to memory then load again. if (sum1 !=sum2) throw "kaboom"; // this can happen. } 

更复杂的计算更可能。

我将在这里添加off-beat的答案,并且说因为你已经把这个问题标记为C ++,所以不能保证浮点数据的精度。 绝大多数实现在实现浮点types时使用IEEE-754 ,但这不是必需的。 C ++语言唯一需要的是(C ++规范§3.9.1.8):

有三种浮点types:float,double和long double。 doubletypes至less提供了与float相同的精度,long doubletypes的精度至less与double相同。 floattypes的值的集合是doubletypes的值集合的子集; doubletypes的值的集合是long doubletypes的值集合的子集。 浮点types的值表示是实现定义的 。 积分和浮动types统称为算术types。 标准模板std :: numeric_limits(18.3)的专业化应指定实现的每种算术types的最大值和最小值。

存储float所需的空间量将是恒定的,同样也是double ; 相对而言,有用精度的数量通常会有所不同,但是,在2 23中的一部分和2 24中的一部分之间float ,或者一部分在2 52和2 53中double 。 精度非常接近零不是很好,第二小的正值是最小的两倍大,这将是无限大于零。 然而,在整个范围内,精确度将如上所述变化。

请注意,尽pipe在整个范围内相对精度变化小于2倍的types通常是不现实的,但精度的变化有时会导致计算结果的计算精度远不如预期的那么高。 考虑一下,例如, 16777215.0f + 4.0f - 4.0f 。 所有的值都可以精确地表示为使用相同比例的float ,大数值的最接近值是16,777,215中的+/- 1个部分,但是第一次加法会产生部分float范围的结果,其中一部分只有8,388,610,导致四舍五入到16,777,220。 因此,减4将产生16,777,216,而不是16,777,215。 对于16777216附近的大部分float值,加上4.0f和减去4.0f将会使原始值保持不变,但是在转换点处的精度正好改变会导致结果在最低处被closures一个额外的位。

那么这个答案很简单,但是很复杂。 这些数字以二进制forms存储。 根据它是浮点数还是双精度值,计算机使用不同数量的二进制数来存储数字。 你得到的精度取决于你的二进制文件。 如果你不知道二进制数是如何工作的,那么查一下它是个好主意。 但简单地说,有些数字比其他数字需要更多的零和零。

所以精度是固定的 (相同数量的二进制数字),但实际精度取决于您使用的数字。