2016-03-01 17 views
5

チュートリアルでは整数計算で困っていることがあります。正確には、整数除算。丸め整数ルーチン

一見好ましい方法は、最も近い整数にfloatを丸め、その後、フロートに除数をキャストしているが、当時の整数にすることををキャスト:

#include <cmath> 
int round_divide_by_float_casting(int a, int b){ 
    return (int) std::roundf(a/(float) b); 
} 

しかし、これはとあなたの左耳を掻くように思えるがあなたの右手。私が使用しているのは:

int round_divide (int a, int b){ 
    return a/b + a % b * 2/b; 
} 

これは画期的なことではありませんが、それは標準ではないという事実が私に何か不足しているのだろうか? 私の(限られたテストでも)2つの方法が私に異なる結果をもたらすシナリオは見つけられませんでした。誰かが、int-> float-> int型のキャストがより正確な結果を出すようなシナリオを実行しましたか?

+1

私の場合は、最初の方がはるかに明確です。私はそれが何をしようとしているのか知っています。私はそれをどうしようとしているのか分かっています。私は紙でそれを実行することなく、私は2番目のものが何をしているのか分かりません。私はまた、2番目の算術演算を行うので、最初の方が速くなると指摘しています – John3136

+0

私は透明度が重要であることに同意します。しかし、操作の複雑さ(と速度)については、わかりません。 –

答えて

1

標準液を推奨します。 cstdlibで宣言された関数のstd :: divファミリを使用します。

参照:http://en.cppreference.com/w/cpp/numeric/math/div

編集:いくつかのアーキテクチャ上で非常に非効率的かもしれintに、次にfloatにキャストし、e.x.マイクロコントローラ。

+0

提案していただきありがとうございます。私はそのような基本的な仕事をするためにすでにたくさんのルーチンが作られていることを知っていました。私の質問は、キャスト(一般的な方法)をモジュロ演算(不人気の方法)よりも信頼性が高く高速であるかどうかです。 –

+1

これはいくつかのテストの後に追加するだけです。これは** int-> float- > int **キャスティングではあるが、モジュロ算術よりも遅い。 –

+0

ベンチマークはおそらくHWアーキテクチャ特有のものです。ほとんどのアーキテクチャでは、モジュロソリューションはおそらくキャスト方法よりも高速です。 – teroi

3

それは本当に、x86-64のとARMのような近代的な「ビッグ」のCPUは、プロセッサに依存し、かつ優れている整数の範囲(およびdoubleを使用すると、範囲の問題のほとんどを解決するでしょう)

う整数除算と浮動小数点除算はほぼ同じ努力であり、整数を浮動小数点数に変換するか、またはその逆の変換は "難しい"作業ではない(少なくともその変換では正確に丸めを行う)ため、あります。

atmp = (float) a; 
btmp = (float) b; 
resfloat = divide atmp/btmp; 
return = to_int_with_rounding(resfloat) 

約4つの機械命令。

一方、あなたのコードでは、モジュロと乗算の2つの除算が使用されていますが、そのようなプロセッサではかなり長くなる可能性があります。 ( - しかし、それはまだ二つの異なる除算のコンパイラはa % bためa/bを再利用してくれない限り)

tmp = a/b; 
tmp1 = a % b; 
tmp2 = tmp1 * 2; 
tmp3 = tmp2/b; 
tmp4 = tmp + tmp3; 

だから、5つの命令、およびそれらの3つが「分割」です。

もちろん、floatまたはdoubleが数字を失うことなく保持できる桁数の範囲外の場合(floatの場合は23ビット、doubleの場合は53ビット)、メソッドはより良いかもしれません(ただし、整数の計算でオーバーフロー)。

さらに、最初のフォームは "everyone"によって使用されるため、コンパイラが認識して最適化できるフォームです。

明らかに、結果は使用されているコンパイラとそれが実行されているプロセッサの両方に依存しますが、これらは上記のコードを実行した結果です。clang++(v3.9、リリース前3.8)。

round_divide_by_float_casting(): 32.5 ns 
      round_divide_by_modulo(): 113 ns 
    divide_by_quotient_comparison(): 80.4 ns 

しかし、私は、生成されたコードを見たとき、私は見つける興味深い:

