質問があります。
uint64_t var = 1; // this is 000000...00001 right?
そして、私のコードではこれが機能します:
var ^ (1 << 43)
しかし、1 が 64 ビットであることをどのように判断するのでしょうか? 代わりにこれを書くべきではありませんか?
var ^ ( (uint64_t) 1 << 43 )
あなたが推測したように、1 は単純な符号付きint
(おそらくあなたのプラットフォームでは 2 の補数演算で 32 ビット幅です) であり、43 も同様1<<43
です。int
結果も同様になることを指示しint
ます。
それでも、C では、符号付き整数のオーバーフローは未定義の動作であるため、原則として何でも起こり得ます。あなたの場合、おそらくコンパイラは64ビットレジスタでそのシフトを実行するコードを発行したので、運が良ければうまくいくようです。保証された正しい結果を得るには、作成した 2 番目の形式を使用するか、または接尾辞 (は少なくとも64 ビットであることが保証されています)を使用してリテラル1
として指定する必要があります。unsigned long long
ull
unsigned long long
var ^ ( 1ULL << 43 )
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 long
16バイト(0〜少なくとも2バイト)のマシンで実行されたとしましょう128 -1)。
以下の不自然な例: の最初の結果は^
ですuint64_t
。3 を掛けると、積は でありuint64_t
、モジュロ 2 64を実行します。オーバーフローが発生すると、結果が に割り当てられd1
ます。次の場合、 の結果は で^
あり、unsigned long long
3 を掛けると、その積は 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
バリアント サイズの型が計算を混乱させることは望ましくありません。
unit64_t
定数を持つポータブルな方法は、UINT64_C
マクロを使用することです (からstdint.h
):
UINT64_C(1) << 43
ほとんどの場合、 のUINT64_C(c)
ようなものに定義されていc ## ULL
ます。
C標準から:
マクロ
INT
Nは、型N_C(value)
に対応する整数定数式に展開されます。マクロCは、型Nに対応する整数定数式に展開され ます。たとえば、 が型の名前である場合、整数 定数に展開される可能性があります。int_least
_t
UINTN_
(value)
uint_least
_t
uint_least64_t
unsigned long long int
UINT64_C(0x123)
0x123ULL
var ^ ( 1ULL << 43 )
するべきです。
コンパイラは、シフトを 64 ビットで行う必要があることを知りません。ただし、この特定のコードに対してこの特定の構成でコンパイラのこの特定のバージョンを使用すると、2 つの間違いが起こります。それを頼りにしないでください。
int
それがあなたのプラットフォームで 32 ビット型であると仮定すると(その可能性は非常に高い)、2 つの誤りは次の1 << 43
とおりです。
x
型の場合、またはn ≥ 32 の場合と同様に、 の動作が未定義であることを意味します。たとえば、動作も未定義です。int
unsigned int
x << 43
x << 32
x << n
1u << 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) …