2016-07-01 9 views
3

は、次のコードサンプル考えてみましょう:私は-1をすることを期待している間にsize_tのremainder演算子で負の剰余を取得するにはどうすればよいですか?

#include <iostream> 
#include <string> 

int main() 
{ 
    std::string str("someString"); // length 10 
    int num = -11; 
    std::cout << num % str.length() << std::endl; 
} 

http://cpp.shにこのコードを実行すると、私は、結果として5を取得します。

Iはstr.length()のタイプが符号なしの実装に依存し、そしてためnum符号なしsize_tsigned intから変換させる二項演算子で発生暗黙的型変換のsize_t(複数あるため、これが起こることを知っていますhere); これにより、負の値が正の値になり、操作の結果が失われます。

一つはintに明示的なキャストとの問題に対処すると考えることもできます。これはうまくいくかもしれない

num % (int)str.length() 

それはintの最大値より大きい長さの文字列の場合には、たとえば、保証されていません。 long longのようなより大きなタイプのリスクを減らすことができますが、size_tunsigned long longの場合はどうなりますか?同じ問題。

この問題は、移植性が高く堅牢な方法でどう対処しますか?

+1

これは剰余演算子です。動作は使用している標準のバージョンによって異なります。 – EJP

+0

2011年以前に書かれたCコードと2011年以前に書かれたC++コードは、この場合、矛盾した動作をする可能性があります。最新のコンパイラ/標準を使用してください。 – Lundin

+0

size_tは長い間長く表現されるかもしれませんが、私はそれが最大になるとは思っていません。あなたのプラットフォームがexa-byte文字列の長さをサポートしていない限り、私はどんな場合でも妥当な制限だと思います。 – Andreas

答えて

3

C++ 11以降は、lengthの結果をstd::string::difference_typeにキャストできます。

宛てに "サイズが大きすぎる場合はどうなりますか?"

これは、64ビットプラットフォームでは発生しません。また、小さなものでも、合計RAMの半分以上を占める文字列が最後にあったのはいつですか? difference_typeを使用して実際に特定のもの(あなたが知っているだろう)をしていない限り、それは問題ありません。戦う幽霊をやめる。

また、int64_tを使用するだけでも十分です。 (おそらく、32ビットプロセッサ上のループは、int32_tより遅いかもしれませんが、単なるモジュラス演算では問題ありません)。

(面白い事実:参考のために符号なしのタイプミスと標準ライブラリのために、

プレC++ 11は、負の値を持つ%の符号が実装定義された)、午前9時50分、42:40で1時02分50秒を this panel参照しますよく定義された振る舞いの場合は、std::divに上記のキャストの1つを加えたものを使用します。

+0

'difference_type'についての洞察に感謝します。私が正しく理解していれば、 'size_t'は文字列の長さを表現するのに十分ですが、' difference_type'は同じ文字列の2つのイテレータの可能な最大差を表すのに十分です。最大の違いは文字列の長さなので、 'size_t'を' difference_type'にキャストすることは常に安全でなければなりません。正しい? – elnigno

+0

* "' difference_type'は、同じ文字列上の2つのイテレータの可能な最大差を表現するのに十分な大きさです; "*実際にはこれはほとんど成立しますが、保証されません。 32ビットシステムでは、 'size_t'と差分型の両方が32ビットの整数になる可能性が高いので、文字列が2.5GB以上であれば、サイズは符号付きのものに収まらないでしょう。 –

3

我々は

-a % b == -(a % b) 

ですから、このような何か書くことができることを知っている:

template<typename T, typename T2> 
constexpr T safeModulo(T a, T2 b) 
{ 
    return (a >= 0 ? 1 : -1) * static_cast<T>(std::llabs(a) % b); 
} 

99で、このしませんオーバーフロー。例98%、std::size_tはその後unsigned long longT2 -> unsigned long longT -> intとして実装されている場合は、この

safeModulo(num, str.length()); 

考えるために。

としてはaは符号がオーバーフローする除去、intの可能な最小値であるかのためstd::llabs代わりにstd::absを使用することが、重要であり、コメントで指摘しました。 をlong longにプロモートすると、この問題は発生しません。long longの値の範囲がより広いためです。

static_cast<int>(std::llabs(a) % b)は、常にaより小さい値になるため、intにキャストすることは決してオーバーフロー/アンダーフローしません。 aunsigned long longに昇格されたとしても、aはすでにstd::llabs(a)から「符号なし」であるため、値は変更されない(つまり、オーバーフロー/アンダーフローしなかった)ので重要ではありません。

上記のプロパティのため、aが負の場合は、結果に-1を掛けてください。正しい結果が得られます。


astd::numeric_limits<long long>::min()とき記号を除去することは未定義の動作をもたらす、aをオーバーフローとしては、未定義の動作になる場合のみです。関数を実装する別の方法がおそらくありますが、私はそれについて考えるでしょう。

+0

ありがとう、これは理にかなっていますが、aが 'INT_MIN'なら未定義の動作につながりませんか? http://en.cppreference.com/w/cpp/numeric/math/abs – elnigno

+0

@elnigno本当に、私はその事件を完全に忘れてしまった。 'a long long 'に' a'を宣言すると、定義された振る舞いになります。ありがとう:) – Rakete1111

関連する問題