【ICS】CS:APP3e Homework 2.97 关于整型转单精度浮点数的方法讨论

关于整型转单精度浮点数的方法讨论

CS:APP即著名的计算机系统书籍《Computer Systems: A Programmer’s Perspective》(《深入理解计算机系统》),本篇博客基于其第三版第二章课后题2.97,讨论基于移位操作的整型转单精度浮点数底层实现,提供了笔者自己的原创代码以及网络上一些现有的实现代码。

CS:APP原题

2.97 遵循位级浮点编码规则, 实现具有如下原型的函数:
/*Compute (float)i */
float_bits float_i2f(int i);
对于整数i,这个函数计算(float) i 的位级表示。
测试你的函数,对参数f可以取的所有223个值求值,将结果与你使用机器的浮点运算得到的结果相比较。

题目分析

作为该章课后作业的最后一题,以及书中标注的四星难度,这个题还是需要花费一些时间才能做出来的。题目要求读者自己通过一些逻辑、位运算这样较为底层的操作实现C语言中从整型到单精度浮点型类型转换,而不能使用浮点数据类型、运算或者常数。其他限制这里不再列举,具体参照原书。
C语言中从整型至单精度浮点数的强制类型转换,并非像无符号数和补码转换位不变,直接映射,而是转换成对应的值。
所以,对于两者的转换,我们能够采用的方法就是根据IEEE754标准中浮点数的构成原则与整数补码的位关系找到规律,从而进行映射。
关于IEEE754标准及补码的二进制知识,本文不再赘述。

实现代码

这里首先给出笔者经过一段时间研究与测试得到的最终代码。

#include <stdio.h>

typedef unsigned float_bits;

/* Compute (float)i */
float_bits float_i2f(int i);
float u2f(unsigned x);

int main()
{
    int i = 0;
    printf("If you want to stop, just type 0.\n");
    do{
        printf("Please input an integer number:");
        scanf("%d",&i);
        printf("%g\t%g\n",(float)i,u2f(float_i2f(i)));
    }while(i!=0);
    return 0;
}

float_bits float_i2f(int i)
{
    if (i == 0)
        return 0;
    /*因为无符号数和浮点数均可用0x00000000表示0,所以当整数i为0时可直接返回*/
    unsigned s = i>>31<<31;
    /*通过左移和右移使除符号位外均为0,以便最后的或操作*/
    if ((s>>31) == 1)
        i = ~(i-1);
    /*若输入的整数为负数,只需要单独讨论其符号位。因为对于浮点数来讲相反数的其它位是相同的,
    所以我们先把其按照补码规则转化为其相反数(正数),然后就可以和正数同样处理,最后通过
    或操作可以保证其符号位*/
    unsigned m_bits = 0;//补码除去第一位后的有效位数(原因参照浮点数规格化数的表示)
    unsigned e;//阶码
    unsigned m = (unsigned)i;//未经处理的尾数
    unsigned isLow = 0;//确定尾数长短位置的Flag
    int j = 0;
    for (j = 0; j<32; j++)
    {
        if (i>>(31-j))
        {
            m_bits = 32-j;
            break;
        }
    }
    /*确定补码的有效位数,通过循环移位,直到移位后结果不是0,从而确定其有效长度,忽略前面
    的0位。如果是负数,因为之前我们已经将其最高位通过转为为对应相反数,故不会因为最高位而
    影响有效位数。 */
    m_bits--;
    m = m<<(32-m_bits)>>(32-m_bits);
    /*根据规格化数构成方法排除第一位影响*/
    e = (m_bits+127)<<23;
    /*根据规格化数构成方法确定阶码*/
    if (m_bits <23)
        isLow = 1;
    if (isLow)
        m = m <<(23-m_bits);
    else
        m = m >>(m_bits-23);
    /*对于单精度浮点数,符号位1位,阶码位8位,尾数位23位,以此作为参照选择左移右移,得到最
    终正确位置的尾数*/
    return s|e|m;
    /*通过或操作,合并符号位、阶码、尾数*/
}

float u2f(unsigned x)
{
    return *(float*)&x;
     /*位不变的将无符号数转化为对应单精度浮点数*/
}

原理解释

其实这个题的原理,书中已经给出。
《深入理解计算机系统 第三版》P82
理解了这个,我们只需要把这个原理“翻译”成C语言代码。简单的说就是获取对应的尾数以及阶码,并注意一下对于正负的讨论。归根到底还是考察对IEEE754标准的理解。
具体的解释可以参照给出的代码注释。

其他方法

笔者在起初做题时尝试去参考网上一些方法,后来觉得尝试去理解其他人的方法不如自己试试能否实现。当然,笔者的代码中也有一些现成思路的影子。尽管还没有研究明白,但这里引用下网上其他的方法,供大家参考。
方法一:

