32

質問があります。

uint64_t var = 1; // this is 000000...00001 right?

そして、私のコードではこれが機能します:

var ^ (1 << 43)

しかし、1 が 64 ビットであることをどのように判断するのでしょうか? 代わりにこれを書くべきではありませんか?

var ^ ( (uint64_t) 1 << 43 )
4

5 に答える 5

35

あなたが推測したように、1 は単純な符号付きint(おそらくあなたのプラットフォームでは 2 の補数演算で 32 ビット幅です) であり、43 も同様1<<43です。int結果も同様になることを指示しintます。

それでも、C では、符号付き整数のオーバーフローは未定義の動作であるため、原則として何でも起こり得ます。あなたの場合、おそらくコンパイラは64ビットレジスタでそのシフトを実行するコードを発行したので、運が良ければうまくいくようです。保証された正しい結果を得るには、作成した 2 番目の形式を使用するか、または接尾辞 (は少なくとも64 ビットであることが保証されています)を使用してリテラル1として指定する必要があります。unsigned long longullunsigned long long

var ^ ( 1ULL << 43 )
于 2013-10-18T13:45:54.680 に答える
11

OPのアプローチをお勧めします。定数をキャストします( (uint64_t) 1 << 43 )

OPの小さな例では、以下の2つは同じように実行される可能性があります。

uint64_t var = 1; 
// OP solution)
var ^ ( (uint64_t) 1 << 43 )
// Others suggested answer
var ^ ( 1ULL << 43 )        

上記の結果は同じ値ですが、タイプが異なります。潜在的な違いは、C: に 2 つの型がどのように存在するかuint64_t、およびその後unsigned long longに何が続くかにあります。

uint64_t正確な範囲は 0 ~ 2 64 -1 です。
unsigned long long範囲は 0 から少なくとも2 64 -1 です。

unsigned long long多くのマシンでそうであるように、常に64ビットである場合、問題はありませんが、将来に目を向けて、このコードがunsigned long long16バイト(0〜少なくとも2バイト)のマシンで実行されたとしましょう128 -1)。

以下の不自然な例: の最初の結果は^ですuint64_t。3 を掛けると、積は でありuint64_t、モジュロ 2 64を実行します。オーバーフローが発生すると、結果が に割り当てられd1ます。次の場合、 の結果は で^あり、unsigned long long3 を掛けると、その積は 2 64よりも大きくなる可能性があり、これが に割り当てられd2ます。だから、別の答えd1を持っています。d2

double d1, d2;
d1 = 3*(var ^ ( (uint64_t) 1 << 43 ));
d2 = 3*(var ^ ( 1ULL << 43 ));

で作業したい場合はunit64_t、一貫性を保ってください。unit64_t想定していないunsigned long longものと同じです。答えが でよければunsigned long long、結構です。しかし、私の経験では、 のような固定サイズの型を使い始めた場合、uint64_tバリアント サイズの型が計算を混乱させることは望ましくありません。

于 2013-10-18T15:20:59.480 に答える
4

unit64_t定数を持つポータブルな方法は、UINT64_Cマクロを使用することです (からstdint.h):

UINT64_C(1) << 43

ほとんどの場合、 のUINT64_C(c)ようなものに定義されていc ## ULLます。

C標準から:

マクロINTNは、型N_C(value)に対応する整数定数式に展開されます。マクロCは、型Nに対応する整数定数式に展開され ます。たとえば、 が型の名前である場合、整数 定数に展開される可能性があります。int_least_tUINTN_(value)uint_least_tuint_least64_tunsigned long long intUINT64_C(0x123)0x123ULL

于 2013-10-18T19:36:53.887 に答える
4

var ^ ( 1ULL << 43 )するべきです。

于 2013-10-18T13:47:24.013 に答える
3

コンパイラは、シフトを 64 ビットで行う必要があることを知りません。ただし、この特定のコードに対してこの特定の構成でコンパイラのこの特定のバージョンを使用すると、2 つの間違いが起こります。それを頼りにしないでください。

intそれがあなたのプラットフォームで 32 ビット型であると仮定すると(その可能性は非常に高い)、2 つの誤りは次の1 << 43とおりです。

  • シフト量が左オペランドの型の幅以上の場合、動作は未定義です。これは、がorx型の場合、またはn ≥ 32 の場合と同様に、 の動作が未定義であることを意味します。たとえば、動作も未定義です。intunsigned intx << 43x << 32x << n1u << 43
  • 左オペランドに符号付きの型があり、演算の結果がその型をオーバーフローする場合、動作は未定義です。たとえば0x12345 << 16、左のオペランドの型は符号付きの型intですが、結果の値が に収まらないため、未定義の動作がありintます。一方、0x12345u << 16は明確に定義されており、値は です0x23450000u

「未定義の動作」とは、クラッシュしたり間違った結果を返すコードをコンパイラが自由に生成できることを意味します。この場合、目的の結果が得られることがあります — これは禁止されているわけではありませんが、マーフィーの法則により、生成されたコードが意図したとおりに動作しなくなる日が来ることが規定されています。

演算が 64 ビット型で確実に行われるようにするには、左側のオペランドが 64 ビット型であることを確認する必要があります。結果を代入する変数の型は関係ありません。float x = 1 / 2これは、0.5 ではなく 0 を含む結果と同じ問題xです。算術演算子の動作を決定するには、オペランドの型のみが重要です。または(uint64)1 << 43または(long long)1 << 43または(unsigned long long)1 << 43または1ll << 43または1ull << 43しましょう。符号付きの型を使用する場合、オーバーフローがない場合にのみ動作が定義されるため、オーバーフロー時に切り捨てが予想される場合は、必ず符号なしの型を使用してください。動作が再現可能であるため、オーバーフローが発生することが想定されていない場合でも、通常は unsigned 型が推奨されます。符号付き型を使用すると、デバッグ目的で値を出力するだけで動作が変わる可能性がありますマイクロレベルで最も効率的なコードを生成するための未定義の動作の影響を受けます。これは、レジスタ割り当てに対するプレッシャーなどに非常に敏感です)

結果を type にするつもりなので、uint64_tその型ですべての計算を実行する方が明確です。したがって:

uint64_t var = 1;
… var ^ ((uint64_t)1 << 43) …
于 2013-10-19T17:13:13.647 に答える