无法分离 ieee 754 浮点的不同部分
Posted
技术标签:
【中文标题】无法分离 ieee 754 浮点的不同部分【英文标题】:cannot separate the different parts of an ieee 754 floating point 【发布时间】:2022-01-15 01:54:06 【问题描述】:我目前正在尝试使用 C 位运算符将单精度浮点的不同部分与 IEEE 754 分开。我计划将分离的部分放在一个结构中。我的最终目标是使用位运算符编写算术运算。
然而,我偶然发现了一个小问题,我的结果没有任何意义。我一直无法找到解决此问题的方法,也无法在互联网上找到解决方案。对此的任何见解将不胜感激。
以下是我用过的所有模块。
//test.c
#include <stdio.h>
#include "splicing.h"
int main(void)
float a = 5, b = -3, c = 0.1;
sploat A, B, C;
printf("%f\n%x\n", a, *(unsigned int*) &a);
printf("%f\n%x\n", b, *(unsigned int*) &b);
printf("%f\n%x\n\n", c, *(unsigned int*) &c);
splice(a, A);
splice(b, B);
splice(c, C);
printf("%f\n%hhu %hhi %x\n\n", a, A.s, A.e, A.m);
printf("%f\n%hhu %hhi %x\n\n", b, B.s, B.e, B.m);
printf("%f\n%hhu %hhi %x\n\n", c, C.s, C.e, C.m);
return 0;
/*
* Expected results
*
* 5 = 0x40a00000
* exp = +2
* man = 0x200000 (explicit) 0xa00000 (spliced)
* sign = 0
*
* -3 = 0xc0400000
* exp = +1
* man = 0x400000 (explicit) 0xc00000 (spliced)
* sign = 1
*
* 0.1 = 0x3dccccd
* exp = -4
* man = 0x4ccccc (explicit) 0xcccccc (spliced)
* sign = 0
*/
//splicing.h
typedef struct splicedflt
unsigned char s; //sign
signed char e; //exponent
unsigned int m; //mantissa
sploat; //short for spliced float
//unfinished
//Makes inserted sploat reflect inserted float. The problem child I need help with.
int splice(float, sploat);
//splicing.c
int splice(float num, sploat strukt)
unsigned int raw = *(unsigned int*) # //floats don't allow for bitmagic.
strukt.s = raw >> 31;
strukt.e = (raw << 1) >> 24;
strukt.m = ((raw << 9) >> 9) | 0x1000000;
return 0;
以下是程序的输出。我不知道为什么这不起作用。
$ gcc test.c
$ ./a.out
5.000000
40a00000
-3.000000
c0400000
0.100000
3dcccccd
5.000000
0 0 0
-3.000000
160 0 5588
0.100000
160 -20 7ffe
$
【问题讨论】:
永远不要使用*(unsigned int*) &a
。在 C 中,使用(union float f; unsigned u; ) a .u
或FloatToRepresentation(a)
,其中FloatToRepresentation
使用static unsigned FloatToRepresentation(float x) unsigned int u; memcpy(&u, &x, sizeof u); return u;
定义。这假定 float
和 unsigned
的大小相同。 (在 C++ 中,不要使用前者。)
浮点数小数部分的首选术语是“有效位”。 “尾数”是对数的小数部分的旧术语。尾数是对数的(加上尾数乘以表示的数字)。有效数字是线性的(有效数字乘以表示的数字)。
其他问题包括:strukt.m = ((raw << 9) >> 9) | 0x1000000;
对 0,0 和亚法线数的处理不当。代码不考虑无穷大或非数字。 float, unsigned
的大小可能不同。
【参考方案1】:
(据我所知)您的代码中有三个问题。
第一个非常重要的问题是您将spfloat
结构传递给splice
函数按值;也就是说,将相应值的 副本 赋予该函数,并且该副本被修改 - 原始结构(因此在您的 main
函数中保持不变)。要解决这个问题,请“通过引用”传递这些结构(即,使用指向结构的指针作为参数)。
修复此问题后,您的指数字段将出错,因为 IEEE-754 格式使用 biased exponents - 对于单精度(32 位)浮点数据,您可以通过减去该值来纠正此问题(在大多数情况下)存储值的偏差(127)。
您的unsigned int raw = *(unsigned int*) &num;
行中还存在违反strict aliasing rules 的潜在问题;使用memcpy
函数来防止这种情况发生。
这是您的 splice
函数的修改版本:
int splice(float num, sploat* strukt) // Pass "strukt" as a pointer
unsigned int raw;
memcpy(&raw, &num, sizeof(raw)); // Avoid strict aliasing violation
strukt->s = raw >> 31;
strukt->e = (signed char)((raw << 1) >> 24) - 127; // Remove the BIAS
strukt->m = ((raw << 9) >> 9) | 0x1000000;
return 0;
这是在main
中的调用方式:
int main(void)
float a = 5, b = -3, c = 0.1f;
sploat A, B, C;
//...
splice(a, &A); // Pass the ADDRESS of each structure...
splice(b, &B);
splice(c, &C);
// ...
return 0;
【讨论】:
【参考方案2】:splice(a, A);
形式的调用无法更改 A
,因为该调用仅将 A
的值传递给函数。地址或任何其他访问A
的方式都不会传递给函数。
更改splice
,使其接受float
参数并返回sploat
值:
sploat splice(float num)
sploat S;
unsigned raw = (union float f; unsigned u; ) num .u;
S.s = raw >> 31;
S.e = (raw << 1) >> 24;
S.m = ((raw << 9) >> 9) | 0x1000000;
return S;
更改调用以匹配:
A = splice(a);
B = splice(b);
C = splice(c);
【讨论】:
【参考方案3】:您需要将引用传递给您的结构。目前,您的函数没有修改 strukt
参数,因为它是通过 value 传递的,并且您更改了它的 local 副本。
您还必须避免指针双关语,因为它违反了严格的别名规则。请改用memcpy
。
int splice(float num, sploat *strukt)
unsigned raw;
memcpy(&raw, &num, sizeof(raw));
strukt -> s = raw >> 31;
strukt -> e = (raw << 1) >> 24;
strukt -> m = ((raw << 9) >> 9) | 0x1000000;
return 0;
splice(a, &A);
splice(b, &B);
splice(c, &C);
PS 我没有修改你的移位逻辑,因为这是你的作业,不是我的。
【讨论】:
您忘记了指数偏差。 @AdrianMole 我实际上没有检查按位逻辑:) OP 必须完成一些工作以上是关于无法分离 ieee 754 浮点的不同部分的主要内容,如果未能解决你的问题,请参考以下文章