エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

浮動小数点数値をバイト列にする

今週、仕事場で協業している某社からリリースされてきたコードを見ていて。次のような雰囲気のコードを見つけました。実際に目にしたものからだいぶ書き換えてますが、こんな雰囲気ということで。

    // double value = ...;
    UINT64* p = reinterpret_cast<UINT64*>(&value);

    do_something((*p >> 56) & 0xff)
    do_something((*p >> 48) & 0xff)
    do_something((*p >> 40) & 0xff)
    do_something((*p >> 32) & 0xff)
    do_something((*p >> 24) & 0xff)
    do_something((*p >> 16) & 0xff)
    do_something((*p >>  8) & 0xff)
    do_something( *p        & 0xff)

double型の値のビット表現を上位バイトから順に操作しようとしているわけです。なんとも危なっかしいことしてるなぁ、と。
ここでUINT64型というのはいまの環境ではunsigned long long int型の別名になっているんですが、設定によってはクラスとして定義される場合もあって、このような書き方はだいぶ危険に見えます(double型もついても、IEEEの倍精度(64ビット)を採用している場合が多いようですが、そうでない場合もあるわけですから)。


これを見て、じゃぁ安全にdouble型の値を意図した順序のバイト列に変換できるか考えてみたんですが、これが意外と難しい。
一番単純なのは、char型(あるいはunsigned char型)の配列にキャストすることですが…

    unsigned char (&a)[sizeof(double)] =
        reinterpret_cast<unsigned char (&)[sizeof(double)]>(value);

…今度はエンディアンの影響を受けます。
次のようなコードを書けばエンディアンを識別できるので、ビッグエンディアンかリトルエンディアンかでキャストされた配列にアクセスする順序を変えればいいのですが、スマートでない。

bool isBigendian()
{
    static const int n = 1;
    return reinterpret_cast<const char&>(n) == 0;
}


ふだん浮動小数点数値を使わないので気がつきませんでしたが、有効桁数以外でも扱いに気をつかうことに気がついた一件でした。


おまけ。
任意のデータをビッグエンディアンでバイト列にするイテレータを書いてみた。以下コード。

class Iterator
{
public:
    enum Endian
    {
        big,
        little
    };

    static Endian getEndian()
    {
        static const int n = 1;
        return (reinterpret_cast<const char&>(n) == 0) ? big : little;
    }

    template<typename T>
    explicit Iterator(const T& value) : value_(reinterpret_cast<const unsigned char*>(&value)), size_(sizeof(T)), pos_(0), endian_(getEndian())
    {
    }

    bool hasNext() const
    {
        return pos_ < size_;
    }

    unsigned char next()
    {
        if(endian_ == big)
        {
            return value_[pos_++];
        }
        else
        {
            return value_[size_ - ++pos_];
        }
    }

private:
    const unsigned char* value_;
    const int            size_;
    int                  pos_;
    const Endian         endian_;
};

#include <iostream>
#include <iomanip>

int main(int, char* [])
{
    std::cout << (Iterator::getEndian() == Iterator::big ? "BIG" : "LITTLE") << std::endl;

    std::cout << std::hex;

    double r = 0.12345678;

    for(Iterator i(r); i.hasNext(); )
    {
        std::cout << std::setw(2) << static_cast<unsigned int>(i.next()) << " ";
    }
    std::cout << std::endl;

    int n = 0x12345678;

    for(Iterator i(n); i.hasNext(); )
    {
        std::cout << std::setw(2) << static_cast<unsigned int>(i.next()) << " ";
    }
    std::cout << std::endl;

    return 0;
}


実行結果。

LITTLE
3f bf 9a dd 10 91 c8 95 
12 34 56 78 
BIG
3f bf 9a dd 10 91 c8 95 
12 34 56 78