浮動小数点のビット表現

共用体とビットフィールドを覚えたので、その応用。
IEEE754浮動小数点規格の単精度浮動小数点のビット表現を可視化してみる。
浮動小数点型のデータはビット演算できないので、float型とint型の共用体を使った。


実行結果はこんな感じ。

? -5.6875
+-+--------+-----------------------+
|S|Exponent|Fraction               |
|1|10000001|01101100000000000000000|
+-+--------+-----------------------+
31 30    23 22                    0
  Sign     = -1
  Exponent = 2 ( 129 - [bias:127] )
  Fraction = 1.01101100000000000000000

本なんかで読んでなんとなく理解していたけど、実際に表示させてみるとおおーって感じでちょっと感動した。ただし、このプログラムには問題があって、int が 32 ビットであることと、リトルエンディアンであることが制約になっている。エンディアン形式の問題については、共用体の中にビックエンディアン用のビットフィールドの並びにした構造体を加えれば解決できそうだけど、int のサイズの問題については解決方法がよく分からなかった。

#include <stdio.h>

#define STACK_SIZE  64
#define BIAS       127

typedef union _float {
  float    num;
  unsigned bitdata;
  struct _IEEE754 {
    unsigned fraction : 23;
    unsigned exponent :  8;
    unsigned sign     :  1;
  } IEEE754;
} Float;


void printbits(int n, int len)
{
  static char stack[STACK_SIZE];
  static int  sp = 0;
  
  int i;
  for (i = 0; i < len; i++, n >>= 1)
    stack[sp++] = n & 0x01;
  
  for (i = 0; i < len; i++)
    printf("%d", stack[--sp]);
}


void print_fp(Float f)
{
  unsigned sign, exponent, fraction;
  
  sign     = f.IEEE754.sign;
  exponent = f.IEEE754.exponent;
  fraction = f.IEEE754.fraction;
  
  printf("+-+--------+-----------------------+\n");
  printf("|S|exponent|Fraction               |\n");
  
  printf("|%d|", sign);
  printbits(exponent, 8);
  putchar('|');
  printbits(fraction, 23);
  putchar('|');
  putchar('\n');
  
  printf("+-+--------+-----------------------+\n");
  printf("31 30    23 22                    0 \n");
  printf("  Sign     = %d\n", sign ? -1 : 1);
  printf("  Exponent = %d ( %d - [bias:%d] )\n", exponent - BIAS, exponent, BIAS);
  printf("  Fraction = %d.", f.num == 0 ? 0 : 1);
  printbits(fraction, 23);
  
  putchar('\n');
}


int main(void)
{
  Float f;
  
  while (1) {
    while (1) {
      printf("? ");
      if (scanf("%f", &f.num) == 1) break;
      while (getchar() != '\n');
    }
    print_fp(f);
  }
  
  return 0;
}

倍精度の double 型を扱うには以下のような共用体を使えばよい。
でも、やっぱり int が 32ビット、リトルエンディアンが制約になっている。

typedef union _double {
  double num;
  struct _bitdata {
    unsigned L;
    unsigned H;
  } bitdata;
  struct _IEEE754 {
    unsigned fraction2: 32;
    unsigned fraction1: 20;
    unsigned exponent : 11;
    unsigned sign     :  1;
  } IEEE754;
} Double;