久しぶりに format クラスを更新

format クラスの見直し

  • format クラスは、組み込みマイコン用に、危険な printf を置き換える為に、boost::format の縮小版のようなノリでコツコツ実装してきた。
  • 組み込みマイコンで C++ を使う場合に、重宝するものと思う。
  • 組み込みマイコンでは、リソースの問題で、iostream などを使う事が難しい。
  • かと言って、C++ で printf を使うのも気が引ける。
  • printf は可変引数を使っているので、致命的な欠点を持っている。
  • 通常、組み込みマイコンを使った製品では、内部で printf を使う事はない。
  • 自前 format クラスは、printf を使うよりコンパクトになるものと思う。
  • 細かい部分を見直し、常に品質を向上するようにしている。
  • 今まで、%g の対応をしていなかったので、今回は、それに注力した。
  • テストケースも同時にアップデートしてより広範囲に仕様を満足するようにした。

浮動小数点の扱い

  • printf の場合、浮動小数点は、内部的には double 扱いになっている。
  • format クラスは、リソースを節約する為、float 扱いだが、有効桁が8桁くらいなら精度も問題なく運用出来ると思う。
  • float は IEEE-754 32 ビットフォーマットとなっている。

何と言っても簡単に使える

  • format クラスは、「format.hpp」のヘッダーのみで、インクルードするだけで使える。
  • ライブラリのリンクも必要無い。
  • 名前空間として「utils」を使っている。
  • boost::format は、フォームとパラメーターが食い違う場合、例外を投げる、自前 format は、内部でエラーコードで対応している。
  • C++ のオペレーター機能を使い、パラメーターを与えているので、安全で、printf に近いコンビニエンス性がある。
#include "format.hpp"

int main()
{
    float value = 1.234f;
    utils::format("Value: %4.3f\n") % value;
}
Value: 1.234

拡張機能

  • 拡張機能として、二進表示「%b」を追加してある。
  • ポインターのアドレスを表示する場合「%p」を使う。

標準出力

  • format は、標準出力として、stdout を使う。
  • 組み込みマイコンでは、write 関数に対して、stdout のハンドルを使い出力する。
  • RX マイコンでは、syscalls.c にその実装があり、内部で put_char 関数に文字を出力するようになっている。
  • メイン部などで、put_char 関数を "C" 用に extern しておいて、その関数から、SCI などに送れば、シリアル出力できる。

他の使い方

    typedef basic_format<stdout_buffered_chaout<256> > format;
    typedef basic_format<stdout_chaout> nformat;
    typedef basic_format<memory_chaout> sformat;
    typedef basic_format<null_chaout> null_format;
    typedef basic_format<size_chaout> size_format;

上記のような「typedef」があり、文字列出力の他、サイズだけ取得するなど、色々な応用が出来る。

※通常、format は小さなバッファを通して、標準出力しているので、「改行」を送らない場合、明示的に「flush」を呼んで、バッファに残った文字を掃き出す必要がある。

今回のアップデートでは、%g(有効桁自動表示)対応がメイン

長い間、浮動小数点の「%g」(自動)フォームに対応していなかったので、それに対応した。
※「Test20」がその検査にあたる。

まだ、テストケースが不十分な感じがするので、バグがあるものと思うが、とりあえず大丈夫そうなので、Github にプッシュした。

Github: format_class

テストケースは、以下のように20パターンあるけど、まだ足りないので、追々追加して、強固なものにしようと思う。

