浮動小数点数を2の乗数で割る

a/bをする場合、bが2の乗数であれば「aの指数 - log2(b)」で除算ができます。

#include <iostream>
#include <bitset>
#include <cstdint>
#include <cassert>
#include <cmath>

union SingleFloat {
    float value;
    struct {
        int fraction : 23;
        int exponent : 8;
        bool sign : 1;
    } parts;
};

SingleFloat divide(SingleFloat a, std::uint32_t b)
{
    // 2の乗数のみ受け付ける
    assert(std::bitset<32>(b).count() == 1);

    SingleFloat result;
    result.parts.fraction = a.parts.fraction;
    result.parts.exponent = a.parts.exponent - std::log2(b);
    result.parts.sign = a.parts.sign;
    return result;
}

SingleFloat make_by_float(float x)
{
    SingleFloat result;
    result.value = x;
    return result;
}

int main()
{
    // 8/4 == 2
    std::cout << divide(make_by_float(8.0f), 4).value << std::endl;

    // 9/4 == 2.25
    std::cout << divide(make_by_float(9.0f), 4).value << std::endl;

    // 24/8 == 3
    std::cout << divide(make_by_float(24.0f), 8).value << std::endl;
}

出力:

2
2.25
3

ここでは、aが0、非正規化数、無限大、NaNの場合については考慮していません。

追記(2016/12/27 17:45)

もうちょっと真面目に実装し、上述した未考慮のケースに対応したコードはこちら:

#include <iostream>
#include <bitset>
#include <cstdint>
#include <cassert>
#include <cmath>

union SingleFloat {
    float value;
    struct {
        int fraction : 23;
        int exponent : 8;
        bool sign : 1;
    } parts;
};

SingleFloat make_by_float(float x)
{
    SingleFloat result;
    result.value = x;
    return result;
}

SingleFloat divide(SingleFloat a, std::uint32_t b)
{
    // 2の乗数のみ受け付ける
    assert(std::bitset<32>(b).count() == 1);

    if (std::isnan(a.value))
        return make_by_float(a.value);

    if (std::isinf(a.value))
        return make_by_float(a.value);

    int exponent_x = a.parts.exponent;
    int exponent_y = std::log2(b);
    if (exponent_x <= exponent_y) {
        // 0や非正規化数のように指数が小さいaをなにかの値で割るとアンダーフローするので、0を返す
        return make_by_float(0.0f);
    }
    else {
        SingleFloat result;
        result.parts.fraction = a.parts.fraction;
        result.parts.sign = a.parts.sign;
        result.parts.exponent = exponent_x - exponent_y;
        return result;
    }
}

int main()
{
    std::cout << " 8/4 = " << divide(make_by_float(8.0f), 4).value << std::endl;
    std::cout << " 9/4 = " << divide(make_by_float(9.0f), 4).value << std::endl;
    std::cout << "24/8 = " << divide(make_by_float(24.0f), 8).value << std::endl;

    std::cout << " 0/2 = " << divide(make_by_float(0.0f), 2).value << std::endl;
    std::cout << "-0/2 = " << divide(make_by_float(-0.0f), 2).value << std::endl;

    using limits = std::numeric_limits<float>;
    std::cout << "denorm_min/2 = " << divide(make_by_float(limits::denorm_min()), 2).value << std::endl;
    std::cout << "NaN/2 = " << divide(make_by_float(limits::quiet_NaN()), 2).value << std::endl;
    std::cout << "inf/2 = " << divide(make_by_float(limits::infinity()), 2).value << std::endl;
    std::cout << "-inf/2 = " << divide(make_by_float(-limits::infinity()), 2).value << std::endl;
}

出力:

 8/4 = 0
 9/4 = 0
24/8 = 0
 0/2 = 0
-0/2 = 0
denorm_min/2 = 0
NaN/2 = nan
inf/2 = inf
-inf/2 = -inf

参照

編集履歴

  • 2018/03/01 11:51 : コメント欄での指摘を受け、-limits::quiet_NaN()のように負数のNaNを使用していたところの符号を削除