Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2010-06-14

IEEE754

17:10

なんか書いてみたくなったので。

Number を受け取って、[0,1,1,1,...] という配列に変換。

function toIEEE754(x) {
  var neg = x < 0;
  if (neg) x *= -1;

  if (x === 0) {
    var exp = 0, frac = 0;
  } else if (x === 1/0) {
    var exp = 2047, frac = 0;
  } else if (isNaN(x)) {
    var exp = 2047, frac = 2251799813685248; // == Math.pow(2, 51);
  } else {
    var exp = Math.floor(Math.log(x) / Math.LN2); // floor(log_2(x))
    var frac = Math.floor(x * Math.pow(2, 52 - exp)); // floor is not necessary
    exp += 1023; // exp part is 11 bytes, of which half is for negative exp, so raise by 2^10-1     
  }

  var bits = [];

  for(var i = 0, b; i < 52; i++){
    bits.unshift(b = frac % 2);
    frac = (frac - b) / 2;
  }
  bits.unshift('_'); // for debug

  for (var i = 0, b; i < 11; i++){
    bits.unshift(b = exp % 2);
    exp = (exp-b) / 2;
  }
  bits.unshift('_'); // for debug

  bits.unshift(neg*1);

  return bits;
}

function test(num) {
  document.write(num + '=' + toIEEE754(num).join('') + '<br>');
}

test(0);
test(2);
test(10.1);
test(-118.625);
test(0/0); // NaN
test(1/0); // Infinity
test(-1/0); // -Infinity
0=0_00000000000_0000000000000000000000000000000000000000000000000000
2=0_10000000000_0000000000000000000000000000000000000000000000000000
10.1=0_10000000010_0100001100110011001100110011001100110011001100110011
-118.625=1_10000000101_1101101010000000000000000000000000000000000000000000
NaN=0_11111111111_1000000000000000000000000000000000000000000000000000
Infinity=0_11111111111_0000000000000000000000000000000000000000000000000000
-Infinity=1_11111111111_0000000000000000000000000000000000000000000000000000

JavaScriptの大きな数と小さな数の仕組みを理解する 〜 IEEE754入門 〜 - 風と宇宙とプログラム のコードを使わせてもらって C で答え合わせ。(自分の環境では #include <stdint.h> が無いとエラーだった)

#include <stdio.h>
#include <stdint.h>
#include <math.h>

char* getbitstr(double val)
{
  static char buf[67];
  uint64_t x = *(uint64_t*)&val;
  uint64_t flg = 1LL << 63; 
  int j = 0;
  int i = 0;
  for (; i < 64; i++) {
    buf[j++] = (x & flg) ? '1' : '0';
    if (i == 0 || i == 11) {
      buf[j++] = '_';
    }
    flg >>= 1;
  }
  buf[j] = 0;
  return buf;
}

#define SHOW_BINARY(x) printf(#x "=%s\n", getbitstr(x))

int main() {
  SHOW_BINARY(0);
  SHOW_BINARY(2);
  SHOW_BINARY(10.1);
  SHOW_BINARY(-118.625);
  SHOW_BINARY(NAN);
  SHOW_BINARY(INFINITY);
  SHOW_BINARY(-INFINITY);
}
0=0_00000000000_0000000000000000000000000000000000000000000000000000
2=0_10000000000_0000000000000000000000000000000000000000000000000000
10.1=0_10000000010_0100001100110011001100110011001100110011001100110011
-118.625=1_10000000101_1101101010000000000000000000000000000000000000000000
NAN=0_11111111111_1000000000000000000000000000000000000000000000000000
INFINITY=0_11111111111_0000000000000000000000000000000000000000000000000000
-INFINITY=1_11111111111_0000000000000000000000000000000000000000000000000000

あってるっぽい。


そういえば

mindcat さんのところに書いてあるように整数部分を2で割っていき、少数部分を2倍していく方法ではなくて、

var frac = Math.floor(x * Math.pow(2, 52 - exp));

とした理由をすっかり忘れてた。

Opera の float.toString(2) が壊れているので、整数に変換してしまえばそのまま toString(2) が使えるじゃん、ってことでこういうふうにしたのだった。

ゼロと NaNInfinity を除いてあればこれだけで 0_11111111111_0000... のような文字列にできるはず。

function toIEEE754(x) {
  var neg = x < 0;
  if (neg) x *= -1;

  var exp = Math.floor(Math.log(x) / Math.LN2);
  var frac = Math.floor(x * Math.pow(2, 52 - exp));
  exp += 1023;

  return (neg*1) + '_' + ('0000000000'+exp.toString(2)).slice(-11) + '_' + frac.toString(2).slice(1);
}

最初 Math.log(x) / Math.LN2 | 0 とやってたけど、それだと exp がマイナスになったときに問題があった。小数点以下切り捨てか、超えない整数かの違いで。もちろん + 1023 を先にやっておいたらその心配はないけど、その場合は Math.pow(2, 52 + 1023 - exp) としないとだめ。

frac.toString(2).slice(1) はつまり最初の1は自明なので省いてあるということ。上の Wikipedia のページやここに書いてある「けち表現」というやつ。IEEE754 表現の 64bit float と言った場合は「けち表現」と思っていいんだろうか。

トラックバック - http://javascript.g.hatena.ne.jp/edvakf/20100614
 |