Test01, output buffer size check: (0)  Ref: '0123456789' <-> Res: '0123456'  Pass.
Test02(0), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(1), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test02(2), decimal check. Ref: 'form=     12345678' <-> Res: 'form=     12345678'  Pass.
Test02(3), decimal check. Ref: 'form=    -12345678' <-> Res: 'form=    -12345678'  Pass.
Test02(4), decimal check. Ref: 'form=000012345678' <-> Res: 'form=000012345678'  Pass.
Test02(5), decimal check. Ref: 'form=-00012345678' <-> Res: 'form=-00012345678'  Pass.
Test02(6), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(7), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test02(8), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(9), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test03(0), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test03(1), octal check. Ref: 'form=   1245667' <-> Res: 'form=   1245667'  Pass.
Test03(2), octal check. Ref: 'form=001245667' <-> Res: 'form=001245667'  Pass.
Test03(3), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test03(4), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test04(0), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test04(1), binary check. Ref: 'form=    10101110' <-> Res: 'form=    10101110'  Pass.
Test04(2), binary check. Ref: 'form=0000010101110' <-> Res: 'form=0000010101110'  Pass.
Test04(3), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test04(4), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test05(0), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(1), hex-dedcimal check. Ref: 'form=  12a4bf9c' <-> Res: 'form=  12a4bf9c'  Pass.
Test05(2), hex-dedcimal check. Ref: 'form=012a4bf9c' <-> Res: 'form=012a4bf9c'  Pass.
Test05(3), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(4), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(5), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test05(6), hex-dedcimal check. Ref: 'form=  12A4BF9C' <-> Res: 'form=  12A4BF9C'  Pass.
Test05(7), hex-dedcimal check. Ref: 'form=012A4BF9C' <-> Res: 'form=012A4BF9C'  Pass.
Test05(8), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test05(9), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test06(0), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(1), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test06(2), positive decimal check. Ref: 'form=     12345678' <-> Res: 'form=     12345678'  Pass.
Test06(3), positive decimal check. Ref: 'form=   4282621618' <-> Res: 'form=   4282621618'  Pass.
Test06(4), positive decimal check. Ref: 'form=000012345678' <-> Res: 'form=000012345678'  Pass.
Test06(5), positive decimal check. Ref: 'form=004282621618' <-> Res: 'form=004282621618'  Pass.
Test06(6), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(7), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test06(8), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(9), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test07(0), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(1), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(2), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(3), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(4), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(5), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(6), floating point check. Ref: 'form=1.0063' <-> Res: 'form=1.0063'  Pass.
Test07(7), floating point check. Ref: 'form=-1.0063' <-> Res: 'form=-1.0063'  Pass.
Test07(8), floating point check. Ref: 'form=1.0063' <-> Res: 'form=1.0063'  Pass.
Test07(9), floating point check. Ref: 'form=-1.0063' <-> Res: 'form=-1.0063'  Pass.
Test07(10), floating point check. Ref: 'form=     1' <-> Res: 'form=     1'  Pass.
Test07(11), floating point check. Ref: 'form=    -1' <-> Res: 'form=    -1'  Pass.
Test08(0), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(1), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(2), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(3), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(4), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(5), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(6), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(7), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(8), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(9), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(10), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(11), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(12), floating point (exponent) check. Ref: 'form=1.0250e+05' <-> Res: 'form=1.0250e+05'  Pass.
Test08(13), floating point (exponent) check. Ref: 'form=3.2500e-08' <-> Res: 'form=3.2500e-08'  Pass.
Test08(14), floating point (exponent) check. Ref: 'form=-1.0750e+05' <-> Res: 'form=-1.0750e+05'  Pass.
Test08(15), floating point (exponent) check. Ref: 'form=-6.2500e-08' <-> Res: 'form=-6.2500e-08'  Pass.
Test08(16), floating point (exponent) check. Ref: 'form=1.0250e+05' <-> Res: 'form=1.0250e+05'  Pass.
Test08(17), floating point (exponent) check. Ref: 'form=3.2500e-08' <-> Res: 'form=3.2500e-08'  Pass.
Test08(18), floating point (exponent) check. Ref: 'form=-1.0750e+05' <-> Res: 'form=-1.0750e+05'  Pass.
Test08(19), floating point (exponent) check. Ref: 'form=-6.2500e-08' <-> Res: 'form=-6.2500e-08'  Pass.
Test09(0), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test09(1), text check. Ref: '   AbcdEFG' <-> Res: '   AbcdEFG'  Pass.
Test09(2), text check. Ref: '  AbcdEFG' <-> Res: '  AbcdEFG'  Pass.
Test09(3), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test09(4), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test10, format poniter to nullptr, error code: (1)  Pass.
Test11, different type, error code: (3) '%s' (target float)  Pass.
Test11, different type, error code: (3) '%d' (target float)  Pass.
Test11, different type, error code: (3) '%c' (target float)  Pass.
Test11, different type, error code: (3) '%u' (target float)  Pass.
Test11, different type, error code: (3) '%p' (target float)  Pass.
Test12, pointer type check: (0)  Ref: '000000000064fcbc' <-> Res: '000000000064fcbc'  Pass.
Test13, floating point infinity check: (0) Ref: 'inf' <-> Res: 'inf'  Pass.
Test14, different type, error code: (3) '%s' (target integer)  Pass.
Test14, different type, error code: (3) '%f' (target integer)  Pass.
Test14, different type, error code: (3) '%p' (target integer)  Pass.
Test14, different type, error code: (3) '%g' (target integer)  Pass.
Test15(0), fixed point check. Ref: '0.10' <-> Res: '0.10'  Pass.
Test15(1), fixed point check. Ref: '0.49' <-> Res: '0.49'  Pass.
Test15(2), fixed point check. Ref: '0.73' <-> Res: '0.73'  Pass.
Test15(3), fixed point check. Ref: '0.98' <-> Res: '0.98'  Pass.
Test15(4), fixed point check. Ref: '1.00' <-> Res: '1.00'  Pass.
Test16 floating point '-1' check. Ref: '-99.000000' <-> Res: '-99.000000'  Pass.
Test17 floating point '%-' check. Ref: '-99.000000' <-> Res: '-99.000000'  Pass.
Test18 report pointer (char*) '%p' check. Ref: '000000000064fcc0' <-> Res: '000000000064fcc0'  Pass.
Test19 report pointer (int*) '%p' check. Ref: '000000000040f7a0' <-> Res: '000000000040f7a0'  Pass.
Test20(0), floating point auto check. Ref: '1e+06' <-> Res: '1e+06'  Pass.
Test20(1), floating point auto check. Ref: '1.41421e+06' <-> Res: '1.41421e+06'  Pass.
Test20(2), floating point auto check. Ref: '100000' <-> Res: '100000'  Pass.
Test20(3), floating point auto check. Ref: '141421' <-> Res: '141421'  Pass.
Test20(4), floating point auto check. Ref: '-100000' <-> Res: '-100000'  Pass.
Test20(5), floating point auto check. Ref: '-141421' <-> Res: '-141421'  Pass.
Test20(6), floating point auto check. Ref: '1000' <-> Res: '1000'  Pass.
Test20(7), floating point auto check. Ref: '1414.21' <-> Res: '1414.21'  Pass.
Test20(8), floating point auto check. Ref: '1' <-> Res: '1'  Pass.
Test20(9), floating point auto check. Ref: '1.41421' <-> Res: '1.41421'  Pass.
Test20(10), floating point auto check. Ref: '0.001' <-> Res: '0.001'  Pass.
Test20(11), floating point auto check. Ref: '0.00141421' <-> Res: '0.00141421'  Pass.
Test20(12), floating point auto check. Ref: '-1e-05' <-> Res: '-1e-05'  Pass.
Test20(13), floating point auto check. Ref: '-1.41421e-05' <-> Res: '-1.41421e-05'  Pass.
Test20(14), floating point auto check. Ref: '1e-05' <-> Res: '1e-05'  Pass.
Test20(15), floating point auto check. Ref: '1.41421e-05' <-> Res: '1.41421e-05'  Pass.
Test20(16), floating point auto check. Ref: '1e-06' <-> Res: '1e-06'  Pass.
Test20(17), floating point auto check. Ref: '1.41421e-06' <-> Res: '1.41421e-06'  Pass.

format class Version: 96
All Pass: 20/20

まとめ

  • C++17 対応のコンパイラなら、RX マイコンに限らず、他の環境でも使えると思う。
  • やった事は無いが、Arduino でも使えるものと思う。(C++17 がコンパイル出来れば・・)
  • CC-RX は C++11 さえ未対応なので使えない。