固定小数点の再発明

Adafruit TrinketのようなROMが数キロバイトしかないマイコンでは、float型を用いると浮動小数点のライブラリがROM容量を圧迫してしまう。そこで固定小数点クラスを作ってみた。

【2019/05/24 追記】
Arduino IDEでビルドしてみたところ、AVRマイコンではfloat使うよりROMサイズが増えてしまった。クラス化はあきらめてCだけで書いたらいちおうfloatよりROM削減にはなったけど、それも思ったほどの効果でもなかった。8ビットマイコンで64ビットの乗算をするためけっこうROMを食うようだ。残念な結果である。
固定小数点計算ふたたび - 滴了庵日録

fixed24クラスの仕様

  • 内部表現は符号1ビット、整数部7ビット、小数部24ビットの全32ビット。
  • 代入(=)、比較(==, != , >, <, >=, <=)、四則演算(+, -, *, /) の演算子が使える。
  • +=のような糖衣な演算子はサポートしない。
  • 生値(32ビットの内部表現) または (分子, 分母) からインスタンス生成する。
  • 整数への変換にはtoInt()メソッドを用いる。(キャスト演算子は暗黙のキャストが危険なため)
  • デバッグ用にdoubleへのキャスト演算子を用意。(通常は無効)
  • 生値(32ビットの内部表現)へのアクセスを許す。(どうせこんなクラス使うのは泥臭い世界)

fixed24.h

#ifndef _FIXED24_H
#define _FIXED24_H

#include<stdint.h>

class fixed24
{
public:
    // constructors
    fixed24(void) {
        value = 0;
    }
    fixed24(int32_t rawval){
        value = rawval;
    }
    fixed24(int32_t numer, int32_t denom){
        int64_t x = ((int64_t)numer << 24) / (int64_t)denom;
        value = (int32_t)x;
    }

    // operators
    void  operator =  (fixed24 x){ value = x.value; }
    bool  operator == (fixed24 x) const { return (value == x.value); }
    bool  operator != (fixed24 x) const { return (value != x.value); }
    bool  operator >  (fixed24 x) const { return (value >  x.value); }
    bool  operator <  (fixed24 x) const { return (value <  x.value); }
    bool  operator >= (fixed24 x) const { return (value >= x.value); }
    bool  operator <= (fixed24 x) const { return (value <= x.value); }
    fixed24 operator + (fixed24 x) const { return fixed24(value + x.value); }
    fixed24 operator - (fixed24 x) const { return fixed24(value - x.value); }
    fixed24 operator * (fixed24 x) const {
        int64_t a = (int64_t)value;
        int64_t b = (int64_t)x.value;
        a *= b;
        a >>= 24;
        return fixed24((int32_t)a);
    }
    fixed24 operator * (int32_t x) const {
        int32_t y = value * x;
        return fixed24((int32_t)y);
    }
    fixed24 operator / (fixed24 x) const {
        int64_t a = (int64_t)value << 32;
        int64_t b = (int64_t)x.value;
        a /= b;
        a >>= 8;
        return fixed24((int32_t)a);
    }
    fixed24 operator / (int32_t x) const {
        int32_t y = value / x;
        return fixed24((int32_t)y);
    }
    
    // convert to integer (-128 to +127)
    // not cast operator because implicit cast is dangerous
    int toInt() const { return (int)(value >> 24); }

#ifdef _DEBUG_FIXED24
    // cast to double (for debug)
    operator double () const {
        double x = (double)value / (double)(1UL << 24);
        return x;
    }
#endif
    
    // raw value
    int32_t    value;
};

#endif

テスト

#include <stdio.h>

#define _DEBUG_FIXED24
#include "fixed24.h"

int main(void)
{
    fixed24 x = fixed24(10, 1);   // 10/1 = 10
    fixed24 y = fixed24(20, 100); // 20/100 = 0.20
    fixed24 z = fixed24(0x01000000 / 2); // 1/2 = 0.5

    printf("x = %d (%08X)\n", x.toInt(), x.value);
    printf("y = %f (%08X)\n", (double)y, y.value);
    printf("z = %f (%08X)\n", (double)z, z.value);

    z = x + y;
    printf("%f + %f = %f (%08X)\n", (double)x, (double)y, (double)z, z.value);
    z = x - y;
    printf("%f - %f = %f (%08X)\n", (double)x, (double)y, (double)z, z.value);
    z = x * y;
    printf("%f * %f = %f (%08X)\n", (double)x, (double)y, (double)z, z.value);
    z = x / y;
    printf("%f / %f = %f (%08X)\n", (double)x, (double)y, (double)z, z.value);
    z = x * 2;
    printf("%f * 2 = %f (%08X)\n", (double)x, (double)z, z.value);
    z = x / 2;
    printf("%f / 2 = %f (%08X)\n", (double)x, (double)z, z.value);

    x = fixed24(1, 1);
    y = fixed24(1, 1);
    printf("%f == %f : %s\n", (double)x, (double)y, (x == y) ? "TRUE" : "FALSE");
    printf("%f != %f : %s\n", (double)x, (double)y, (x != y) ? "TRUE" : "FALSE");
    y = fixed24(2, 1);
    printf("%f == %f : %s\n", (double)x, (double)y, (x == y) ? "TRUE" : "FALSE");
    printf("%f != %f : %s\n", (double)x, (double)y, (x != y) ? "TRUE" : "FALSE");

    return 0;
}