我必须编写一个模拟浮点乘法的程序。对于此程序,我们假设单个精度浮点数存储在unsigned long a
中。我必须使用以下运算符将a
中存储的数字乘以2:<< >> | & ~ ^
我理解这些运算符的功能,但我对如何实现它的逻辑感到困惑。任何帮助将不胜感激。
必须使用以下运算符将乘以2的数字相乘:<< >> | &〜^
因为我们给出了一个无符号长度来模拟具有单点精度的浮点值,所以我们应该处理所有可以模拟的值。 ref
首先让我们假设浮点数被编码为binary32,而unsigned
是32位。 C不需要其中任何一个。
首先隔离指数以处理float
子组:亚正常,正常,无穷大和NAN。
下面是一些经过严格测试的代码 - 我稍后会回顾,现在将其视为伪代码模板。
#define FLT_SIGN_MASK 0x80000000u
#define FLT_MANT_MASK 0x007FFFFFu
#define FLT_EXPO_MASK 0x7F800000u
#define FLT_EXPO_LESSTHAN_MAXLVAUE(e) ((~(e)) & FLT_EXPO_MASK)
#define FLT_EXPO_MAX FLT_EXPO_MASK
#define FLT_EXPO_LSBit 0x00800000u
unsigned increment_expo(unsigned a) {
unsigned carry = FLT_EXPO_LSBit;
do {
unsigned sum = a ^ carry;
carry = (a & carry) << 1;
a = sum;
} while (carry);
return a;
}
unsigned float_x2_simulated(unsigned x) {
unsigned expo = x & FLT_EXPO_MASK;
if (expo) { // x is a normal, infinity or NaN
if (FLT_EXPO_LESSTHAN_MAXLVAUE(expo)) { // x is a normal
expo = increment_expo(expo); // Double the number
if (FLT_EXPO_LESSTHAN_MAXLVAUE(expo)) { // no overflow
return (x & (FLT_SIGN_MASK | FLT_MANT_MASK)) | expo;
}
return (x & FLT_SIGN_MASK) | FLT_EXPO_MAX;
}
// x is an infinity or NaN
return x;
}
// x is a sub-normal
unsigned m = (x & FLT_MANT_MASK) << 1; // Double the value
if (m & FLT_SIGN_MASK) {
// Doubling caused sub-normal to become normal
// Special code not needed here and the "carry" becomes the 1 exponent.
}
return (x & FLT_SIGN_MASK) | m;
}
这是我使用按位运算符的代码。
此代码乘以2单个精度浮点,将浮点指数增加1并仅使用按位运算符;此外,还要处理指数和数字符号(第30和31位)。
它并没有假装涵盖浮点详细说明的所有方面。
请记住,如果代码更改了位30和/或31,我们就会溢出。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
int main()
{
float f = -23.45F;
uint32_t *i=(uint32_t *)(&f);
uint32_t sgn;
uint32_t c,sc;
printf("%08X %f\n",*i,f);
sgn = *i & (0xC0000000); // copies bits 31 and 30
c = *i & (1U<<23);
*i ^= (1U<<23);
while(c)
{
sc = c << 1;
c = *i & sc;
*i ^= sc;
};
if (sgn != *i & (0xC0000000)) {
puts("Exponent overflow");
}
printf("%08X %f\n",*i,f);
return 0;
}
这是一个使用+
运算符的简单代码。它并没有假装涵盖浮点详细说明的所有方面。此解决方案向您显示单个精度浮点的esponent递增1,位23-29(30是指数符号),您获得乘以2。
此代码仅使用按位运算符来考虑符号位并避免最终指数溢出。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
int main()
{
float f = 23.45F;
uint32_t *i=(uint32_t *)(&f);
uint32_t app;
printf("%08X %f\n",*i,f);
app = *i & (0xC0000000); // copies bits 31 and 30
*i += (1U<<23);
*i &= ~(0xC0000000); // leave bits 31 and 30
*i |= app; // set original bits 31 and 30
printf("%08X %f\n",*i,f);
return 0;
}
下面的函数fpmul_by_2()
实现了所需的功能,假设'unsigned long'是32位整数类型,'float'是映射到IEEE-754'binary32'的32位浮点类型。进一步假设我们要模拟禁用异常的IEEE-754乘法,产生标准规定的掩蔽响应。
使用两个辅助函数,分别实现32位整数加法和相等比较。这个加法是基于二进制加法中的和和进位的定义(详细解释见this previous question),而等式比较利用了(a^b) == 0
iff a == b
这一事实。
浮点参数的处理需要广泛地区分三类操作数:非正规和零,法线,无穷大和NaN。由于我们使用二进制浮点格式操作,因此通过敲击指数来实现法线加倍。可能发生溢出,在这种情况下必须返回无穷大。除了将SNaN转换为QNaNs(这是IEEE-754规定的屏蔽响应)之外,无穷大和NaN保持不变。通过将有效数字加倍来处理非正规和零。处理零,次正规和无穷大可能会破坏符号位,因此参数的符号位强制结果。
下面包含的测试框架详尽地测试了fpmul_by_2()
,这在现代PC上只需要几分钟。我在运行Windows的x64平台上使用了英特尔编译器。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// assumptions:
// 'unsigned long' is a 32-bit type
// 'float' maps to IEEE-754 'binary32'. Exceptions are disabled
// add using definition of sum and carry bits in binary addition
unsigned long add (unsigned long a, unsigned long b)
{
unsigned long sum, carry;
carry = b;
do {
sum = a ^ carry;
carry = (a & carry) << 1;
a = sum;
} while (carry);
return sum;
}
// return 1 if a == b, else 0
int eq (unsigned long a, unsigned long b)
{
unsigned long t = a ^ b;
// OR all bits into lsb
t = t | (t >> 16);
t = t | (t >> 8);
t = t | (t >> 4);
t = t | (t >> 2);
t = t | (t >> 1);
return ~t & 1;
}
// compute 2.0f * a
unsigned long fpmul_by_2 (unsigned long a)
{
unsigned long expo_mask = 0x7f800000UL;
unsigned long expo_lsb = 0x00800000UL;
unsigned long qnan_mark = 0x00400000UL;
unsigned long sign_mask = 0x80000000UL;
unsigned long zero = 0x00000000UL;
unsigned long r;
if (eq (a & expo_mask, zero)) { // subnormal or zero
r = a << 1; // double significand
} else if (eq (a & expo_mask, expo_mask)) { // INF, NaNs
if (eq (a & ~sign_mask, expo_mask)) { // INF
r = a;
} else { // NaN
r = a | qnan_mark; // quieten SNaNs
}
} else { // normal
r = add (a, expo_lsb); // double by bumping exponent
if (eq (r & expo_mask, expo_mask)) { // overflow
r = expo_mask;
}
}
return r | (a & sign_mask); // result has sign of argument
}
float uint_as_float (unsigned long a)
{
float r;
memcpy (&r, &a, sizeof r);
return r;
}
unsigned long float_as_uint (float a)
{
unsigned long r;
memcpy (&r, &a, sizeof r);
return r;
}
int main (void)
{
unsigned long res, ref, a = 0;
do {
res = fpmul_by_2 (a);
ref = float_as_uint (2.0f * uint_as_float (a));
if (res != ref) {
printf ("error: a=%08lx res=%08lx ref=%08lx\n", a, res, ref);
return EXIT_FAILURE;
}
a++;
} while (a);
printf ("test passed\n");
return EXIT_SUCCESS;
}