ChatGPT解决这个技术问题 Extra ChatGPT

如何获取浮点数的符号、尾数和指数

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

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

按照该图的格式,

https://i.stack.imgur.com/tPPeM.jpg

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

S
Stargateur

我认为最好使用工会来做演员,这更清楚。

#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) unsigned/float 的字节序不匹配时,此方法会失败。 (稀有的)。 4) exponent/mantissa 使用数学解释,因为这个答案显示了有偏的指数和不完整的有效数/尾数。
上面的代码是可移植的吗?在大端和小端机器上会发生什么?
在这里聚会很晚,但不,union 也不是更好,因为它根本不能保证工作。它当然不是便携式的。没有什么能限制 C 实现对位域进行布局,以便联合将它们映射到 float 表示的所需部分,尽管存在依赖类型双关的单独问题。
P
Pietro Braione

我的建议是坚持规则 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 中的宏。


有什么方法可以知道有效数字中隐藏位的存在吗?那么指数偏差的检测呢?
C
Community

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

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

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


太糟糕了,我正在寻找一种可移植的解决方案来实现 dtoa,它在某种程度上是 printf 的子集...
X
Xymostech

&错位了。我想你想要:

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

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

请注意,面具直接来自您的图片。因此,指数掩码将如下所示:

0 11111111 00000000000000000000000

尾数掩码看起来像:

0 00000000 11111111111111111111111


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

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

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.  */

我们如何在实践中使用这些?如果我们必须检查我们是否有一个 nan,我们该怎么做?
@DimitriLesnoff 从 std::isnan 开始。
这看起来是一个很好的(彻底的)开始,但是要求一个显示输入和输出以及两者如何工作(或不工作)的小测试代码是否太过分了?
@詹姆斯布什godbolt.org/z/EzbY5jnTE
佚名

不要制作做多种事情的函数。不要掩饰然后转移;移位然后掩码。不要不必要地改变值,因为它很慢,缓存破坏并且容易出错。不要使用幻数。

/* 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
A
AymenTM

请参阅此 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

有关如何将 double 值转换为字符串形式的演示,请参阅 dtoa_base.c

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

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