1. 程式人生 > >CS:APP3e Homework 2.97 關於整型轉單精度浮點數的方法討論

CS:APP3e Homework 2.97 關於整型轉單精度浮點數的方法討論

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

啟示與思考

這段時間在學習計算機系統的相關知識,涉及到一些底層二進位制實現原理,包括一些整數、浮點數編碼與轉換。本題就是一個典型的例子,涉及到許多相關知識。儘管筆者的程式碼很多地方寫的還是比較幼稚,但是在一定程度上幫助筆者加深對於計算機底層編碼實現的一些理解。
具體到這個題,我們要學會利用書中給出的原理,嘗試去自己實現,並靈活運用各種位及邏輯操作,善作總結,這樣有助於我們更好的理解書中內容。