xorps %xmm0, %xmm0 
cvtsi2ssl 8016(%rsp,%rbp), %xmm0 
xorps %xmm1, %xmm1 
cvtsi2ssl 4016(%rsp,%rbp), %xmm1 
divss %xmm1, %xmm0 
callq roundf 
cvttss2si %xmm0, %eax 
movl %eax, 16(%rsp,%rbp) 
addq $4, %rbp 
cmpq $4000, %rbp    # imm = 0xFA0 
jne .LBB0_7 

roundが実際にコールであるということです。これは本当に私を驚かせるものですが、なぜいくつかのマシン(特に最近のx86プロセッサ)では高速です。

g++が周り与える、-ffast-mathとのより良い結果が得られる:

round_divide_by_float_casting(): 17.6 ns 
      round_divide_by_modulo(): 43.1 ns 
    divide_by_quotient_comparison(): 18.5 ns 

(これは100kの値に増加した数である)

+0

ありがとうございます。私は先に進み、いくつかのテストを行った。私のマシン(i7)でvs2015を使用すると、モジュロ演算は約2倍速くなりました。 ** roundf()** –

+0

マツに答えが更新されてありがとうございます。あなたの場合、モジュロ算術による除算が実際に最も遅いのはどういうことかは非常に興味深いです。すべての私のベンチマークで - 一見@YSCによって作られたものも - 最も遅い(そしてあなたにとって最も速い)丸いフロートをキャストした部門でした。私はC++やコンパイラの最適化などが新しくなっていますが、パフォーマンスにどの程度の変動があるのか​​魅力的です。いつか理由を理解することは素晴らしいことでしょう...乾杯! –

4

を算術ソリューション

一つは、あなたの関数が返すべきかを定義した場合「f(a, b)は、aの除算結果の最も近い整数を返します。これはです。は実際の除数の環にあります。

このように、問題は次のようにまとめることができます。この最も近い整数を整数除算だけで定義できますか?私たちはできると思います。

に最も近い整数はa/b(a/b) + 1(1)という2つの候補があります。 a % b0に近い場合はbになるので、a/bの結果が簡単です。そうでない場合は、(a/b) + 1です。この定義は、ニーズを満たしながら、1が使用してbによってaの2回の分裂を計算しないことによって、それを最適化することができ

int divide(int a, int b) 
{ 
    const int quot = a/b; 
    const int rem = a % b; 
    int result; 

    if (rem < b - rem) { 
     result = quot; 
    } else { 
     result = quot + 1; 
    } 
    return result; 
} 

一つは、最適化し、優れた慣行を無視して、に似た書き込み何かをできましたstd::div()の:

int divide(int a, int b) 
{ 
    const std::div_t dv = std::div(a, b); 
    int result = dv.quot; 

    if (dv.rem >= b - dv.rem) { 
     ++result; 
    } 
    return result; 
} 

我々は以前の私たちの実装のよく定義された行動を私たちに保証した問題の分析。

(1)最後にチェックするのは1つだけです:aまたはbが負の場合、どのように動作しますか?これは読者に任されている;)。

ベンチマーク

#include <iostream> 
#include <iomanip> 
#include <string> 

// solutions 
#include <cmath> 
#include <cstdlib> 

// benchmak 
#include <limits> 
#include <random> 
#include <chrono> 
#include <algorithm> 
#include <functional> 

// 
// Solutions 
// 
namespace 
{ 
    int round_divide_by_float_casting(int a, int b) { 
     return (int)roundf(a/(float)b); 
    } 

    int round_divide_by_modulo(int a, int b) { 
     return a/b + a % b * 2/b; 
    } 

    int divide_by_quotient_comparison(int a, int b) 
    { 
     const std::div_t dv = std::div(a, b); 
     int result = dv.quot; 

     if (dv.rem >= b - dv.rem) 
     { 
      ++result; 
     } 
     return result; 
    } 
} 

// 
// benchmark 
// 
class Randomizer 
{ 
    std::mt19937 _rng_engine; 
    std::uniform_int_distribution<int> _distri; 

public: 
    Randomizer() : _rng_engine(std::time(0)), _distri(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()) 
    { 
    } 

    template<class ForwardIt> 
    void operator()(ForwardIt begin, ForwardIt end) 
    { 
     std::generate(begin, end, std::bind(_distri, _rng_engine)); 
    } 
}; 

class Clock 
{ 
    std::chrono::time_point<std::chrono::steady_clock> _start; 

public: 
    static inline std::chrono::time_point<std::chrono::steady_clock> now() { return std::chrono::steady_clock::now(); } 

