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 にプッシュした。
テストケースは、以下のように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 さえ未対応なので使えない。