long doubleと拡張倍精度浮動小数点数フォーマット

はじめに

x86系では、long doubleが内部80bit精度の拡張倍精度浮動小数点数フォーマットが採用されている。おそらくx87系命令の内部精度が80bitなのと合わせてあるのだと思われる。それをざっと確認しておく。

拡張倍精度浮動小数点数フォーマットとは

Wikipediaのこの図の通りなんだけれど、要するに

  • 符号 1bit
  • 指数部 15bit
  • 仮数部 64bit

の合計80bitの表現になっている。ここで気をつけなくてはならないのは、この仮数部はいわゆる「ケチ表現」になっておらず、正規化した数字の最上位ビット1を省略しないこと。従って、単精度、倍精度と仮数部のbit数を比較するなら、63bitの精度になっている。

long double

環境に依存するのだろうが、手元のMacやx86 Linux+GCCの組み合わせでは、long doubleのサイズは16バイト、精度は80bitの拡張倍精度浮動小数点数フォーマットになっていた。

拡張倍精度浮動小数点数フォーマットを見てみる

というわけでそのビット表現を見てみる。こんなコードを書いてみよう。

#include <stdio.h>

template <class T>
void bitdump(T a){
  char *x = (char*)(&a);
  int count = 0;
  int s = 8;
  int s2 = 12;
  if(sizeof(T)==16){
    s = 10;
    s2 = 16;
  }else if(sizeof(T) == 4){
    s = 4;
    s2 = 9;
  }
  for(int i=0;i<s;i++){
    for(int j=0;j<8;j++){
      if(x[s-i-1] & (1<<(7-j)))printf("1");
      else printf("0");
      count++;
      if(count==1 || count==s2)printf(" ");
    }
  }
  printf("\n");
}

これはいくつかの浮動小数点型のビットダンプを取る超手抜きコードである。これをこんな呼び方をしてみる。

int
main(void){
  bitdump(1.125F);
  bitdump(1.125);
  bitdump(1.125L);
}

ケチ表現の有無がわかりやすいように、1.125を表現してみた。1.125は1 + 1/8なので、2進数で表現すると「1.001」となる。これをfloatdoublelong doubleの型でビットダンプすると、こんな結果になる。

$ ./a.out  
0 01111111 00100000000000000000000
0 01111111111 0010000000000000000000000000000000000000000000000000
0 011111111111111 1001000000000000000000000000000000000000000000000000000000000000

仮数部が、floatdoubleは最上位ビットが省略された「001」に、long doubleは省略されていない1001になっていることがわかる。

同様に、1.125の2倍である2.25 (二進表記 10.01)をそれぞれダンプしてみると

0 10000000 00100000000000000000000
0 10000000000 0010000000000000000000000000000000000000000000000000
0 100000000000000 1001000000000000000000000000000000000000000000000000000000000000

と、指数部の下駄に1足されて(小数点を左に一つ移動し)、整数部が1になるように調整されてから、floatdoubleはケチ表現で「001」に、long doubleは最上位の整数部を省略せずに「1001」になっていることがわかる。

まとめ

x86+GCCの組み合わせにおいてlong doubleが80bitの拡張倍精度浮動小数点フォーマットになっていることを確認した。拡張倍精度浮動小数点フォーマットは単精度と倍精度のフォーマットと同じだが、ケチ表現の有無が異なる。

ちなみにlong doubleのサイズ(sizeof(long double)が返す値)は16なので、16バイトを利用するのだが、内部的には10バイトしか使っておらず、残りの6バイトの値は何も保証されない。ゼロクリアもされないので、long doubleの指すポインタを16バイト分ダンプすると、6バイト分は実行の度に値が変わるので注意されたい1

  1. たまたまそのデータが意味ありげなビット列だったこともあって、筆者はそれで「あれ?どうして余計なbitが立ってるんだろ?」としばらく悩んでしまった・・・