浮点数指数的偏差值和范围

问题描述

我意识到我坐在大学课桌时没有适当注意 IEEE 754 标准的浮点部分。然而,即使我目前没有在嵌入式方面苦苦挣扎,我也觉得自己无能,由于缺乏某种数学计算方法和完全掌握标准,我无法获得工程师的称号。

我所知道的是

  • 0255 是表示 0infinity 的特殊值 价值。

  • 隐含的 1 用于将 23bit 表示为 24

  • 其中,e 仅当它是 000 时才变为 1,如果它是 111 并且尾数是 0000,那么它是无穷大,如果它是 111 并且尾数是 {{ 1}},则它不是数字。

我不明白的是

  • 我们如何才能同时提及 XXXX-126?怎么可能全部 254 个值被划分为包含值?
  • 为什么选择 127 作为偏差值?
  • 一些来源将分段解释为 127some [-125...128]。这真的很复杂而且令人困惑。
  • 如果不是上述第二个来源,我们怎么能说最小的 [-126..127]?如果是 2^{-126} ? (虽然我很挣扎,但直到现在我都无法运行我的大脑来理解它:)
  • 使用 modulo 余数运算符与偏置值而不是减法(即 2^{-125})是否更合乎逻辑? (更正感谢chux

解决方法

指数范围

对于 32 位浮点数,原始指数 rexp 为 8 位 <0,255>,偏差为 127。排除特殊情况 { 0,255 } 我们得到 <1,254> 应用偏差:

expmin =   1-127 = -126
expmax = 254-127 = +127

非正规值没有隐含的 1,所以对于最小数,尾数是 1,如果指数应该指向尾数的 lsb,那么我们需要移动更多:

expmin =   0-127-(23-1) = -149

正常最大值将具有最大尾数,因此:

max = ((2^24)-1)*(2^127) =  (2^24)*(2^127) - (2^127) = 2^151 - 2^127

所以 float 的实际范围(包括非正规数)是:

<2^-149,2^+151  )
<1.40e-45,2.85e+45) 

在大多数规范和文档中,仅显示标准化数字的指数:

<2^-126,2^+127  >
<1.175e-38,1.701e38>

这里有一个剖析 32 位和 64 位浮点数的小 C++/VCL 示例:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
typedef unsigned __int32 U32;
typedef __int32          S32;
//---------------------------------------------------------------------------
// IEEE 754 double MSW masks
const U32 _f64_sig    =0x80000000;  // sign
const U32 _f64_exp    =0x7FF00000;  // exponent
const U32 _f64_exp_sig=0x40000000;  // exponent sign
const U32 _f64_exp_bia=0x3FF00000;  // exponent bias
const U32 _f64_exp_lsb=0x00100000;  // exponent LSB
const U32 _f64_exp_pos=        20;  // exponent LSB bit position
const U32 _f64_man    =0x000FFFFF;  // mantisa
const U32 _f64_man_msb=0x00080000;  // mantisa MSB
const U32 _f64_man_bits=       52;  // mantisa bits
const double _f64_lsb    = 1.7e-308;    // abs min number
// IEEE 754 single masks <2^-149,2^+151) <1.40e-45,2.85e+45).
const U32 _f32_sig    =0x80000000;  // sign
const U32 _f32_exp    =0x7F800000;  // exponent
const U32 _f32_exp_sig=0x40000000;  // exponent sign
const U32 _f32_exp_bia=0x3F800000;  // exponent bias
const U32 _f32_exp_lsb=0x00800000;  // exponent LSB
const U32 _f32_exp_pos=        23;  // exponent LSB bit position
const U32 _f32_man    =0x007FFFFF;  // mantisa
const U32 _f32_man_msb=0x00400000;  // mantisa MSB
const U32 _f32_man_bits=       23;  // mantisa bits
const float _f32_lsb     =  3.4e-38;// abs min number
//---------------------------------------------------------------------------
void f64_disect(double x)
    {
    const int h=1;  // may be platform dependent MSB/LSB order
    const int l=0;
    union _f64
        {
        double f;   // 64bit floating point
        U32 u[2];   // 2x32 bit uint
        } f64;

    AnsiString txt="";

    U32 man[2];
    S32 exp,bias;
    char sign='+';
    f64.f=x;
    bias=_f64_exp_bia>>_f64_exp_pos;

    if (f64.u[h]&_f64_sig) sign='-';
    exp   =(f64.u[h]&_f64_exp)>>_f64_exp_pos;
    exp  -=bias;
    man[h]=f64.u[h]&_f64_man;
    man[l]=f64.u[l];

        if (exp==-bias  )           // zero,denormalized
        {
        exp-=_f64_man_bits-1;       // change exp pointing from msb to lsb (ignoring implicit bit)
        txt=AnsiString().sprintf("%c%06X%08Xh>>%4i",sign,man[h],man[l],-exp);
        }
    else if (exp==+bias+1)          // Inf,NaN
        {
        if (man[h]|man[l]==0) txt=AnsiString().sprintf("%cInf                  ",sign);
         else                 txt=AnsiString().sprintf("%cNaN                  ",sign);
        man[h]=0; man[l]=0; exp=0;
        }
    else{
        exp   -=_f64_man_bits;      // change exp pointing from msb to lsb
        man[h]|=_f64_exp_lsb;       // implicit msb mantisa bit for normalized numbers
        txt=AnsiString().sprintf("%06X",man);
        if (exp<0) txt=AnsiString().sprintf("%c%06X%08Xh>>%4i",-exp);
         else      txt=AnsiString().sprintf("%c%06X%08Xh<<%4i",+exp);
        }

    // reconstruct man,exp back to double
    double y=double(man[l])*pow(2.0,exp);
          y+=double(man[h])*pow(2.0,exp+32.0);
    Form1->mm_log->Lines->Add(AnsiString().sprintf("%21.10lf = %s = %21.10lf",x,txt,y));
    }
