Boost Karma 事始め
Boost Spirit のパーサ Qi に対応するジェネレータ Karma を使うと、以下のように型安全な printf
風の出力が簡単に行える。
#include <iostream> #include <cmath> #include <boost/spirit/include/karma.hpp> namespace karma = boost::spirit::karma; int main() { long double v[] = { std::exp(1), std::sqrt(2), }; std::cout << karma::format(karma::long_double << ',' << karma::long_double, v[0], v[1]) << std::endl; return 0; }
2.718,1.414
それどころか、様々なコンテナに対応しているので、std::vector
に格納された要素の出力も極めて簡単である。
#include <iostream> #include <cmath> #include <vector> #include <boost/assign.hpp> #include <boost/spirit/include/karma.hpp> namespace karma = boost::spirit::karma; int main() { std::vector<long double> v = boost::assign::list_of(std::exp(1))(std::sqrt(2)); std::cout << karma::format(karma::long_double % ',', v) << std::endl; return 0; }
2.718,1.414
Boost Karma による C99 printf double ステージ1
しかし、fprintf
のような書式指定を行おうとすると少々厄介なので、ここでは浮動小数点に関して C99 printf 互換のジェネレータを用意してしまおう。
具体的には、以下のような C99 printf 書式指定のような浮動小数点の表示を、段階的に可能としていこう。
scientific_real %.3e 1.000e+00 %e 1.000000e+00 %+e +1.000000e+00 % e 1.000000e+00 fixed_real %.3f 1.000 %f 1.000000 %+f +1.000000 % f 1.000000 general_real %.3g 1 %g 1 %+g +1 % g 1 alt_general_real %#.3g 1.00 %#g 1.00000 %#+g +1.00000 %# g 1.00000
ちなみに、幅指定は最後の最後で対応する。
さて、Karma における浮動小数点の書式指定は real_policies<T>
を継承したクラスにて行うものとなっているので、まずはこれらの「ポリシー」を用意しよう。
/* boost-spirit-karma-real_generators.hpp Copyright (C) 2013 Taiji Yamada <taiji@aihara.co.jp> Distributed under the Boost Software License, Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt) */ #ifndef _boost_spirit_karma_real_generators_hpp_ #define _boost_spirit_karma_real_generators_hpp_ #include <boost/spirit/include/karma.hpp> namespace boost { namespace spirit { namespace karma { template <typename T> struct scientific_real_policies : real_policies<T> { static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } }; template <typename T> struct fixed_real_policies : scientific_real_policies<T> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } }; template <typename T> struct alt_general_real_policies : fixed_real_policies<T> { static int floatfield(T n) { return real_policies<T>::floatfield(n); } }; template <typename T> struct general_real_policies : alt_general_real_policies<T> { }; } } } #endif
floatfield(T n)
が常に real_policies<T>::fmtflags::scientific
を返せば %e
相当、常に real_policies<T>::fmtflags::fixed
を返せば %f
相当、既定の real_policies<T>::floatfield(n)
に従えば %g
相当のようである。
例えばこれは、以下のように利用する。
#include <iostream> #include <cmath> #include <boost/spirit/include/karma.hpp> #include "boost-spirit-karma-real_generators00.hpp" namespace karma = boost::spirit::karma; typedef karma::real_generator<long double, karma::scientific_real_policies<long double> > long_double_generator; int main() { long_double_generator long_double; long double v[] = { std::exp(1), std::sqrt(2), }; std::cout << karma::format(long_double << ',' << long_double, v[0], v[1]) << std::endl; return 0; }
この既定の段階では、以下のような表示形式になっている。
scientific_real_policies<T> %e 1.0e00 T=double fixed_real_policies<T> %f 1.0 T=double general_real_policies<T> %g 1.0 T=double
まずは、精度を返す precision(T n)
および、符号を正符号においても強制的に印字するか否かを返す force_sign(T n)
で利用者がテンプレート引数で指定した属性を返すようにする。
--- boost-spirit-karma-real_generators00.hpp 2013-06-04 16:57:44.000000000 +0900 +++ boost-spirit-karma-real_generators01.hpp 2013-06-04 16:57:44.000000000 +0900 @@ -13,26 +13,28 @@ namespace boost { namespace spirit { namespace karma { -template <typename T> +template <typename T, unsigned Precision = 6, bool ForceSign = false> struct scientific_real_policies : real_policies<T> { + static bool force_sign(T) { return ForceSign; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } + static unsigned precision(T) { return Precision; } }; -template <typename T> -struct fixed_real_policies : scientific_real_policies<T> { +template <typename T, unsigned Precision = 6, bool ForceSign = false> +struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } }; -template <typename T> -struct alt_general_real_policies : fixed_real_policies<T> { +template <typename T, unsigned Precision = 6, bool ForceSign = false> +struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign> { static int floatfield(T n) { return real_policies<T>::floatfield(n); } }; -template <typename T> -struct general_real_policies : alt_general_real_policies<T> { +template <typename T, unsigned Precision = 6, bool ForceSign = false> +struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign> { }; }}}
Karma の既定の精度が 3 に対して、C99 printf の既定の精度は 6 なので、そのようにした。
この段階で以下のような表示形式になっている。
scientific_real_policies<T, Precision=6, ForceSign=false> %.3e 1.0e00 T=double, Precision=3, ForceSign=false %e 1.0e00 T=double, Precision=6, ForceSign=false %+e +1.0e00 T=double, Precision=6, ForceSign=true fixed_real_policies<T, Precision=6, ForceSign=false> %.3f 1.0 T=double, Precision=3, ForceSign=false %f 1.0 T=double, Precision=6, ForceSign=false %+f +1.0 T=double, Precision=6, ForceSign=true general_real_policies<T, Precision=6, ForceSign=false> %.3g 1.0 T=double, Precision=3, ForceSign=false %g 1.0 T=double, Precision=6, ForceSign=false %+g +1.0 T=double, Precision=6, ForceSign=true
キリの良い数字の場合、ゼロが切り詰められているのは、%g
以外では意図した書式ではないので、それに trailing_zeros(T n)
で対応しよう。
--- boost-spirit-karma-real_generators01.hpp 2013-06-04 16:57:44.000000000 +0900 +++ boost-spirit-karma-real_generators02.hpp 2013-06-04 16:57:44.000000000 +0900 @@ -16,6 +16,7 @@ template <typename T, unsigned Precision = 6, bool ForceSign = false> struct scientific_real_policies : real_policies<T> { static bool force_sign(T) { return ForceSign; } + static bool trailing_zeros(T) { return true; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } static unsigned precision(T) { return Precision; } }; @@ -27,6 +28,7 @@ template <typename T, unsigned Precision = 6, bool ForceSign = false> struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign> { + static bool trailing_zeros(T) { return true; } static int floatfield(T n) { return real_policies<T>::floatfield(n); @@ -35,6 +37,7 @@ template <typename T, unsigned Precision = 6, bool ForceSign = false> struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign> { + static bool trailing_zeros(T) { return false; } }; }}}
この段階で以下のような表示形式になる。
scientific_real_policies<T, Precision=6, ForceSign=false> %.3e 1.000e00 T=double, Precision=3, ForceSign=false %e 1.000000e00 T=double, Precision=6, ForceSign=false %+e +1.000000e00 T=double, Precision=6, ForceSign=true fixed_real_policies<T, Precision=6, ForceSign=false> %.3f 1.000 T=double, Precision=3, ForceSign=false %f 1.000000 T=double, Precision=6, ForceSign=false %+f +1.000000 T=double, Precision=6, ForceSign=true general_real_policies<T, Precision=6, ForceSign=false> %.3g 1.0 T=double, Precision=3, ForceSign=false %g 1.0 T=double, Precision=6, ForceSign=false %+g +1.0 T=double, Precision=6, ForceSign=true alt_general_real_policies<T, Precision=6, ForceSign=false> %#.3g 1.000 T=double, Precision=3, ForceSign=false %#g 1.000000 T=double, Precision=6, ForceSign=false %#+g +1.000000 T=double, Precision=6, ForceSign=true
次に、%e
の指数部の符号は常に表示するようにしよう。
--- boost-spirit-karma-real_generators02.hpp 2013-06-04 22:38:15.000000000 +0900 +++ boost-spirit-karma-real_generators03.hpp 2013-06-04 22:38:15.000000000 +0900 @@ -17,10 +17,21 @@ struct scientific_real_policies : real_policies<T> { static bool force_sign(T) { return ForceSign; } static bool trailing_zeros(T) { return true; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } static unsigned precision(T) { return Precision; } + template <typename CharEncoding, typename Tag, typename OutputIterator> + static bool exponent(OutputIterator &sink, long n) + { + long abs_n = traits::get_absolute_value(n); + bool r = char_inserter<CharEncoding, Tag>::call(sink, 'e') && + sign_inserter::call(sink, /*traits::test_zero(n)*/false, + traits::test_negative(n), /*false*/true); + if (r && abs_n < 10) // the C99 Standard requires at least two digits in the exponent + r = char_inserter<CharEncoding, Tag>::call(sink, '0'); + return r && int_inserter<10>::call(sink, abs_n); + } }; template <typename T, unsigned Precision = 6, bool ForceSign = false> struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; }
すると以下のような表示形式になる。
scientific_real_policies<T, Precision=6, ForceSign=false> %.3e 1.000e+00 T=double, Precision=3, ForceSign=false %e 1.000000e+00 T=double, Precision=6, ForceSign=false %+e +1.000000e+00 T=double, Precision=6, ForceSign=true
sign_inserter::call
の第4引数が強制的に符号を印字するか否かを決定している。ちなみに、第2引数は、真のときに正符号の代わりに「空白」を印字してしまうので、ここでは偽を指定してそれを抑止している。
次に、正符号の代わりに「空白」を印字することを、利用者がテンプレート引数で選べるようにする。
--- boost-spirit-karma-real_generators03.hpp 2013-06-04 16:57:45.000000000 +0900 +++ boost-spirit-karma-real_generators04.hpp 2013-06-04 16:57:45.000000000 +0900 @@ -13,12 +13,18 @@ namespace boost { namespace spirit { namespace karma { -template <typename T, unsigned Precision = 6, bool ForceSign = false> +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> struct scientific_real_policies : real_policies<T> { static bool force_sign(T) { return ForceSign; } static bool trailing_zeros(T) { return true; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } static unsigned precision(T) { return Precision; } + template <typename OutputIterator> + static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) + { + return sign_inserter::call(sink, (force_sign && !sign && BlankSign) || traits::test_zero(n), sign, force_sign) && + int_inserter<10>::call(sink, n); + } template <typename CharEncoding, typename Tag, typename OutputIterator> static bool exponent(OutputIterator &sink, long n) { @@ -32,13 +38,18 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false> -struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> +struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } + template <typename OutputIterator> + static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) + { + return real_policies<T>::integer_part(sink, n, sign, force_sign); + } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false> -struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> +struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign> { static bool trailing_zeros(T) { return true; } static int floatfield(T n) { @@ -46,8 +57,8 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false> -struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> +struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign> { static bool trailing_zeros(T) { return false; } };
ここでは、scientific_real_policies<T>
の仮数部を印字する integer_part
内の sign_inserter::call
の第2引数は、真のときに正符号の代わりに「空白」を印字する条件を「正のとき空白としての符号が強制されるとき、または、仮数部がゼロのとき」として C99 printf 互換となるようにしている。
また、fixed_real_policies<T>
の仮数部を印字する integer_part
は、取り敢えず real_policies<T>
のものを踏襲している。
この段階で以下のような表示形式になる。
scientific_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3e 1.000e+00 T=double, Precision=3, ForceSign=false, BlankSign=false %e 1.000000e+00 T=double, Precision=6, ForceSign=false, BlankSign=false %+e +1.000000e+00 T=double, Precision=6, ForceSign=true, BlankSign=false % e 1.000000e+00 T=double, Precision=6, ForceSign=true, BlankSign=true fixed_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3f 1.000 T=double, Precision=3, ForceSign=false, BlankSign=false %f 1.000000 T=double, Precision=6, ForceSign=false, BlankSign=false %+f +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=false % f +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=true general_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3g 1.0 T=double, Precision=3, ForceSign=false, BlankSign=false %g 1.0 T=double, Precision=6, ForceSign=false, BlankSign=false %+g +1.0 T=double, Precision=6, ForceSign=true, BlankSign=false % g +1.0 T=double, Precision=6, ForceSign=true, BlankSign=true alt_general_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %#.3g 1.000 T=double, Precision=3, ForceSign=false, BlankSign=false %#g 1.000000 T=double, Precision=6, ForceSign=false, BlankSign=false %#+g +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=false %# g +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=true
ここまでで %e
については意図した書式になったが、まだまだ C99 printf 互換には程遠い。
次に % f
に対応しよう。
--- boost-spirit-karma-real_generators04.hpp 2013-06-04 22:38:15.000000000 +0900 +++ boost-spirit-karma-real_generators05.hpp 2013-06-04 22:38:15.000000000 +0900 @@ -42,11 +42,12 @@ struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } template <typename OutputIterator> static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) { - return real_policies<T>::integer_part(sink, n, sign, force_sign); + return sign_inserter::call(sink, force_sign && !sign && BlankSign, sign, force_sign) && + int_inserter<10>::call(sink, n); } }; template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign> {
ここでは、fixed_real_policies<T>
の仮数部を印字する integer_part
内の sign_inserter::call
の第2引数は、真のときに正符号の代わりに「空白」を印字する条件を「正のとき空白としての符号が強制されるとき」として C99 printf 互換となるようにしている。
次に、%g
では、キリの良い数字の場合、ゼロだけでなく小数点も不要だ。
--- boost-spirit-karma-real_generators05.hpp 2013-06-04 16:57:45.000000000 +0900 +++ boost-spirit-karma-real_generators06.hpp 2013-06-04 16:57:45.000000000 +0900 @@ -61,6 +61,26 @@ template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign> { static bool trailing_zeros(T) { return false; } + static int floatfield(T n) + { + if (traits::test_zero(n)) + return real_policies<T>::fmtflags::fixed; + return alt_general_real_policies<T, Precision, ForceSign, BlankSign>::floatfield(n); + } + template <typename OutputIterator> + static bool dot(OutputIterator &sink, T n, unsigned precision) + { + if (traits::test_zero(n)) + return true; + return alt_general_real_policies<T, Precision, ForceSign, BlankSign>::dot(sink, n, precision); + } + template <typename OutputIterator> + static bool fraction_part(OutputIterator &sink, T n, unsigned adjprec, unsigned precision) + { + if (traits::test_zero(n)) + return true; + return fixed_real_policies<T, Precision, ForceSign, BlankSign>::fraction_part(sink, n, adjprec, precision); + } }; }}}
小数がゼロの時は real_policies<T>::fmtflags::fixed
で印字し、小数点や小数部は何もしない処理をしている。さもなくば、都合の良い継承しているメンバ関数を呼んでいる。
この段階で以下のような表示形式になる。
scientific_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3e 1.000e+00 T=double, Precision=3, ForceSign=false, BlankSign=false %e 1.000000e+00 T=double, Precision=6, ForceSign=false, BlankSign=false %+e +1.000000e+00 T=double, Precision=6, ForceSign=true, BlankSign=false % e 1.000000e+00 T=double, Precision=6, ForceSign=true, BlankSign=true fixed_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3f 1.000 T=double, Precision=3, ForceSign=false, BlankSign=false %f 1.000000 T=double, Precision=6, ForceSign=false, BlankSign=false %+f +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=false % f 1.000000 T=double, Precision=6, ForceSign=true, BlankSign=true general_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3g 1 T=double, Precision=3, ForceSign=false, BlankSign=false %g 1 T=double, Precision=6, ForceSign=false, BlankSign=false %+g +1 T=double, Precision=6, ForceSign=true, BlankSign=false % g 1 T=double, Precision=6, ForceSign=true, BlankSign=true alt_general_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %#.3g 1.000 T=double, Precision=3, ForceSign=false, BlankSign=false %#g 1.000000 T=double, Precision=6, ForceSign=false, BlankSign=false %#+g +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=false %# g +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=true
相当よくなってきたが、致命的な問題が残っている。%#g
の精度とは「有効数字」のことであり、他における「小数点以下の桁数」ではない。よって、続けて floatfield(T n)
と precision(T n)
の2箇所修正してみよう。
--- boost-spirit-karma-real_generators06.hpp 2013-06-04 22:38:15.000000000 +0900 +++ boost-spirit-karma-real_generators07.hpp 2013-06-04 22:38:15.000000000 +0900 @@ -52,11 +52,14 @@ template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign> { static bool trailing_zeros(T) { return true; } static int floatfield(T n) { - return real_policies<T>::floatfield(n); + using namespace std; + T abs_n = traits::get_absolute_value(n); + return (!(floor(log10(abs_n)) < Precision) || abs_n < 1e-4) ? + real_policies<T>::fmtflags::scientific : real_policies<T>::fmtflags::fixed; } }; template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign> {
--- boost-spirit-karma-real_generators07.hpp 2013-06-04 16:57:45.000000000 +0900 +++ boost-spirit-karma-real_generators08.hpp 2013-06-04 16:57:45.000000000 +0900 @@ -59,6 +59,15 @@ return (!(floor(log10(abs_n)) < Precision) || abs_n < 1e-4) ? real_policies<T>::fmtflags::scientific : real_policies<T>::fmtflags::fixed; } + static unsigned precision(T n) + { + using namespace std; + T abs_n = traits::get_absolute_value(n), l; + if ((l = floor(log10(1./abs_n))) > 1) + return Precision + (l - 1); + l = floor(log10(abs_n)); + return Precision > l ? Precision - (l + 1) : Precision - 1; + } }; template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false>
つまり、precision(T n)
は有効桁 Precision
から得られる小数点以下の桁数を返すようにし、他所との整合性をとった。
この段階で以下のような表示形式になる。
scientific_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3e 1.000e+00 T=double, Precision=3, ForceSign=false, BlankSign=false %e 1.000000e+00 T=double, Precision=6, ForceSign=false, BlankSign=false %+e +1.000000e+00 T=double, Precision=6, ForceSign=true, BlankSign=false % e 1.000000e+00 T=double, Precision=6, ForceSign=true, BlankSign=true fixed_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3f 1.000 T=double, Precision=3, ForceSign=false, BlankSign=false %f 1.000000 T=double, Precision=6, ForceSign=false, BlankSign=false %+f +1.000000 T=double, Precision=6, ForceSign=true, BlankSign=false % f 1.000000 T=double, Precision=6, ForceSign=true, BlankSign=true general_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3g 1 T=double, Precision=3, ForceSign=false, BlankSign=false %g 1 T=double, Precision=6, ForceSign=false, BlankSign=false %+g +1 T=double, Precision=6, ForceSign=true, BlankSign=false % g 1 T=double, Precision=6, ForceSign=true, BlankSign=true alt_general_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %#.3g 1.00 T=double, Precision=3, ForceSign=false, BlankSign=false %#g 1.00000 T=double, Precision=6, ForceSign=false, BlankSign=false %#+g +1.00000 T=double, Precision=6, ForceSign=true, BlankSign=false %# g 1.00000 T=double, Precision=6, ForceSign=true, BlankSign=true
ほぼ完璧と思ったら大間違い。精度が3で値が -0.0001
のように精度を超えた負値の場合、以下のように符号が消えてしまう。
fixed_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3f 0.000 T=double, Precision=3, ForceSign=false, BlankSign=false %#.3f 0.000 T=double, Precision=3, ForceSign=false, BlankSign=false
いくら精度以下とはいえ、負のほぼ零と正のほぼ零は意味が全く異る。
fixed_real_policies<T, Precision=6, ForceSign=false, BlankSign=false> %.3f -0.000 T=double, Precision=3, ForceSign=false, BlankSign=false %#.3f -0.000 T=double, Precision=3, ForceSign=false, BlankSign=false
これに対応するには、結局、オリジナルが処理していることと微妙に異なるコードを書く必要があった。
--- boost-spirit-karma-real_generators08.hpp 2013-06-04 16:57:46.000000000 +0900 +++ boost-spirit-karma-real_generators09.hpp 2013-06-04 16:57:46.000000000 +0900 @@ -13,8 +13,74 @@ namespace boost { namespace spirit { namespace karma { -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> struct scientific_real_policies : real_policies<T> { + typedef CE CharEncoding; + typedef CC Tag; + template <typename Inserter, typename OutputIterator, typename Policies> + static bool call(OutputIterator &sink, T n, Policies const &p) + { + bool force_sign = p.force_sign(n); + bool sign_val = false; + int flags = p.floatfield(n); + if (traits::test_negative(n)) { + n = -n; + sign_val = true; + } + unsigned precision = p.precision(n); + if (std::numeric_limits<T>::digits10) { + precision = (std::min)(precision, + (unsigned)std::numeric_limits<T>::digits10 + 1); + } + using namespace std; + T dim = 0; + if (0 == (Policies::fmtflags::fixed & flags) && !traits::test_zero(n)) { + dim = log10(n); + if (dim > 0) + n /= spirit::traits::pow10<T>(traits::truncate_to_long::call(dim)); + else if (n < 1.) { + long exp = traits::truncate_to_long::call(-dim); + if (exp != -dim) + ++exp; + dim = -exp; + n *= spirit::traits::pow10<T>(exp); + } + } + T integer_part; + T precexp = spirit::traits::pow10<T>(precision); + T fractional_part = modf(n, &integer_part); + fractional_part = floor(fractional_part * precexp + T(0.5)); + if (fractional_part >= precexp) { + fractional_part = floor(fractional_part - precexp); + integer_part += 1; + } + T long_int_part = floor(integer_part); + T long_frac_part = fractional_part; + unsigned prec = precision; + if (!p.trailing_zeros(n)) { + T frac_part_floor = long_frac_part; + if (0 != long_frac_part) { + while (0 != prec && + 0 == traits::remainder<10>::call(long_frac_part)) { + long_frac_part = traits::divide<10>::call(long_frac_part); + --prec; + } + } + else + prec = 0; + if (precision != prec) + long_frac_part = frac_part_floor / spirit::traits::pow10<T>(precision-prec); + } + if (false && + sign_val && traits::test_zero(long_int_part) && traits::test_zero(long_frac_part)) + sign_val = false; + bool r = p.integer_part(sink, long_int_part, sign_val, force_sign); + r = r && p.dot(sink, long_frac_part, precision); + r = r && p.fraction_part(sink, long_frac_part, prec, precision); + if (r && 0 == (Policies::fmtflags::fixed & flags)) + return p.template exponent<CharEncoding, Tag>(sink, traits::truncate_to_long::call(dim)); + return r; + } static bool force_sign(T) { return ForceSign; } static bool trailing_zeros(T) { return true; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } @@ -38,8 +104,8 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> -struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> +struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign, CE, CC> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } template <typename OutputIterator> static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) @@ -49,8 +115,8 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> -struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> +struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign, CE, CC> { static bool trailing_zeros(T) { return true; } static int floatfield(T n) { @@ -70,8 +136,8 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false> -struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> +struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign, CE, CC> { static bool trailing_zeros(T) { return false; } static int floatfield(T n) {
call
でやっていることは sign_val = false
を抑制しているだけで、オリジナルとほぼ一緒。ついでに、大文字の %E
, %G
に対応させるための変更 typename CE = karma::char_encoding::ascii
, typename CC = karma::tag::upper
もやっているので注意。
Boost Karma による C99 printf double ステージ2
先の書式指定に加えて、以下のような幅指定及び各種フラグ -0
の追加に対応させよう。ひとつの値だけでも組み合わせはこれだけある。
%-12.3e 1.000e+00 %-12e 1.000000e+00 %-+12e +1.000000e+00 %- 12e 1.000000e+00 %-12.3f 1.000 %-12f 1.000000 %-+12f +1.000000 %- 12f 1.000000 %-12.3g 1 %-12g 1 %-+12g +1 %- 12g 1 %#-12.3g 1.00 %#-12g 1.00000 %#-+12g +1.00000 %#- 12g 1.00000 %12.3e 1.000e+00 %12e 1.000000e+00 %+12e +1.000000e+00 % 12e 1.000000e+00 %12.3f 1.000 %12f 1.000000 %+12f +1.000000 % 12f 1.000000 %12.3g 1 %12g 1 %+12g +1 % 12g 1 %#12.3g 1.00 %#12g 1.00000 %#+12g +1.00000 %# 12g 1.00000 %12.3g 1 %12g 1 %+12g +1 % 12g 1 %012.3e 0001.000e+00 %012e 1.000000e+00 %+012e +1.000000e+00 % 012e 1.000000e+00 %012.3f 00000001.000 %012f 00001.000000 %+012f +0001.000000 % 012f 0001.000000 %012.3g 000000000001 %012g 000000000001 %+012g +00000000001 % 012g 00000000001 %#012.3g 000000001.00 %#012g 000001.00000 %#+012g +00001.00000 %# 012g 00001.00000 %012.3g 000000000001 %012g 000000000001 %+012g +00000000001 % 012g 00000000001
以上、C99 printf と一致していることは確認した。
--- boost-spirit-karma-real_generators09.hpp 2013-06-04 16:57:46.000000000 +0900 +++ boost-spirit-karma-real_generators10.hpp 2013-06-04 16:57:46.000000000 +0900 @@ -13,14 +13,18 @@ namespace boost { namespace spirit { namespace karma { -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, + int Width = 0, int Align = -1, char Pad = ' ', + typename CE = unused_type, typename CC = unused_type> struct scientific_real_policies : real_policies<T> { + typedef boost::mpl::int_< + karma::generator_properties::countingbuffer + > properties; typedef CE CharEncoding; typedef CC Tag; template <typename Inserter, typename OutputIterator, typename Policies> - static bool call(OutputIterator &sink, T n, Policies const &p) + static bool call_n(OutputIterator &sink, T n, Policies const &p, bool force_sign) { - bool force_sign = p.force_sign(n); bool sign_val = false; int flags = p.floatfield(n); if (traits::test_negative(n)) { @@ -81,6 +85,43 @@ return p.template exponent<CharEncoding, Tag>(sink, traits::truncate_to_long::call(dim)); return r; } + + template <typename Inserter, typename OutputIterator, typename Policies> + static bool call(OutputIterator &sink, T n, Policies const &p) + { + if (!Width) + return call_n<Inserter, OutputIterator, Policies>(sink, n, p, p.force_sign(n)); + bool r = false; + if (Align < 0) { // left align + karma::detail::enable_counting<OutputIterator> counting(sink); + r = call_n<Inserter, OutputIterator, Policies>(sink, n, p, p.force_sign(n)); + while (r && int(counting.count()) < Width) + r = karma::generate(sink, ' '); + } + else { // right align + bool sign_val = false; + if (Pad == '0' && traits::test_negative(n)) { + n = -n; + sign_val = true; + } + karma::detail::enable_buffering<OutputIterator> buffering(sink, Width); + { + karma::detail::disable_counting<OutputIterator> nocounting(sink); + r = call_n<Inserter, OutputIterator, Policies>(sink, n, p, (Pad == '0' && p.force_sign(n)) ? false : p.force_sign(n)); + } + buffering.disable(); + karma::detail::enable_counting<OutputIterator> counting(sink, buffering.buffer_size()); + if (sign_val || (Pad == '0' && p.force_sign(n))) + r = sign_inserter::call(sink, (force_sign && !sign_val && BlankSign) || traits::test_zero(n), sign_val, p.force_sign(n)); + while (r && int(counting.count()) < Width) + r = karma::generate(sink, Pad); + if (r) { + buffering.buffer_copy(); + } + } + return r; + } + static bool force_sign(T) { return ForceSign; } static bool trailing_zeros(T) { return true; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } @@ -104,8 +145,10 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> -struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign, CE, CC> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, + int Width = 0, int Align = -1, char Pad = ' ', + typename CE = unused_type, typename CC = unused_type> +struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign, Width, Align, Pad, CE, CC> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } template <typename OutputIterator> static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) @@ -115,8 +158,10 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> -struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign, CE, CC> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, + int Width = 0, int Align = -1, char Pad = ' ', + typename CE = unused_type, typename CC = unused_type> +struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign, Width, Align, Pad, CE, CC> { static bool trailing_zeros(T) { return true; } static int floatfield(T n) { @@ -136,8 +181,10 @@ } }; -template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, typename CE = unused_type, typename CC = unused_type> -struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign, CE, CC> { +template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, + int Width = 0, int Align = -1, char Pad = ' ', + typename CE = unused_type, typename CC = unused_type> +struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign, Width, Align, Pad, CE, CC> { static bool trailing_zeros(T) { return false; } static int floatfield(T n) {
最後に全体像を示す。Boost Spirit Karma のサンプルは参考にしたが、実用的ではないので、以下のものでよいと思う。
/* boost-spirit-karma-real_generators.hpp Copyright (C) 2013 Taiji Yamada <taiji@aihara.co.jp> Distributed under the Boost Software License, Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt) */ #ifndef _boost_spirit_karma_real_generators_hpp_ #define _boost_spirit_karma_real_generators_hpp_ #include <boost/spirit/include/karma.hpp> namespace boost { namespace spirit { namespace karma { template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, int Width = 0, int Align = -1, char Pad = ' ', typename CE = unused_type, typename CC = unused_type> struct scientific_real_policies : real_policies<T> { typedef boost::mpl::int_< karma::generator_properties::countingbuffer > properties; typedef CE CharEncoding; typedef CC Tag; template <typename Inserter, typename OutputIterator, typename Policies> static bool call_n(OutputIterator &sink, T n, Policies const &p, bool force_sign) { bool sign_val = false; int flags = p.floatfield(n); if (traits::test_negative(n)) { n = -n; sign_val = true; } unsigned precision = p.precision(n); if (std::numeric_limits<T>::digits10) { precision = (std::min)(precision, (unsigned)std::numeric_limits<T>::digits10 + 1); } using namespace std; T dim = 0; if (0 == (Policies::fmtflags::fixed & flags) && !traits::test_zero(n)) { dim = log10(n); if (dim > 0) n /= spirit::traits::pow10<T>(traits::truncate_to_long::call(dim)); else if (n < 1.) { long exp = traits::truncate_to_long::call(-dim); if (exp != -dim) ++exp; dim = -exp; n *= spirit::traits::pow10<T>(exp); } } T integer_part; T precexp = spirit::traits::pow10<T>(precision); T fractional_part = modf(n, &integer_part); fractional_part = floor(fractional_part * precexp + T(0.5)); if (fractional_part >= precexp) { fractional_part = floor(fractional_part - precexp); integer_part += 1; } T long_int_part = floor(integer_part); T long_frac_part = fractional_part; unsigned prec = precision; if (!p.trailing_zeros(n)) { T frac_part_floor = long_frac_part; if (0 != long_frac_part) { while (0 != prec && 0 == traits::remainder<10>::call(long_frac_part)) { long_frac_part = traits::divide<10>::call(long_frac_part); --prec; } } else prec = 0; if (precision != prec) long_frac_part = frac_part_floor / spirit::traits::pow10<T>(precision-prec); } if (false && sign_val && traits::test_zero(long_int_part) && traits::test_zero(long_frac_part)) sign_val = false; bool r = p.integer_part(sink, long_int_part, sign_val, force_sign); r = r && p.dot(sink, long_frac_part, precision); r = r && p.fraction_part(sink, long_frac_part, prec, precision); if (r && 0 == (Policies::fmtflags::fixed & flags)) return p.template exponent<CharEncoding, Tag>(sink, traits::truncate_to_long::call(dim)); return r; } template <typename Inserter, typename OutputIterator, typename Policies> static bool call(OutputIterator &sink, T n, Policies const &p) { if (!Width) return call_n<Inserter, OutputIterator, Policies>(sink, n, p, p.force_sign(n)); bool r = false; if (Align < 0) { // left align karma::detail::enable_counting<OutputIterator> counting(sink); r = call_n<Inserter, OutputIterator, Policies>(sink, n, p, p.force_sign(n)); while (r && int(counting.count()) < Width) r = karma::generate(sink, ' '); } else { // right align bool sign_val = false; if (Pad == '0' && traits::test_negative(n)) { n = -n; sign_val = true; } karma::detail::enable_buffering<OutputIterator> buffering(sink, Width); { karma::detail::disable_counting<OutputIterator> nocounting(sink); r = call_n<Inserter, OutputIterator, Policies>(sink, n, p, (Pad == '0' && p.force_sign(n)) ? false : p.force_sign(n)); } buffering.disable(); karma::detail::enable_counting<OutputIterator> counting(sink, buffering.buffer_size()); if (sign_val || (Pad == '0' && p.force_sign(n))) r = sign_inserter::call(sink, (force_sign && !sign_val && BlankSign) || traits::test_zero(n), sign_val, p.force_sign(n)); while (r && int(counting.count()) < Width) r = karma::generate(sink, Pad); if (r) { buffering.buffer_copy(); } } return r; } static bool force_sign(T) { return ForceSign; } static bool trailing_zeros(T) { return true; } static int floatfield(T) { return real_policies<T>::fmtflags::scientific; } static unsigned precision(T) { return Precision; } template <typename OutputIterator> static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) { return sign_inserter::call(sink, (force_sign && !sign && BlankSign) || traits::test_zero(n), sign, force_sign) && int_inserter<10>::call(sink, n); } template <typename CharEncoding, typename Tag, typename OutputIterator> static bool exponent(OutputIterator &sink, long n) { long abs_n = traits::get_absolute_value(n); bool r = char_inserter<CharEncoding, Tag>::call(sink, 'e') && sign_inserter::call(sink, /*traits::test_zero(n)*/false, traits::test_negative(n), /*false*/true); if (r && abs_n < 10) // the C99 Standard requires at least two digits in the exponent r = char_inserter<CharEncoding, Tag>::call(sink, '0'); return r && int_inserter<10>::call(sink, abs_n); } }; template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, int Width = 0, int Align = -1, char Pad = ' ', typename CE = unused_type, typename CC = unused_type> struct fixed_real_policies : scientific_real_policies<T, Precision, ForceSign, BlankSign, Width, Align, Pad, CE, CC> { static int floatfield(T) { return real_policies<T>::fmtflags::fixed; } template <typename OutputIterator> static bool integer_part(OutputIterator &sink, T n, bool sign, bool force_sign) { return sign_inserter::call(sink, force_sign && !sign && BlankSign, sign, force_sign) && int_inserter<10>::call(sink, n); } }; template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, int Width = 0, int Align = -1, char Pad = ' ', typename CE = unused_type, typename CC = unused_type> struct alt_general_real_policies : fixed_real_policies<T, Precision, ForceSign, BlankSign, Width, Align, Pad, CE, CC> { static bool trailing_zeros(T) { return true; } static int floatfield(T n) { using namespace std; T abs_n = traits::get_absolute_value(n); return (!(floor(log10(abs_n)) < Precision) || abs_n < 1e-4) ? real_policies<T>::fmtflags::scientific : real_policies<T>::fmtflags::fixed; } static unsigned precision(T n) { using namespace std; T abs_n = traits::get_absolute_value(n), l; if ((l = floor(log10(1./abs_n))) > 1) return Precision + (l - 1); l = floor(log10(abs_n)); return Precision > l ? Precision - (l + 1) : Precision - 1; } }; template <typename T, unsigned Precision = 6, bool ForceSign = false, bool BlankSign = false, int Width = 0, int Align = -1, char Pad = ' ', typename CE = unused_type, typename CC = unused_type> struct general_real_policies : alt_general_real_policies<T, Precision, ForceSign, BlankSign, Width, Align, Pad, CE, CC> { static bool trailing_zeros(T) { return false; } static int floatfield(T n) { if (traits::test_zero(n)) return real_policies<T>::fmtflags::fixed; return alt_general_real_policies<T, Precision, ForceSign, BlankSign>::floatfield(n); } template <typename OutputIterator> static bool dot(OutputIterator &sink, T n, unsigned precision) { if (traits::test_zero(n)) return true; return alt_general_real_policies<T, Precision, ForceSign, BlankSign>::dot(sink, n, precision); } template <typename OutputIterator> static bool fraction_part(OutputIterator &sink, T n, unsigned adjprec, unsigned precision) { if (traits::test_zero(n)) return true; return fixed_real_policies<T, Precision, ForceSign, BlankSign>::fraction_part(sink, n, adjprec, precision); } }; } } } #endif
Boost Karma による C99 printf int
さて、Karma における符号付き整数のジェネレータである int_generator<T, Radix=10, force_sign=false>
は、right_align(width, pad)
ディレクティブとの組み合わせなどで、ほとんどの書式が可能であるが、以下のような書式の実現は困難である。
%04lld 0001 %+04lld +001 % 04lld 001
そして、int_policies
のようなポリシークラスがあるわけではないので、書式を拡張するには int_generator
そのものを継承する必要がある。
よって、C99 printf 書式指定のような符号付き整数の表示を可能とする c99_int_generator
を用意してしまおう。これは以下のように使う。
#include <iostream> #include <boost/range.hpp> #include <boost/spirit/include/karma.hpp> #include "boost-spirit-karma-int_generators.hpp" namespace karma = boost::spirit::karma; typedef karma::c99_int_generator<long long, 10, true, true, 6, 1, '0'> long_long_generator; int main() { long_long_generator long_long; long long v[] = { 0, 1, -100, }; std::cout << karma::format(long_long % karma::lit(','), boost::make_iterator_range(v, v+sizeof(v)/sizeof(v[0]))) << std::endl; return 0; }
right_align(width, pad)
を使うとどうしても以下のようになってしまう表示が、
# karma::right_align(6, '0')[karma::int_generator<long long, 10, true>()] 0000 0,0000+1,00-100
以下のように表示できる。
00000, 00001,-00100
ここで使われているのは、以下の通りである。基本的には、浮動小数点でやったことと同様だ。
/* boost-spirit-karma-int_generators.hpp Copyright (C) 2013 Taiji Yamada <taiji@aihara.co.jp> Distributed under the Boost Software License, Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt) */ #ifndef _boost_spirit_karma_int_generators_hpp_ #define _boost_spirit_karma_int_generators_hpp_ #include <boost/spirit/include/karma.hpp> namespace boost { namespace spirit { namespace karma { template <typename T, unsigned Radix = 10, bool ForceSign = false, bool BlankSign = false, int Width = 0, int Align = -1, char Pad = ' ', typename CharEncoding = unused_type, typename Tag = unused_type> struct c99_int_generator : any_int_generator<T, CharEncoding, Tag, Radix, ForceSign> { typedef boost::mpl::int_< karma::generator_properties::countingbuffer > properties; template <typename OutputIterator, typename Attribute> static bool insert_int(OutputIterator &sink, Attribute const &attr, bool force_sign = ForceSign) { return sign_inserter::call(sink, (force_sign && !traits::test_negative(attr) && BlankSign), traits::test_negative(attr), force_sign) && int_inserter<Radix, CharEncoding, Tag>::call(sink, traits::get_absolute_value(attr)); } template <typename OutputIterator, typename Context, typename Delimiter, typename Attribute> static bool generate(OutputIterator &sink, Context &context, Delimiter const &d, Attribute const &attr) { if (!traits::has_optional_value(attr)) return false; if (!Width) return insert_int(sink, traits::extract_from<T>(attr, context), ForceSign) && delimit_out(sink, d); bool r = false; if (Align < 0) { // left align karma::detail::enable_counting<OutputIterator> counting(sink); r = insert_int(sink, traits::extract_from<T>(attr, context), ForceSign) && delimit_out(sink, d); while (r && int(counting.count()) < Width) r = karma::generate(sink, ' '); } else { // right align bool sign_val = false; Attribute n = attr; if (Pad == '0' && traits::test_negative(n)) { n = -n; sign_val = true; } karma::detail::enable_buffering<OutputIterator> buffering(sink, Width); { karma::detail::disable_counting<OutputIterator> nocounting(sink); r = insert_int(sink, traits::extract_from<T>(n, context), (Pad == '0' && ForceSign) ? false : ForceSign) && delimit_out(sink, d); } buffering.disable(); karma::detail::enable_counting<OutputIterator> counting(sink, buffering.buffer_size()); if (sign_val || (Pad == '0' && ForceSign)) r = sign_inserter::call(sink, (ForceSign && !sign_val && BlankSign), sign_val, ForceSign); while (r && int(counting.count()) < Width) r = karma::generate(sink, Pad); if (r) { buffering.buffer_copy(); } } return r; } }; } } } #endif
符号無し整数ではこうした問題はない。