as 引数を取り、合同モジュロ UINT_MAX+1 をunsigned int
引数に返す関数を定義したいと考えています。int
最初の試行は次のようになります。
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
しかし、どの言語弁護士も知っているように、INT_MAX より大きい値の unsigned から signed へのキャストは実装定義です。
(a) 仕様で義務付けられている動作のみに依存するようにこれを実装したいと考えています。(b)最新のマシンと最適化コンパイラでノーオペレーションにコンパイルされます。
奇妙なマシンについては... unsigned int に対する unsigned int 合同法 UINT_MAX+1 がない場合、例外をスローしたいとしましょう。複数ある場合 (これが可能かどうかはわかりません)、最大のものが欲しいとしましょう。
OK、2 回目の試行:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
典型的な 2 の補数システムを使用していないときは、効率はあまり気にしません。そして、私のコードが 2050 年のどこにでもあるサインマグニチュード システムのボトルネックになったとしても、誰かがそれを見つけて最適化できるに違いありません。
さて、この 2 回目の試行は、私が望むものにかなり近いものです。一部の入力については、へのキャストint
は実装定義ですが、へのキャスト バックunsigned
は、モジュロ UINT_MAX+1 の値を保持することが標準によって保証されています。したがって、条件は私が望むものを正確にチェックし、遭遇する可能性のあるシステムでは何もコンパイルしません。
ただし...int
実装定義の動作を呼び出すかどうかを最初に確認せずに、まだキャストしています。2050 年の仮想システムでは、誰が何を知っているかがわかります。だから私はそれを避けたいとしましょう。
質問: 「3 回目の試行」はどのように表示されますか?
要約すると、次のことを行います。
- unsigned int から signed int へのキャスト
- 値 mod UINT_MAX+1 を保持します
- 標準で義務付けられた動作のみを呼び出す
- 最適化コンパイラを使用して、典型的な 2 の補数のマシンでノーオペレーションにコンパイルします。
[アップデート]
これが些細な質問ではない理由を示す例を挙げましょう。
次のプロパティを持つ架空の C++ 実装を検討してください。
sizeof(int)
4に等しいsizeof(unsigned)
4に等しいINT_MAX
32767 に等しいINT_MIN
-2 32 + 32768に等しいUINT_MAX
2 32 - 1に等しい- の算術演算
int
は 2 32を法として( ~ の範囲INT_MIN
にINT_MAX
) std::numeric_limits<int>::is_modulo
本当です- unsigned
n
を int にキャストすると、0 <= n <= 32767 の値が保持され、それ以外の場合はゼロになります。
この仮想的な実装では、int
各値に一致する値 (mod UINT_MAX+1) が 1つだけありunsigned
ます。したがって、私の質問は明確に定義されます。
この架空の C++ 実装は、C++98、C++03、および C++11 の仕様に完全に準拠していると主張します。私はそれらすべての単語をすべて覚えていないことを認めます...しかし、関連するセクションを注意深く読んだと思います。したがって、私にあなたの答えを受け入れてもらいたい場合は、(a) この架空の実装を除外する仕様を引用するか、(b) 正しく処理する必要があります。
実際、正解は、標準で許可されているすべての仮想実装を処理する必要があります。それが、定義上、「標準で義務付けられた動作のみを呼び出す」という意味です。
std::numeric_limits<int>::is_modulo
ちなみに、ここでは複数の理由でまったく役に立たないことに注意してください。1 つには、true
unsigned-to-signed キャストが大きな unsigned 値に対して機能しない場合でも、可能性があります。別の例ではtrue
、算術演算が整数範囲全体を単純にモジュロする場合、1 の補数または符号 - マグニチュード システムでも可能です。等々。あなたの答えが に依存しているならis_modulo
、それは間違っています。
【アップデート2】
hvd の答えは私に何かを教えてくれました: 整数に対する私の架空の C++ 実装は、最新の C では許可されていません。C99および C11 標準は、符号付き整数の表現について非常に具体的です。実際、それらは 2 の補数、1 の補数、および符号の大きさのみを許可します (セクション 6.2.6.2 パラグラフ (2); )。
しかし、C++ は C ではありません。結局のところ、この事実が私の質問の核心にあるのです。
元の C++98 標準は、はるかに古い C89 に基づいていました (セクション 3.1.2.5):
符号付き整数型ごとに、対応する (ただし異なる) 符号なし整数型 (キーワード unsigned で指定) があり、同じ量のストレージ (符号情報を含む) を使用し、同じ配置要件があります。符号付き整数型の負でない値の範囲は、対応する符号なし整数型の部分範囲であり、各型の同じ値の表現は同じです。
C89 では、符号ビットが 1 つしかないことや、2 の補数/1 の補数/符号の大きさしか許可されていないことについては何も述べていません。
C++98 標準では、この言語をほぼそのまま採用しています (セクション 3.9.1 段落 (3))。
それぞれの符号付き整数型には、対応する (ただし異なる)符号なし整数型"
unsigned char
"、"unsigned short int
"、"unsigned int
"、および "unsigned long int
" が存在し、それぞれが同じ量のストレージを占有し、同じアライメント要件 (3.9 ) 対応する符号付き整数型として; つまり、各符号付き整数型は、対応する符号なし整数型と同じオブジェクト表現を持ちます。符号付き整数型の負でない値の範囲は、対応する符号なし整数型の部分範囲であり、対応する各符号付き/符号なし型の値表現は同じでなければなりません。
C++03 標準は、C++11 と同様に、本質的に同一の言語を使用します。
私が知る限り、標準の C++ 仕様では、符号付き整数表現を C 仕様に制限することはできません。そして、単一の符号ビットまたはその種のものを義務付けるものは何もありません。それが言っているのは、負でない符号付き整数は対応する符号なしの部分範囲でなければならないということだけです。
したがって、ここでも、INT_MIN=-2 32 +32768を指定した INT_MAX=32767が許可されていると主張します。あなたの答えがそうでないと想定している場合、 C++標準を引用して私が間違っていることを証明しない限り、それは正しくありません。