    Clock() : _start(now()) 
    { 
    } 

    template<class DurationUnit> 
    std::size_t end() 
    { 
     return std::chrono::duration_cast<DurationUnit>(now() - _start).count(); 
    } 
}; 

// 
// Entry point 
// 
int main() 
{ 
    Randomizer randomizer; 
    std::array<int, 1000> dividends; // SCALE THIS UP (1'000'000 would be great) 
    std::array<int, dividends.size()> divisors; 
    std::array<int, dividends.size()> results; 
    randomizer(std::begin(dividends), std::end(dividends)); 
    randomizer(std::begin(divisors), std::end(divisors)); 

    { 
     Clock clock; 
     auto dividend = std::begin(dividends); 
     auto divisor = std::begin(divisors); 
     auto result = std::begin(results); 
     for (; dividend != std::end(dividends) ; ++dividend, ++divisor, ++result) 
     { 
      *result = round_divide_by_float_casting(*dividend, *divisor); 
     } 
     const float unit_time = clock.end<std::chrono::nanoseconds>()/static_cast<float>(results.size()); 
     std::cout << std::setw(40) << "round_divide_by_float_casting(): " << std::setprecision(3) << unit_time << " ns\n"; 
    } 
    { 
     Clock clock; 
     auto dividend = std::begin(dividends); 
     auto divisor = std::begin(divisors); 
     auto result = std::begin(results); 
     for (; dividend != std::end(dividends) ; ++dividend, ++divisor, ++result) 
     { 
      *result = round_divide_by_modulo(*dividend, *divisor); 
     } 
     const float unit_time = clock.end<std::chrono::nanoseconds>()/static_cast<float>(results.size()); 
     std::cout << std::setw(40) << "round_divide_by_modulo(): " << std::setprecision(3) << unit_time << " ns\n"; 
    } 
    { 
     Clock clock; 
     auto dividend = std::begin(dividends); 
     auto divisor = std::begin(divisors); 
     auto result = std::begin(results); 
     for (; dividend != std::end(dividends) ; ++dividend, ++divisor, ++result) 
     { 
      *result = divide_by_quotient_comparison(*dividend, *divisor); 
     } 
     const float unit_time = clock.end<std::chrono::nanoseconds>()/static_cast<float>(results.size()); 
     std::cout << std::setw(40) << "divide_by_quotient_comparison(): " << std::setprecision(3) << unit_time << " ns\n"; 
    } 
} 

出力

Demo

2つの算術ソリューション公演

g++ -std=c++11 -O2 -Wall -Wextra -Werror main.cpp && ./a.out 
     round_divide_by_float_casting(): 54.7 ns 
       round_divide_by_modulo(): 24 ns 
     divide_by_quotient_comparison(): 25.5 ns 
ありません区別できる(あなたがベンチサイズをスケールアップしたときに、そのベンチマークが収束)。

+0

こんにちは、提案していただきありがとうございます。これは** int-> float-> int **のキャストより高速ですが、モジュロ算術よりも遅いです。 –

+0

@AdlA良いコンパイラはおそらく、最適化が有効になっているときに同様のアセンブリを生成します。違いは、読みやすさと、それが正しく動作する_proving_で見つけられる容易さにあります。 – YSC

+0

私はあなたが何を意味するかを見ます。本質的に 'a%b * 2/b'は** 0 ** ** ** 1 **を返します。それは' if(rem> b-rem){return quot;} else {return quot + 1;} '。パフォーマンスが他の場所になければならない点 –

0

これまでの提案に感謝します。私はパフォーマンスを比較するためのテストセットアップを行った。次のように

#include <iostream> 
#include <string> 
#include <cmath> 
#include <cstdlib> 
#include <chrono> 

using namespace std; 

int round_divide_by_float_casting(int a, int b) { 
    return (int)roundf(a/(float)b); 
} 

int round_divide_by_modulo(int a, int b) { 
    return a/b + a % b * 2/b; 
} 

int divide_by_quotient_comparison(int a, int b) 
{ 
    const std::div_t dv = std::div(a, b); 
    int result = dv.quot; 

    if (dv.rem <= b - dv.rem) { 
     ++result; 
    } 
    return result; 
} 

