【问题标题】:How to get the sign, mantissa and exponent of a floating point number如何获取浮点数的符号、尾数和指数
【发布时间】:2013-03-19 02:07:11
【问题描述】:

我有一个程序,它在两个处理器上运行,其中一个不支持浮点。因此,我需要在该处理器中使用定点执行浮点计算。为此,我将使用浮点仿真库。

我需要首先在支持浮点的处理器上提取浮点数的符号、尾数和指数。所以,我的问题是如何获得单精度浮点数的符号、尾数和指数。

按照该图的格式,

这就是我到目前为止所做的,但除了符号,尾数和指数都不正确。我想,我错过了一些东西。

void getSME( int& s, int& m, int& e, float number )
{
    unsigned int* ptr = (unsigned int*)&number;

    s = *ptr >> 31;
    e = *ptr & 0x7f800000;
    e >>= 23;
    m = *ptr & 0x007fffff;
}

【问题讨论】:

  • 尝试从这里开始:en.wikipedia.org/wiki/Single-precision_floating-point_format,但我几乎可以肯定你看到了这个
  • C 标准不支持通过指针转换产生别名,在某些编译器中可能会很麻烦。最好使用(union { float f; uint32_t u; }) { number } .u。这将返回一个 uint32_t,它是 float number 的字节,被重新解释为 32 位无符号整数。
  • 我假设 IEEE 754 32 位二进制。您是否意识到以下问题? (1) 指数有偏差,实际指数加 127。 (2) 除了非常小的浮点数之外的所有浮点数都被归一化,并且一个归一化的浮点尾数的前1位不被存储。
  • 你是指C还是C++(C没有引用,只有指针)
  • 三个问题: 0. 没有从编码指数中去除偏差 1. 没有为正常的非零数添加隐式尾数位 2. 没有处理非正规数、无穷大和 sNaN/qNaNs

标签: c floating-point emulation


【解决方案1】:

请参阅此 IEEE_754_types.h 标头以了解要提取的联合类型:floatdoublelong double,(已处理字节序)。这是摘录:

/*
** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
**  Single Precision (float)  --  Standard IEEE 754 Floating-point Specification
*/

# define IEEE_754_FLOAT_MANTISSA_BITS (23)
# define IEEE_754_FLOAT_EXPONENT_BITS (8)
# define IEEE_754_FLOAT_SIGN_BITS     (1)

.
.
.

# if (IS_BIG_ENDIAN == 1)
    typedef union {
        float value;
        struct {
            __int8_t   sign     : IEEE_754_FLOAT_SIGN_BITS;
            __int8_t   exponent : IEEE_754_FLOAT_EXPONENT_BITS;
            __uint32_t mantissa : IEEE_754_FLOAT_MANTISSA_BITS;
        };
    } IEEE_754_float;
# else
    typedef union {
        float value;
        struct {
            __uint32_t mantissa : IEEE_754_FLOAT_MANTISSA_BITS;
            __int8_t   exponent : IEEE_754_FLOAT_EXPONENT_BITS;
            __int8_t   sign     : IEEE_754_FLOAT_SIGN_BITS;
        };
    } IEEE_754_float;
# endif

请参阅dtoa_base.c,了解如何将double 值转换为字符串形式。

此外,请查看C/CPP Reference Book1.2.1.1.4.2 - Floating-Point Type Memory Layout 部分,它非常简单地解释了所有浮点类型的内存表示/布局以及如何按照实际情况对它们进行解码(带插图) IEEE 754 浮点规范。

它还包含指向非常好的资源的链接,可以更深入地解释。

