今回扱うのは整数除算の結果を最近接遇数丸めする方法についてです。
最近接遇数丸めというのは四捨五入に近いのですが、端数が0.5だったときに偶数方向に丸めるというものです。たとえば、1.5でも2.5でも2になりますし、3.5や4.5は4になります。こういう端数処理の方法にはいろいろな呼び方があって、JIS丸め、ISO丸め、それから銀行丸めとも呼ばれています。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
template <class T> T round_half_to_even(T dividend, T divisor) { // 符号付き整数は、いったん符合無しで計算してから除数の符号にあわせる。 if (is_signed<T>::value) { typedef typename make_unsigned<T>::type U; U lhs = dividend; U rhs = divisor; if (dividend < 0) lhs = -lhs; if (divisor < 0) rhs = -rhs; T t = round_half_to_even(lhs, rhs); if (dividend < 0) t = -t; return t; } T quot = dividend / divisor; T rem = dividend % divisor; T rem2 = rem * 2; if (rem2 > divisor) // 端数 > 0の場合は切り上げ { ++quot; } else if (rem2 == divisor) // 端数=0.5の場合はquotが奇数なら切り上げ { ++quot; quot &= ~T(1); } return quot; } |
一応、C++98以降で使えるように実装してみました。C++11より前のバージョンではmake_signedメタ関数が使えませんので、TR1のものを使うかBoost C++ Librariesのものを使ってください(そういう事情もあって、あえてstd::は省略しています。適切な名前空間を指定してください)。
ところで、C++11以降であれば、C99から導入されたnearbyint関数やlrint関数、llrint関数を使うこともできます。これらの関数は、浮動小数点数を引数に取って、現在の丸めモードの従って整数に丸めた結果を返します。nearbyint関数は引数と同じ型で、lrint関数はlong型、llrint関数はlong long型で結果を返すようになっています。現在の丸めモードとはなっていますが、ISO/IEC 60559に準拠した浮動小数点演算を行う処理系(現在の大多数の処理系が該当するはずです)であれば、最近接偶数丸めになるはずです。
今回のように除算の結果を丸めたいだけであれば、整数の範囲で演算できる今回の実装も悪くないと思います。