#include <stdio.h>
#include <limits.h>

typedef unsigned float_bits;

float_bits float_i2f(int i);
unsigned bits_length(int x);
unsigned bits_mask(unsigned x);
float u2f(unsigned x);

int main()
{
    int i = 123;
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = -123;
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = 0;
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = (~0);
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = (1 << 31);
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));

    return 0;
}

float_bits float_i2f(int i)
{
    unsigned sign, exp, frac, bias;
    bias = 127;
    
    if (i == 0) 
        return 0;
    if (i == INT_MIN) { // -1
        sign = 1;
        exp = 31 + bias; 
        frac = 0; // -1是整数,没有小数部分
        return sign << 31 | exp << 23 | frac;
    }

    sign = i > 0 ? 0 : 1;
    
    if (i < 0)
        i = -i;
    
    unsigned bits_num = bits_length(i);
    unsigned fbits_num = bits_num - 1;
    unsigned fbits;

    exp = bias + fbits_num;

    fbits = i & bits_mask(1 << fbits_num - 1);
    
    if (fbits_num <= 23)
        frac = fbits << (23 - fbits_num);
    else {
        unsigned offset = fbits_num - 23;
        frac = fbits >> offset;
        unsigned round_mid = 1 << (offset - 1);
        unsigned round_part = fbits & bits_mask(1 << offset - 1);
        if (round_part > round_mid)
            ++frac;
        else if (round_part == round_mid) {
            if (frac & 0x1)
                ++frac;
        }
    }
    return sign << 31 | exp << 23 | frac;
}

unsigned bits_length(int x)
{
    unsigned ux = (unsigned) x;
    unsigned count = 0;
    while (ux > 0) {
        ux >>= 1;
        ++count;
    } 
    return count;
}

unsigned bits_mask(unsigned x)
{
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;

    return x;
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

原文地址:https://www.cnblogs.com/chritran-dlay/p/9279184.html

方法二:

/*
 * float-i2f.c
 */
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include "float-i2f.h"

/*
 * Assume i > 0
 * calculate i's bit length
 *
 * e.g.
 * 0x3 => 2
 * 0xFF => 8
 * 0x80 => 8
 */
int bits_length(int i) {
  if ((i & INT_MIN) != 0) {
    return 32;
  }

  unsigned u = (unsigned)i;
  int length = 0;
  while (u >= (1<<length)) {
    length++;
  }
  return length;
}

/*
 * generate mask
 * 00000...(32-l) 11111....(l)
 *
 * e.g.
 * 3  => 0x00000007
 * 16 => 0x0000FFFF
 */
unsigned bits_mask(int l) {
  return (unsigned) -1 >> (32-l);
}

/*
 * Compute (float) i
 */
float_bits float_i2f(int i) {
  unsigned sig, exp, frac, rest, exp_sig /* except sig */, round_part;
  unsigned bits, fbits;
  unsigned bias = 0x7F;

  if (i == 0) {
    sig = 0;
    exp = 0;
    frac = 0;
    return sig << 31 | exp << 23 | frac;
  }
  if (i == INT_MIN) {
    sig = 1;
    exp = bias + 31;
    frac = 0;
    return sig << 31 | exp << 23 | frac;
  }

  sig = 0;
  /* 2's complatation */
  if (i < 0) {
    sig = 1;
    i = -i;
  }

  bits = bits_length(i);
  fbits = bits - 1;
  exp = bias + fbits;

  rest = i & bits_mask(fbits);
  if (fbits <= 23) {
    frac = rest << (23 - fbits);
    exp_sig = exp << 23 | frac;
  } else {
    int offset = fbits - 23;
    int round_mid = 1 << (offset - 1);

    round_part = rest & bits_mask(offset);
    frac = rest >> offset;
    exp_sig = exp << 23 | frac;

    /* round to even */
    if (round_part < round_mid) {
      /* nothing */
    } else if (round_part > round_mid) {
      exp_sig += 1;
    } else {
      /* round_part == round_mid */
      if ((frac & 0x1) == 1) {
        /* round to even */
        exp_sig += 1;
      }
    }
  }

  return sig << 31 | exp_sig;
}

原文地址:https://dreamanddead.gitbooks.io/csapp-3e-solutions/chapter2/2.97.html

启示与思考

这段时间在学习计算机系统的相关知识,涉及到一些底层二进制实现原理,包括一些整数、浮点数编码与转换。本题就是一个典型的例子,涉及到许多相关知识。尽管笔者的代码很多地方写的还是比较幼稚,但是在一定程度上帮助笔者加深对于计算机底层编码实现的一些理解。
具体到这个题,我们要学会利用书中给出的原理,尝试去自己实现,并灵活运用各种位及逻辑操作,善作总结,这样有助于我们更好的理解书中内容。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值