【讨论】:

    【解决方案2】:
    1. 不要创建做多项事情的函数。
    2. 不要掩饰然后换档;移位然后掩码。
    3. 不要不必要地改变值,因为它速度慢、会破坏缓存并且容易出错。
    4. 不要使用幻数。
    /* NaNs, infinities, denormals unhandled */
    /* assumes sizeof(float) == 4 and uses ieee754 binary32 format */
    /* assumes two's-complement machine */
    /* C99 */
    #include <stdint.h>
    
    #define SIGN(f) (((f) <= -0.0) ? 1 : 0)
    
    #define AS_U32(f) (*(const uint32_t*)&(f))
    #define FLOAT_EXPONENT_WIDTH 8
    #define FLOAT_MANTISSA_WIDTH 23
    #define FLOAT_BIAS ((1<<(FLOAT_EXPONENT_WIDTH-1))-1) /* 2^(e-1)-1 */
    #define MASK(width)  ((1<<(width))-1) /* 2^w - 1 */
    #define FLOAT_IMPLICIT_MANTISSA_BIT (1<<FLOAT_MANTISSA_WIDTH)
    
    /* correct exponent with bias removed */
    int float_exponent(float f) {
      return (int)((AS_U32(f) >> FLOAT_MANTISSA_WIDTH) & MASK(FLOAT_EXPONENT_WIDTH)) - FLOAT_BIAS;
    }
    
    /* of non-zero, normal floats only */
    int float_mantissa(float f) {
      return (int)(AS_U32(f) & MASK(FLOAT_MANTISSA_BITS)) | FLOAT_IMPLICIT_MANTISSA_BIT;
    }
    
    /* Hacker's Delight book is your friend. */
    

    【讨论】:

    • “不要使用幻数”强烈建议使用DBL_MANT_DIG 而不是创建自己的版本!
    • @TobySpeight 这是FLT_MANT_DIG
    【解决方案3】:

    我认为最好使用联合进行强制转换,这样更清楚。

    #include <stdio.h>
    
    typedef union {
      float f;
      struct {
        unsigned int mantisa : 23;
        unsigned int exponent : 8;
        unsigned int sign : 1;
      } parts;
    } float_cast;
    
    int main(void) {
      float_cast d1 = { .f = 0.15625 };
      printf("sign = %x\n", d1.parts.sign);
      printf("exponent = %x\n", d1.parts.exponent);
      printf("mantisa = %x\n", d1.parts.mantisa);
    }
    

    基于http://en.wikipedia.org/wiki/Single_precision的示例

    【讨论】:

    • "由于某种原因,联合的最初目的被完全不同的东西“覆盖”:编写联合的一个成员,然后通过另一个成员检查它。这种内存重新解释不是有效使用联合。它通常会导致未定义的行为。 stackoverflow.com/a/2313676/1127387
    • 没有法律规定您只能将事物用于最初创建的目的。否则第一架飞机就不会使用自行车。 “一般”未定义?那些被定义的场合,或者当你对给定平台/情况下的行为感到满意时呢?
    • 此方法在以下情况下失败:1) float 不是 IEEE 754 32 位二进制(并不罕见)2) unsigned 是 16 位(在嵌入式世界中很常见)3) @987654326 的字节序@ 不符合。 (稀有的)。 4)数学解释用于exponent/mantissa,因为这个答案显示了有偏差的指数和不完整的有效数/尾数。
    • 上面的代码可以移植吗?大小端机器上会发生什么?
    • 在这里聚会很晚,但是不,union 也不是更好,因为它根本不能保证工作。它当然不是便携式的。没有什么可以限制 C 实现对位域进行布局,以便联合将它们映射到 float 表示的所需部分,尽管存在依赖类型双关的单独问题。
    【解决方案4】:

    在 Linux 包 glibc-headers 上提供带有浮点类型定义的标头 #include &lt;ieee754.h&gt;,例如:

    union ieee754_double
      {
        double d;
    
        /* This is the IEEE 754 double-precision format.  */
        struct
          {
    #if __BYTE_ORDER == __BIG_ENDIAN
        unsigned int negative:1;
        unsigned int exponent:11;
        /* Together these comprise the mantissa.  */
        unsigned int mantissa0:20;
        unsigned int mantissa1:32;
    #endif              /* Big endian.  */
    #if __BYTE_ORDER == __LITTLE_ENDIAN
    # if    __FLOAT_WORD_ORDER == __BIG_ENDIAN
        unsigned int mantissa0:20;
        unsigned int exponent:11;
        unsigned int negative:1;
        unsigned int mantissa1:32;
    # else
        /* Together these comprise the mantissa.  */
        unsigned int mantissa1:32;
        unsigned int mantissa0:20;
        unsigned int exponent:11;
        unsigned int negative:1;
    # endif
    #endif              /* Little endian.  */
          } ieee;
    
        /* This format makes it easier to see if a NaN is a signalling NaN.  */
        struct
          {
    #if __BYTE_ORDER == __BIG_ENDIAN
        unsigned int negative:1;
        unsigned int exponent:11;
        unsigned int quiet_nan:1;
        /* Together these comprise the mantissa.  */
        unsigned int mantissa0:19;
        unsigned int mantissa1:32;
    #else
    # if    __FLOAT_WORD_ORDER == __BIG_ENDIAN
        unsigned int mantissa0:19;
        unsigned int quiet_nan:1;
        unsigned int exponent:11;
        unsigned int negative:1;
        unsigned int mantissa1:32;
    # else
        /* Together these comprise the mantissa.  */
        unsigned int mantissa1:32;
        unsigned int mantissa0:19;
        unsigned int quiet_nan:1;
        unsigned int exponent:11;
        unsigned int negative:1;
    # endif
    #endif
          } ieee_nan;
      };
    
    #define IEEE754_DOUBLE_BIAS 0x3ff /* Added to exponent.  */
    

    【讨论】:

      【解决方案5】:

      我的建议是坚持规则 0,不要重做标准库已经做的事情,如果这已经足够的话。查看 math.h(标准 C++ 中的 cmath)和函数 frexp、freexpf、freexpl,它们在有效数和指数部分破坏浮点值(double、float 或 long double)。要从有效数字中提取符号,您可以使用符号位,也可以在 math.h / cmath 或 copysign(仅限 C++11)中使用。一些语义稍有不同的替代方案是 modf 和 ilogb/scalbn,在 C++11 中可用; http://en.cppreference.com/w/cpp/numeric/math/logb 比较它们,但我没有在文档中找到所有这些函数在 +/-inf 和 NaN 下的行为。最后,如果您真的想使用位掩码(例如,您迫切需要知道确切的位,并且您的程序可能有不同的 NaN 具有不同的表示形式,并且您不信任上述函数),至少让一切都与平台无关通过使用 float.h/cfloat 中的宏。

      【讨论】:

      • 有什么方法可以知道有效数字中隐藏位的存在吗?那么指数偏差的检测呢?
      【解决方案6】:

      &amp;输入了错误的位。我想你想要:

      s = *ptr >> 31;
      e = *ptr & 0x7f800000;
      e >>= 23;
      m = *ptr & 0x007fffff;
      

      请记住,当您&amp; 时,您会将未设置的位清零。因此,在这种情况下,您希望在获得指数时将符号位清零,并在获得尾数时将符号位和指数清零。

      请注意,面具直接来自您的图片。所以,指数掩码看起来像:

      0 11111111 00000000000000000000000

      尾数掩码看起来像:

      0 00000000 11111111111111111111111

      【讨论】:

      • @MetallicPriest 现在试试,我第一次弄错了面具。
      • 所谓的隐藏位呢?我没有看到有人设置它:m |= 0x00800000;。请注意,应首先检查数字的特殊值(非正规、NaN、无穷大),因为这些需要不同的处理。
      • @RudyVelthuis 从他们的原始代码看来,他们并没有试图实际获取指数和尾数的值,只是试图获取每个的位表示。我假设这是因为他们没有或在隐藏位或规范化符号,但我可能是错的。
      • 我假设他们忘记了,这就是他们得到错误值的原因。但我只能猜测。
      【解决方案7】:

      找出直接支持浮点的CPU上使用的浮点数的格式,并将其分解为这些部分。最常见的格式是IEEE-754

      或者,您可以使用一些特殊函数(double frexp(double value, int *exp);double ldexp(double x, int exp);)获取这些部分,如 this answer 所示。

      Another option 是将%aprintf() 一起使用。

      【讨论】:

      • 太糟糕了,我正在寻找一个可移植的解决方案来实现 dtoa,它在某种程度上是printf 的一个子集...
      猜你喜欢
      • 2018-09-23
      • 2015-07-20
      • 2018-01-12
      • 1970-01-01
      • 1970-01-01
      • 2019-02-15
      • 1970-01-01
      • 1970-01-01
      • 2020-12-20
      相关资源
      最近更新 更多