int main() 
{ 
    int itr = 1000; 

    //while (true) { 
     auto begin = chrono::steady_clock::now(); 
     for (int i = 0; i < itr; i++) { 
      for (int j = 10; j < itr + 1; j++) { 
       divide_by_quotient_comparison(i, j); 
      } 
     } 
     auto end = std::chrono::steady_clock::now(); 
     cout << "divide_by_quotient_comparison(,) function took : " << chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << endl; 

     begin = chrono::steady_clock::now(); 
     for (int i = 0; i < itr; i++) { 
      for (int j = 10; j < itr + 1; j++) { 
       round_divide_by_float_casting(i, j); 
      } 
     } 
     end = std::chrono::steady_clock::now(); 
     cout << "round_divide_by_float_casting(,) function took : " << chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << endl; 

     begin = chrono::steady_clock::now(); 
     for (int i = 0; i < itr; i++) { 
      for (int j = 10; j < itr + 1; j++) { 
       round_divide_by_modulo(i, j); 
      } 
     } 
     end = std::chrono::steady_clock::now(); 
     cout << "round_divide_by_modulo(,) function took : " << chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << endl; 

    //} 

    return 0; 
} 

私は私のマシンに乗った結果が(vs2015とi7の)だった:モジュロ演算が約2倍に高速INT->フローティング状態> int型鋳造方法としてでした。 のstd :: div_t(@YSCと@teroiによって提案さ)に依存する方法が速かったINT->フローティング状態> INTが、モジュロ演算方式よりも遅いこと。第二の試験は、特定のコンパイラの最適化を避けるために行われた

EDIT は@YSCが指摘: の#includeの#include の#includeの#include の#includeの#include 名前空間stdを使用します。この第二の試験で

int round_divide_by_float_casting(int a, int b) { 
    return (int)roundf(a/(float)b); 
} 

int round_divide_by_modulo(int a, int b) { 
    return a/b + a % b * 2/b; 
} 

int divide_by_quotient_comparison(int a, int b) 
{ 
    const std::div_t dv = std::div(a, b); 
    int result = dv.quot; 

    if (dv.rem <= b - dv.rem) { 
     ++result; 
    } 
    return result; 
} 

int main() 
{ 
    int itr = 100; 
    vector <int> randi, randj; 
    for (int i = 0; i < itr; i++) { 
     randi.push_back(rand()); 
     int rj = rand(); 
     if (rj == 0) rj++; 
     randj.push_back(rj); 
    } 
    vector<int> f, m, q; 

    while (true) { 
     auto begin = chrono::steady_clock::now(); 
     for (int i = 0; i < itr; i++) { 
      for (int j = 0; j < itr; j++) { 
       q.push_back(divide_by_quotient_comparison(randi[i] , randj[j])); 
      } 
     } 
     auto end = std::chrono::steady_clock::now(); 
     cout << "divide_by_quotient_comparison(,) function took : " << chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << endl; 

     begin = chrono::steady_clock::now(); 
     for (int i = 0; i < itr; i++) { 
      for (int j = 0; j < itr; j++) { 
       f.push_back(round_divide_by_float_casting(randi[i], randj[j])); 
      } 
     } 
     end = std::chrono::steady_clock::now(); 
     cout << "round_divide_by_float_casting(,) function took : " << chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << endl; 

     begin = chrono::steady_clock::now(); 
     for (int i = 0; i < itr; i++) { 
      for (int j = 0; j < itr; j++) { 
       m.push_back(round_divide_by_modulo(randi[i], randj[j])); 
      } 
     } 
     end = std::chrono::steady_clock::now(); 
     cout << "round_divide_by_modulo(,) function took : " << chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << endl; 
     cout << endl; 

     f.clear(); m.clear(); q.clear(); 
    } 

    return 0; 
} 

最も遅いがSTDに依存divide_by_quotient()た::再びdivide_by_float()続いdiv_t、最速はdivide_by_modulo()ました。しかし、今回はパフ​​ォーマンスの差が20%以下のずっと低いものでした。

+0

コンパイラはどういう意味ですか? – YSC

+0

あなたのベンチマークは非常に厄介です。コンパイラはループの順序を並べ替え、副作用のない同じ計算を最適化します。あなたはランダムなデータで試してみるべきです。 – YSC

+0

提案していただきありがとうございます。ランダムなデータを試してみましたか?私はすぐに試してみる –