//---------------------------------------------------------------------------
void f32_disect(double x)
    {
    union _f32      // float bits access
        {
        float f;    // 32bit floating point
        U32 u;      // 32 bit uint
        } f32;

    AnsiString txt="";

    U32 man;
    S32 exp,bias;
    char sign='+';
    f32.f=x;
    bias=_f32_exp_bia>>_f32_exp_pos;

    if (f32.u&_f32_sig) sign='-';
    exp =(f32.u&_f32_exp)>>_f32_exp_pos;
    exp-=bias;
    man =f32.u&_f32_man;

        if (exp==-bias  )           // zero,denormalized
        {
        exp-=_f32_man_bits-1;       // change exp pointing from msb to lsb (ignoring implicit bit)
        txt=AnsiString().sprintf("%c%06Xh>>%3i",man,NaN
        {
        if (man==0) txt=AnsiString().sprintf("%cInf         ",sign);
         else       txt=AnsiString().sprintf("%cNaN         ",sign);
        man=0; exp=0;
        }
    else{
        exp-=_f32_man_bits;         // change exp pointing from msb to lsb
        man|=_f32_exp_lsb;          // implicit msb mantisa bit for normalized numbers
        txt=AnsiString().sprintf("%06X",man);
        if (exp<0) txt=AnsiString().sprintf("%c%06Xh>>%3i",-exp);
         else      txt=AnsiString().sprintf("%c%06Xh<<%3i",exp back to float
    float y=float(man)*pow(2.0,exp);
    Form1->mm_log->Lines->Add(AnsiString().sprintf("%21.10f = %s = %21.10f",y));
    }
//---------------------------------------------------------------------------
//--- Builder: --------------------------------------------------------------
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    mm_log->Lines->Add("[Float]\r\n");

    f32_disect(123*pow(2.0,-127-22));   // Denormalizxed
    f32_disect(+0.0);                   // Zero
    f32_disect(-0.0);                   // Zero
    f32_disect(+0.0/0.0);               // NaN
    f32_disect(-0.0/0.0);               // NaN
    f32_disect(+1.0/0.0);               // Inf
    f32_disect(-1.0/0.0);               // Inf
    f32_disect(+123.456);               // Normalized
    f32_disect(-0.000123);              // Normalized

    mm_log->Lines->Add("\r\n[Double]\r\n");

    f64_disect(123*pow(2.0,-127-22));   // Denormalizxed
    f64_disect(+0.0);                   // Zero
    f64_disect(-0.0);                   // Zero
    f64_disect(+0.0/0.0);               // NaN
    f64_disect(-0.0/0.0);               // NaN
    f64_disect(+1.0/0.0);               // Inf
    f64_disect(-1.0/0.0);               // Inf
    f64_disect(+123.456);               // Normalized
    f64_disect(-0.000123);              // Normalized

    mm_log->Lines->Add("\r\n[Fixed]\r\n");
    const int n=10;
    float fx=12.345,fy=4.321,fm=1<<n;
    int   x=float(fx*fm);
    int   y=float(fy*fm);
    mm_log->Lines->Add(AnsiString().sprintf("%7.3f + %7.3f = %8.3f = %8.3f",fx,fy,fx+fy,float(int((x+y)   ))/fm));
    mm_log->Lines->Add(AnsiString().sprintf("%7.3f - %7.3f = %8.3f = %8.3f",fx-fy,float(int((x-y)   ))/fm));
    mm_log->Lines->Add(AnsiString().sprintf("%7.3f * %7.3f = %8.3f = %8.3f",fx*fy,float(int((x*y)>>n))/fm));
    mm_log->Lines->Add(AnsiString().sprintf("%7.3f / %7.3f = %8.3f = %8.3f",fx/fy,float(int((x/y)<<n))/fm
                                                                                       +float(int(((x%y)<<n)/y))/fm));
    }
//---------------------------------------------------------------------------

这可能会帮助您了解更多...如果您有兴趣,也可以看看这个:

指数偏差

它被选为范围边缘之间的中间:

bias = (0+255)/2 = 127

使正指数和负指数的范围尽可能相同

取模

使用 exp=rexp%127 不会给你来自无符号 rexp 的负值,更不用说除法是缓慢的操作(至少在创建规范时)......这就是为什么{ {1}}

,

我们如何才能同时提及 -126127?如何将总共可能的 254 个值划分为包含值?

IEEE 754-2008 3.3 规定 emin,最小指数,对于任何格式应该是 1−emax,其中 emax 是最大值指数。该子句中的表 3.2 表示 32 位格式(名为“binary32”)的 emax 应为 127。所以 emin 为 1−127 = −126。

没有强制执行此操作的数学约束。这种关系是根据偏好选择的。我记得有人希望正指数比负指数略多,但不记得这样做的理由。

为什么选择 127 作为偏差值?

一旦选择了上述边界,127 必然是将它们编码为 8 位所需的值(作为代码 1-254,而将 0 和 255 作为特殊代码)。

有些来源将分段解释为 [-126..127],但有些来源解释为 [-125...128]。这真的很复杂而且令人困惑。

给定的二进制 32 位是符​​号位 S、八个指数位 E(它们是数字的二进制表示 e)和 23 个有效位 F(它们是二进制表示一个数 f),并且给定 0 e

  • 表示的数字是 (-1)S • 2e-127 • (1+f /223).
  • 表示的数字是 (-1)S • 2e-127 • 1.F2 .
  • 表示的数字是 (−1)S • 2e−126 • (½+f /224).
  • 表示的数字是 (-1)S • 2e-126 • .1F2
  • 表示的数字是 (-1)S • 2e-150 • (223+ f).
  • 表示的数字是 (-1)S • 2e-150 • 1F.2

前两者的区别只是第一个取有效位F,把它们当作一个二进制数得到一个数f,然后将这个数除以223 并加 1,而第二个使用 23 个有效位 F 写出一个 24 位数字“1.F”,然后将其解释为二进制数字。这两种方法产生相同的值。

第一对和第二对的区别在于,第一对在半开区间[1,2)准备有效数,而第二对在半开区间[½,1)准备有效数,并且调整指数以进行补偿。产品是一样的。

第一对和第三对之间的区别也在于缩放比例。第三对缩放有效数,使其为整数。第一种形式在浮点数的讨论中最常见,但第三种形式对于数学证明很有用,因为数论通常适用于整数。这种形式也在 IEEE 754 中顺便提到,也在第 3.3 条中。

如果不是上述第二个来源,我们怎么能说最小的 2^{-126}?如果是 2^{-125} ? (虽然我很挣扎,但直到现在我都无法运行我的大脑来理解它:)

最小正正常值有 S 位 0、E 位 00000001 和 F 位 000000000000000000000000。在第一种形式中,这表示 +1 • 21−127 • 1 = 2 -126。在第二种形式中,它表示 +1 • 21−126 • ½ = 2−126。在第三种形式中,它表示+1 • 21-150 • 223 = 2−126。所以形式无关紧要;表示的值相同。

将余数运算符用于偏置值而不是减法,即 2^{e%127} 是否更合乎逻辑?

没有。这将导致指数字段值 1 和 128 映射到相同的值,并且会浪费一些编码。这样做没有任何好处。

此外,编码格式使得所有正浮点数与其编码的顺序相同:增加编码增加表示的值,反之亦然。这种关系不会因指数字段的任何类型的包装解释而过时。 (不幸的是,这对于负数是翻转的,因此将浮点数的编码作为纯整数进行比较不会得到与比较浮点数相同的结果。)