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

Wan*_*ool 38 floating-point binary decimal significant-digits floating-point-precision

我不断得到浮点数(即float,, doublelong double)是否只有一个精度值,或者具有可以变化的精度值的混合答案.

一个名为float vs. double precision的主题似乎暗示浮点精度是绝对的.

然而,另一个叫做float和double之间差异的话题说,

通常,double具有15到16个十进制数字的精度

另一位消息人士

float类型的变量通常具有大约 7位有效数字的精度

double类型的变量通常具有大约 16位有效数字的精度

如果我正在使用敏感代码,当我的值不准确时,我不喜欢引用上面的近似值.所以让我们记录下来吧.浮点精度是可变的还是不变的,为什么?

Chr*_*ung 29

精度是固定的,对于双精度,精确到53个二进制数字(如果我们排除隐式前导1,则精度为52).这大约是15位小数.


OP要求我详细说明为什么正好有53个二进制数字意味着"约"15个十进制数字.

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

因此,每个数字看起来像:( - 1)s ×2 yyy ×1.xxxx(其中s是符号位,yyy是指数,1.xxxx是标准化的尾数).对于即时讨论,我们只关注尾数而不是符号或指数.

这是一个1.xxxx所有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
Run Code Online (Sandbox Code Playgroud)

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

为了论证,我们会说它有2个十进制数字:小数精度将是可以表示这些十进制数字的所有值的位数.


好的,那么,如果我们将所有数字减半(所以我们使用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
Run Code Online (Sandbox Code Playgroud)

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

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

  • @jaggedSpire Scientific表示法通常缺少NaN,-0,+/ - inf和非规范化数字.所以,不太相同.;) (3认同)
  • 如果你曾经使用有效数字在科学记数法中使用过数字,那么浮点数就是二进制数. (2认同)
  • 您可能希望扩展"约15个十进制数字",因为这是一个问题(根据所表示的数字,十进制数字的数量可能会有所不同). (2认同)
  • 如果编译器使用IEC 60559 folats(_ STDC_IEC_559 _ defined),则这是唯一的.对于例如嵌入式系统,尤其是没有(兼容的)FPU,这不一定是正确的. (2认同)

gna*_*729 24

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

24,53,和64位尾数意味着对于一个浮点数2之间ķ和2 K + 1的下一个较大数为2 ķ23,2 K-52和2 K-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位小数.然后取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只是一个,则得到最小的十进制数位高于2 ķ和比特小于10 ñ,例如1/1024个 ≤X < 1/1000.相同的二进制精度可以产生十进制精度,最多可达1.3位数或log 10(2×10).

当然,您可以阅读文章" 每个计算机科学家应该了解的关于浮点运算的内容".

  • "所有现代计算机都使用二进制浮点运算"过度陈述了这种情况.选择现代流程直接支持十进制浮点[Power 6,7,8](http://en.wikipedia.org/wiki/POWER8)浮现在脑海中.[IEEE 754-2008](http://en.wikipedia.org/wiki/IEEE_floating_point)在其前身规范上添加了十进制FP格式.我看到硬件十进制FP支持扩展,虽然很慢. (2认同)

wal*_*lyk 9

使用其硬件协处理器(最初为8087)的80x86代码提供三个级别的精度: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没有精确的二进制表示.它是一个重复位模式(0.0001100110011001100110011001100 ...),就像我们习惯于十进制一样,0.3333333333333大约是1/3.

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

NPU具有80位寄存器,并使用完整的80位结果执行所有操作.在NPU堆栈内计算的代码可从此额外精度中受益.遗憾的是,代码生成不良或代码编写不当可能会将中间计算存储在32位或64位变量中,从而截断或舍入中间计算.

  • @supercat你在说什么?浮动变为双倍,双重保持双倍,长双重保持长双倍,总是如此.没有歧义.如果你想要一个长双,你必须明确. (3认同)

chu*_*ica 8

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

通常,给定相同2次幂范围内的任何数字,浮点精度是不变的 - 固定值.绝对精度随每个2阶功率而变化.在整个FP范围内,精度大致相对于幅度.在的小数精度方面与此相对二进制精度招致摇摆变化之间DBL_DIGDBL_DECIMAL_DIG十进制数字-典型地15至17.


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

浮点数的形式为:

签名*有效*pow(基数,指数)

它们具有对数分布.在100.0和3000.0(范围为30x)之间有大约不同的浮点数,因为它们介于2.0和60.0之间.无论底层存储表示如何,都是如此.

1.23456789e100具有与...相同的相对精度1.23456789e-100.


大多数计算机实现doublebinary64.此格式具有53位二进制精度.

n1.0和2.0之间的数字具有相同的绝对精度(1(2.0-1.0)/ pow(2,52)).
64.0和128.0之间的数字也n具有相同的绝对精度1((128.0-) 64.0)/ POW(2,52).

即使在2的幂之间的数组,也具有相同的绝对精度.

在FP数的整个正常范围内,这近似于均匀的相对精度.

当这些数字表示为十进制时,精度摆动:数字1.0到2.0的绝对精度比数字2.0到4.0多1位.比4.0到8.0还多2位等

C提供DBL_DIG,DBL_DECIMAL_DIG以及他们float和他们的long double对应物. DBL_DIG表示最小相对小数精度. DBL_DECIMAL_DIG可以认为是最大相对小数精度.

通常,这意味着给定double的精度为15到17位十进制数.

考虑1.0及其下一个可表示double的数字,直到第17个有效十进制数字才会改变数字.接下来的每一个doublepow(2,-52)或约2.2204e-16相隔.

/*
1 234567890123456789 */
1.000000000000000000...
1.000000000000000222...
Run Code Online (Sandbox Code Playgroud)

现在考虑"8.521812787393891"使用16个有效十进制数字,将其下一个可表示的数字视为十进制数字.即使它们在第16位数字不同,转换为这两个字符串double也是相同的 8.521812787393891142073699....说这double有16位数的精度被夸大了.

/*
1 234567890123456789 */
8.521812787393891
8.521812787393891142073699...
8.521812787393892
Run Code Online (Sandbox Code Playgroud)


Han*_*ant 6

不,它是可变的.起点是非常弱的IEEE-754标准,它只能确定浮动指针数的格式,因为它们存储在内存中.单精度可以计算7位精度,双精度可以计算15位数.

但该标准的一个主要缺陷是它没有具体说明如何进行计算.而且有问题,尤其是英特尔8087浮点处理器给程序员带来了许多不眠之夜.在该芯片A显著设计缺陷是,它存储浮点值与多个比存储器格式比特.设计选择背后的理论是,这允许中间计算更精确并且导致更少的舍入误差.

听起来是个好主意,然而在实践中并没有好转.编译器编写器将尝试生成代码,以尽可能长地保留存储在FPU中的中间值.对代码速度很重要,将值存储回内存是很昂贵的.麻烦的是,他经常必须存储值,FPU中的寄存器数量是有限的,代码可能跨越函数边界.此时值被截断并失去很多精度.对源代码的微小更改现在可以产生截然不同的值.此外,程序的非优化构建产生与优化程序不同的结果.以完全不可识别的方式,您必须查看机器代码以了解结果不同的原因.

英特尔重新设计了处理器以解决此问题,SSE指令集使用与内存格式相同的位数进行计算.然而,慢慢地接受,重新设计编译器的代码生成器和优化器是一项重大投资.三大C++编译器都已切换.但是,例如.NET Framework中的x86抖动仍会生成FPU代码,它始终会生成.


然后存在系统误差,失去精度是转换和计算的必然副作用.转换首先,人类在基数10中工作,但处理器使用基数2.我们使用的漂亮的圆数,如0.1,无法在处理器上转换为漂亮的整数.0.1作为10的幂之和是完美的,但是没有2的幂的有限和产生相同的值.转换它产生无限数量的1和0,就像你不能完美地写下10/3那样.所以它需要被截断以适应处理器并产生一个偏离+/- 0.5位的值十进制值.

并且计算产生错误.乘法或除法将结果中的位数加倍,将其四舍五入以使其适合存储的值会产生+/- 0.5位错误.减法是最危险的操作,可能导致丢失大量有效数字.如果您计算1.234567f - 1.234566f,那么结果只剩下1位有效数字.这是一个垃圾结果.总结具有几乎相同值的数字之间的差异在数值算法中是非常常见的.

获得过多的系统错误最终是数学模型中的一个缺陷.举个例子,你永远不想使用高斯消除,它对精度非常不友好.并且总是考虑另一种方法,LU分解是一种很好的方法.然而,数学家参与构建模型并考虑结果的预期精度并不常见.像Numerical Recipes这样的通用书也没有足够重视精确度,尽管它通过提出更好的模型间接地引导你远离坏模型.最后,程序员经常遇到问题.嗯,这很容易,任何人都可以做到这一点,我将是一个很好的工作:)

  • 由于将结果四舍五入并将其重新拟合为24位,因此每次计算都会失去精度.因此,如果进行乘法,则结果精确到23.5个二进制位+/- 0.5位.使用相同的简单数学,您现在有7.07十进制数字的精度.再做一次乘法,你现在有23个二进制位+/- 1位或6.92十进制数字的精度.这会使你的计算越多.减法更快.没有最小值,因为它完全取决于计算. (2认同)

too*_*ite 5

浮点变量的类型定义了值的范围以及可以表示多少小数位(!).由于十进制和二进制分数之间没有整数关系,小数部分实际上是近似值.

第二:另一个问题是执行精确算术运算.想想1.0/3.0或PI.这些值不能用有限数量的数字表示 - 既不是十进制也不是二进制.因此,必须对值进行舍入以适应给定的空间.可用的小数位数越多,精度越高.

现在考虑应用多个此类操作,例如PI/3.0.这需要进行两次舍入:PI本身并不准确,结果也不是.如果表示变得更糟,这将松散两倍.

因此,回到floatdouble:float根据标准(C11,附件F,也用于其余部分)有较少的可用比特,因此roundig将不那么精确double.想想一个带有2个小数位的十进制数(m.ff,称之为浮点数)和一个带四个的数字(m.ffff,称之为double).如果double用于所有计算,则可以使用更多操作,直到结果只有2个正确的小数位数,而不是已经以float开头,即使浮点结果就足够了.

请注意,在某些(嵌入式)CPU(如ARM Cortex-M4F)上,硬件FPU仅支持folat(单精度),因此双算术成本会更高.其他MCU根本没有硬件浮点计算器,因此必须模拟我的软件(非常昂贵).在大多数GPU上,浮动也比双倍便宜得多,有时甚至超过10倍.


gal*_*p1n 5

其他答案解释说,存储具有二进制的精确数字计数.

有一点需要了解,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.
}
Run Code Online (Sandbox Code Playgroud)

更复杂的计算更有可能.