実装定義の動作を回避する効率的な符号なしから符号付きのキャスト


94

unsigned intas引数を取り、intUINT_MAX +1を法とする合同を引数に返す関数を定義したいと思います。

最初の試みは次のようになります。

int unsigned_to_signed(unsigned n)
{
    return static_cast<int>(n);
}

しかし、言語弁護士なら誰でも知っているように、INT_MAXより大きい値の符号なしから符号付きへのキャストは、実装によって定義されます。

(a)仕様で義務付けられている動作のみに依存するようにこれを実装したいと思います。(b)最新のマシンおよび最適化コンパイラでno-opにコンパイルされます。

奇妙なマシンに関しては... unsignedintに対してUINT_MAX + 1を法とするsignedint合同がない場合、例外をスローしたいとしましょう。複数ある場合(これが可能かどうかはわかりません)、最大のものが必要だとしましょう。

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回目の試み」はどのように見えるべきですか?

要約すると、私はしたい:

  • unsignedintからsignedintにキャストします
  • 値modUINT_MAX +1を保持します
  • 標準で義務付けられている動作のみを呼び出す
  • 最適化コンパイラを使用して、一般的な2の補数マシンでno-opにコンパイルします。

[更新]

これが些細な質問ではない理由を示す例を挙げましょう。

次のプロパティを持つ架空のC ++実装について考えてみます。

  • sizeof(int) 4に等しい
  • sizeof(unsigned) 4に等しい
  • INT_MAX 32767に等しい
  • INT_MIN-2 32 +32768に等しい
  • UINT_MAX2に等しい32 - 1
  • 演算には、intモジュロ2 32(範囲内にINT_MINスルーINT_MAX
  • std::numeric_limits<int>::is_modulo 本当です
  • unsignednをintにキャストすると、0 <= n <= 32767の値が保持され、それ以外の場合はゼロになります。

この架空の実装ではint、各unsigned値に一致する値が1つだけあります(mod UINT_MAX + 1)。したがって、私の質問は明確に定義されます。

この架空のC ++実装は、C ++ 98、C ++ 03、およびC ++ 11仕様に完全に準拠していると私は主張します。私はそれらすべての単語をすべて覚えているわけではないことを認めます...しかし、私は関連するセクションを注意深く読んだと思います。したがって、私にあなたの答えを受け入れてもらいたい場合は、(a)この架空の実装を除外する仕様を引用するか、(b)それを正しく処理する必要があります。

実際、正解は、標準で許可されているすべての仮想実装を処理する必要があります。それが、定義上、「標準で義務付けられた動作のみを呼び出す」という意味です。

ちなみに、std::numeric_limits<int>::is_moduloここでは複数の理由でまったく役に立たないことに注意してください。一つには、trueunsigned-to-signedキャストが大きなunsigned値に対して機能しない場合でも可能です。もう1つは、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_MAX = 32767、INT_MIN = -2 32 + 32768が許可されていると主張します。あなたの答えがそうではないと仮定した場合、私が間違っていることを証明するC ++標準を引用しない限り、それは正しくありません。


@SteveJessop:実際、私はその場合に必要なことを正確に述べました。「符号なし整数に対してUINT_MAX +1を法とする符号付き整数合同がない場合は例外をスローしたいとしましょう。」つまり、「正しい」符号付き整数が存在する場合はそれが必要です。それが存在しない場合(たとえば、パディングビットや1の補数表現の場合に発生する可能性があります)、それを検出して、キャストの特定の呼び出しに対して処理します。
ネモ

すみません、どうしてそれを逃したのかわかりません。
スティーブジェソップ2013

ところで、あなたの仮定のトリッキーな実装でintは、それを表すために少なくとも33ビットが必要だと思います。私はそれが脚注にすぎないことを知っているので、それは非規範的であると主張することができますが、C ++ 11の脚注49は真実であることが意図されていると思います(それは標準で使用される用語の定義であるため)そしてそれは矛盾しません規範的なテキストで明示的に述べられているもの。したがって、すべての負の値は、最上位ビットが設定されているビットパターンで表す必要があります。したがって2^32 - 32768、それらを32ビットに詰め込むことはできません。あなたの議論がint。のサイズに何らかの形で依存しているわけではありません。
Steve Jessop 2013

そして、hvdの回答での編集に関して、注49を誤って解釈したと思います。符号の大きさは禁止されていると言いますが、そうではありません。「連続するビットで表される値は加算的で、1から始まり、(おそらく最も高い位置にあるビットを除いて、2の連続する整数乗で乗算されます)」と読みます。おそらく最も高い位置にあるビットを除いて、「連続するビットで表される値(加算的で、1から始まり、連続する2の整数乗で乗算される)」と読む必要があると思います。つまり、上位ビットが設定されている場合、すべてのベットがオフになります。
Steve Jessop 2013

@SteveJessop:あなたの解釈は正しいかもしれません。もしそうなら、それは私の仮説を除外します...しかし、それはまた本当に膨大な数の可能性をもたらし、この質問に答えることを非常に難しくします。これは実際には仕様のバグのように見えます。(どうやら、C委員会はそう考え、C99で徹底的に修正しました。なぜC ++ 11が彼らのアプローチを採用しなかったのだろうか?)
Nemo

回答:


70

user71404の答えを拡張する:

int f(unsigned x)
{
    if (x <= INT_MAX)
        return static_cast<int>(x);

    if (x >= INT_MIN)
        return static_cast<int>(x - INT_MIN) + INT_MIN;

    throw x; // Or whatever else you like
}

場合はx >= INT_MIN(心の中でプロモーションルールを守る、INT_MINに変換されますunsigned、その後、) x - INT_MIN <= INT_MAX、これは任意のオーバーフローを持っていません。

それが明らかでない場合はx >= -4u、「If 、then x + 4 <= 3。」という主張を見て、INT_MAX少なくとも-INT_MIN-1の数学的な値に等しいことを覚えておいてください。

最も一般的なシステムでは、が!(x <= INT_MAX)意味するx >= INT_MIN場合、オプティマイザーは2番目のチェックを削除し、2つのreturnステートメントを同じコードにコンパイルできることを確認し、最初のチェックも削除できる必要があります。生成されたアセンブリリスト:

__Z1fj:
LFB6:
    .cfi_startproc
    movl    4(%esp), %eax
    ret
    .cfi_endproc

あなたの質問における架空の実装:

  • INT_MAXは32767に等しい
  • INT_MINに等しい-2 32 + 32768

不可能なので、特別な配慮は必要ありません。、またはのINT_MINいずれか-INT_MAXに等しくなります-INT_MAX - 1。これは、nビットが値ビットであり、1ビットが符号ビットである必要があり、単一のトラップ表現(パディングビットのために無効な表現を含まない)のみを許可する整数型のCの表現(6.2.6.2)に続くものです。つまり、そうでなければ負のゼロ/を表すものです-INT_MAX - 1。C ++では、Cで許可されている以上の整数表現は許可されていません。

更新:Microsoftのコンパイラは明らかにそれに気づかず、同じことx > 10x >= 11テストします。x >= INT_MINがに置き換えられた場合にのみ、目的のコードが生成されます。これx > INT_MIN - 1uは、x <= INT_MAX(このプラットフォームでは)の否定として検出できます。

[質問者(Nemo)からの更新、以下の議論について詳しく説明します]

私は今、この答えはすべての場合に機能すると信じていますが、複雑な理由があります。私はこの解決策に報奨金を与える可能性がありますが、誰かが気にかけている場合に備えて、すべての厄介な詳細をキャプチャしたいと思います。

C ++ 11のセクション18.3.3から始めましょう:

表31に、ヘッダーを示し<climits>ます。

..。

内容は標準Cライブラリヘッダーと同じ<limits.h>です。

ここで、「標準C」とはC99を意味し、その仕様は符号付き整数の表現を厳しく制限します。これらは符号なし整数と同じですが、1ビットが「符号」専用で、0ビット以上が「パディング」専用です。パディングビットは整数の値には寄与せず、符号ビットは2の補数、1の補数、または符号の大きさとしてのみ寄与します。

C ++ 11<climits>はC99からマクロを継承するため、INT_MINは-INT_MAXまたは-INT_MAX-1のいずれかであり、hvdのコードは確実に機能します。(パディングにより、INT_MAXはUINT_MAX / 2よりもはるかに小さい可能性があることに注意してください...しかし、signed-> unsignedキャストの動作方法のおかげで、この回答はそれをうまく処理します。)

C ++ 03 / C ++ 98は扱いにくいです。<climits>「標準C」から継承するために同じ表現を使用しますが、「標準C」はC89 / C90を意味します。

これらすべて(C ++ 98、C ++ 03、C89 / C90)には、私の質問で示した表現がありますが、これも含まれています(C ++ 03セクション3.9.1段落7)。

整数型の表現は、純粋な2進記数法を使用して値を定義するものとします。(44)[:この国際規格では、整数型の2の補数、1の補数、および符号付きの大きさの表現が許可されています。]

脚注(44)は、「純粋な2進記数法」を定義しています。

2進数の0と1を使用する整数の位置表現。連続するビットで表される値は加算され、1で始まり、2の連続する整数乗が乗算されます。ただし、おそらく最も高い位置にあるビットは除きます。

この言い回しの興味深い点は、「純粋な2進記数法」の定義では符号/大きさの表現が許可されていないため、矛盾していることです。たとえば、上位ビットの値を-2 n-1(2の補数)または-(2 n-1 -1)(1の補数)にすることができます。ただし、符号/大きさをもたらす上位ビットの値はありません。

とにかく、私の「仮想実装」は、この定義では「純粋なバイナリ」とは見なされないため、除外されます。

ただし、上位ビットが特別であるという事実は、それが任意の値(小さい正の値、大きい正の値、小さい負の値、または大きい負の値)に寄与することを想像できることを意味します。(符号ビットが-(2 n-1 -1)に寄与することができるのなら、なぜ-(2 n-1 -2)ではないのですか?など)

それで、「符号」ビットに奇抜な値を割り当てる符号付き整数表現を想像してみましょう。

符号ビットの小さな正の値は、の正の範囲int(おそらくと同じくらい大きいunsigned)になり、hvdのコードはそれをうまく処理します。

符号ビットの正の値がint大きいと、最大値がunsigned、より大きくなりますが、これは禁止されています。

符号ビットの大きな負の値は、値のint非連続的な範囲を表すことになり、仕様内の他の表現はそれを除外します。

最後に、小さな負の量に寄与する符号ビットはどうですか?「符号ビット」に1を入れて、たとえば-37をintの値に寄与させることはできますか?したがって、INT_MAXは(たとえば)2 31 -1になり、INT_MINは-37になりますか?

これにより、いくつかの数値は2つの表現を持つことになります...しかし、1の補数は2つの表現をゼロにします。これは、「例」に従って許可されています。仕様では、2つの表現を持つ可能性のある整数がゼロだけであるとはどこにも述べられていません。したがって、この新しい仮説は仕様で許可されていると思います。

実際、-1から下までの負の値-INT_MAX-1は「符号ビット」の値として許容されるように見えますが、それよりも小さいものはありません(範囲が連続しないようにします)。つまり、から-1INT_MINまでのいずれかになります-INT_MAX-1

さて、何を推測しますか?実装定義の動作を回避するためのhvdのコードの2番目のキャストでは、x - (unsigned)INT_MIN以下が必要INT_MAXです。先ほど示したのINT_MINは少なくとも-INT_MAX-1です。明らかに、xはせいぜいUINT_MAXです。unsignedに負の数をキャストすることは、を追加することと同じUINT_MAX+1です。すべてをまとめる:

x - (unsigned)INT_MIN <= INT_MAX

場合に限り

UINT_MAX - (INT_MIN + UINT_MAX + 1) <= INT_MAX
-INT_MIN-1 <= INT_MAX
-INT_MIN <= INT_MAX+1
INT_MIN >= -INT_MAX-1

これが最後に示したものなので、このひねくれた場合でも、コードは実際に機能します。

それはすべての可能性を使い果たし、したがってこの非常に学術的な演習を終了します。

結論:C ++ 98 / C ++ 03に継承された、C89 / C90の符号付き整数には、大幅に指定されていない動作がいくつかあります。これはC99で修正されており、C ++ 11<limits.h>はC99から組み込むことで間接的に修正を継承しています。しかし、C ++ 11でさえ、自己矛盾する「純粋なバイナリ表現」という表現を保持しています...


質問が更新されました。私は他の人を落胆させるために(今のところ)この回答に反対票を投じています...答えが面白いので後で反対票を投じます。(Cの場合は正しいが、C ++の場合は間違っていると思います。)
Nemo

@Nemoこの場合、C標準はC ++に適用されます。最低でも、中の値は<limits.h>、Cの標準と同じ意味を持つものとしてC ++標準で定義されているので、すべてのためのCの要件のされているINT_MINと、INT_MAXC ++で継承されます。C ++ 03がC90を参照し、C90が許可される整数表現について曖昧であるというのは正しいですが、C99の変更(少なくとも<limits.h>C ++ 11によって継承され、できればもっと簡単な方法でも)に制限されます。これらの3つは、既存の慣行を成文化したものでした。他の実装は存在しませんでした。

INT_MINなどの意味はCから受け継がれていることに同意します。しかし、それはがそうであるという意味ではありません。(実際、すべての実装が異なるので、どうすればよいでしょうか?)INT_MIN1以内の推論-INT_MAXは、C ++仕様にはまったく表示されない表現に依存します。したがって、C ++はマクロのセマンティックな意味を継承しますが、仕様は推論をサポートする表現を提供(または継承)しません。これは、C ++仕様の見落としであり、完全に準拠した効率的な符号なしから符号付きのキャストを妨げているようです。
ネモ

@Nemo C ++が他の表現を許可すると(おそらく正しく)主張する場合、そのような実装でINT_MIN int、Cに関する限り、型がそうでない場合は、型の表現可能な最小値である必要はないと主張します。の要件に一致するint場合、C標準はその実装をまったくカバーできない可能性があり、C ++標準は「C標準が言うこと」以外の定義を提供しません。もっと簡単な説明があるかどうかを確認します。

7
これはゴージャスです。当時、私がこの質問をどのように見逃したのかわかりません。
軌道上でのライトネスレース2013年

17

このコードは、仕様で義務付けられている動作のみに依存しているため、要件(a)は簡単に満たされます。

int unsigned_to_signed(unsigned n)
{
  int result = INT_MAX;

  if (n > INT_MAX && n < INT_MIN)
    throw runtime_error("no signed int for this number");

  for (unsigned i = INT_MAX; i != n; --i)
    --result;

  return result;
}

要件(b)ではそれほど簡単ではありません。これは、gcc 4.6.3(-Os、-O2、-O3)およびclang 3.0(-Os、-O、-O2、-O3)でno-opにコンパイルされます。Intel 12.1.0は、これを最適化することを拒否します。そして、私はVisualCについての情報を持っていません。


1
OK、これはすごいです。バウンティを80:20に分割できたらいいのにと思います...コンパイラの推論は次のようになります。ループが終了しない場合、resultオーバーフローします。整数オーバーフローは未定義です。したがって、ループは終了します。したがってi == n、終了時。したがって、にresult等しくなりnます。私はまだhvdの答えを好む必要があります(あまりスマートでないコンパイラでの非病理学的な振る舞いのために)が、これはもっと賛成票に値します。
ネモ

1
符号なしはモジュロとして定義されます。nは符号なしの値であり、i最終的にはすべての符号なしの値に到達する必要があるため、ループの終了も保証されます。
idupree 2013

7

元の答えはunsigned=>の場合にのみ問題を解決しましたint。「いくつかの符号なしタイプ」の一般的な問題を対応する符号付きタイプに解決したい場合はどうなりますか?さらに、元の回答は、標準のセクションを引用し、いくつかのコーナーケースを分析するのに優れていましたが、なぜそれが機能したのかを理解するのにあまり役立たなかったので、この回答は強力な概念的基礎を与えることを試みます。この回答は、「理由」の説明に役立ち、最新のC ++機能を使用してコードを単純化しようとします。

C ++ 20の回答

問題はP0907で劇的に単純化されました。符号付き整数は2の補数であり、C ++ 20標準に投票された最後の文言P1236です。さて、答えは可能な限り簡単です:

template<std::unsigned_integral T>
constexpr auto cast_to_signed_integer(T const value) {
    return static_cast<std::make_signed_t<T>>(value);
}

それでおしまい。A static_cast(またはCスタイルのキャスト)は、最終的にこの質問のために必要なことを行うことが保証されており、事多くのプログラマは、それが常にやったと思いました。

C ++ 17の答え

C ++ 17では、物事ははるかに複雑です。3つの可能な整数表現(2の補数、1の補数、および符号の大きさ)を処理する必要があります。可能な値の範囲を確認したために2の補数でなければならないことがわかっている場合でも、符号付き整数の範囲外の値をその符号付き整数に変換すると、実装定義の結果が得られます。他の回答で見たようなトリックを使用する必要があります。

まず、問題を一般的に解決する方法のコードを次に示します。

template<typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
constexpr auto cast_to_signed_integer(T const value) {
    using result = std::make_signed_t<T>;
    using result_limits = std::numeric_limits<result>;
    if constexpr (result_limits::min() + 1 != -result_limits::max()) {
        if (value == static_cast<T>(result_limits::max()) + 1) {
            throw std::runtime_error("Cannot convert the maximum possible unsigned to a signed value on this system");
        }
    }
    if (value <= result_limits::max()) {
        return static_cast<result>(value);
    } else {
        using promoted_unsigned = std::conditional_t<sizeof(T) <= sizeof(unsigned), unsigned, T>;
        using promoted_signed = std::make_signed_t<promoted_unsigned>;
        constexpr auto shift_by_window = [](auto x) {
            // static_cast to avoid conversion warning
            return x - static_cast<decltype(x)>(result_limits::max()) - 1;
        };
        return static_cast<result>(
            shift_by_window( // shift values from common range to negative range
                static_cast<promoted_signed>(
                    shift_by_window( // shift large values into common range
                        static_cast<promoted_unsigned>(value) // cast to avoid promotion to int
                    )
                )
            )
        );
    }
}

これには、受け入れられた回答よりもいくつかのキャストがあります。これは、コンパイラからの符号付き/符号なしの不一致の警告がないことを確認し、整数の昇格ルールを適切に処理するためです。

最初に、2の補数ではないシステムの特殊なケースがあります(したがって、マップするものがないため、可能な最大値を特別に処理する必要があります)。その後、実際のアルゴリズムに到達します。

2番目の最上位条件は単純です。値が最大値以下であることがわかっているため、結果タイプに適合します。3番目の条件は、コメントがあっても少し複雑なので、いくつかの例は、各ステートメントが必要な理由を理解するのに役立つでしょう。

概念的根拠:数直線

まず、このwindowコンセプトは何ですか?次の数直線を考えてみましょう。

   |   signed   |
<.........................>
          |  unsigned  |

2の補数の整数の場合、どちらのタイプでも到達できる数直線のサブセットを3つの同じサイズのカテゴリに分割できることがわかります。

- => signed only
= => both
+ => unsigned only

<..-------=======+++++++..>

これは、表現を検討することで簡単に証明できます。符号なし整数は、で始まり、0すべてのビットを使用して値を2の累乗で増やします。符号付き整数は、の-(2^position)代わりに価値がある符号ビットを除いて、すべてのビットでまったく同じです2^position。これは、すべてのn - 1ビットで、それらが同じ値を表すことを意味します。次に、符号なし整数にはもう1つの通常のビットがあり、値の総数が2倍になります(つまり、そのビットが設定されている場合は、設定されていない場合と同じ数の値があります)。そのビットが設定されているすべての値が負であることを除いて、同じ論理が符号付き整数にも当てはまります。

他の2つの有効な整数表現、1の補数と符号の大きさは、1つを除いて、すべて2の補数の整数と同じ値を持ちます。C ++は、ビット表現ではなく、表現可能な値の範囲に関して、reinterpret_cast(およびC ++ 20 std::bit_cast)を除いて、整数型に関するすべてを定義します。これは、トラップ表現を作成しようとしない限り、これら3つの表現のそれぞれについて分析が保持されることを意味します。この欠落した値にマップされる符号なしの値は、かなり残念な値です。つまり、符号なしの値の真ん中にある値です。幸い、最初の条件は(コンパイル時に)そのような表現が存在するかどうかをチェックし、次にランタイムチェックで特別に処理します。

最初の条件は、=セクションにいる場合を処理します。つまり、一方の値を変更せずにもう一方の値で表すことができる重複領域にいることを意味します。shift_by_windowコード内の関数は、これらの各セグメントのサイズだけすべての値を下に移動します(算術オーバーフローの問題を回避するには、最大値を減算してから1を減算する必要があります)。その領域の外にいる場合(その領域にいる場合+)、ウィンドウサイズを1つ下げる必要があります。これにより、重複範囲になります。つまり、値に変化がないため、符号なしから符号付きに安全に変換できます。ただし、2つの符号なし値を各符号付き値にマップしたため、まだ完了していません。したがって、次のウィンドウにシフトダウンする必要があります(- リージョン)を使用して、一意のマッピングを再度作成します。

さて、これUINT_MAX + 1は質問で要求されたように、結果が一致するmodを与えますか?UINT_MAX + 1は、と同等です2^n。ここnで、は値表現のビット数です。ウィンドウサイズに使用する値は次のとおりです2^(n - 1)(一連の値の最終的なインデックスはサイズより1つ小さくなります)。その値を2回減算します。つまり、に2 * 2^(n - 1)等しい値を減算します2^nx算術modxでは、加算と減算は何の操作も行わないため、元の値modには影響しません2^n

整数プロモーションを適切に処理する

これは一般的な関数であり、とだけintunsignedはないため、汎整数拡張ルールにも注意を払う必要があります。おそらく興味深いケースが2つあります。1つshortはよりも小さい場合int、もう1つshortはと同じサイズの場合intです。

例:shortより小さいint

shortint(最近のプラットフォームで一般的)よりも小さい場合は、にunsigned short収まる可能性があることもわかってintいます。つまり、での操作は実際にで行われるintため、これを回避するためにプロモート型に明示的にキャストします。最終的なステートメントはかなり抽象的であり、実際の値に置き換えると理解しやすくなります。一般性を失うことなく、最初の興味深いケースとして、16ビットshortと17ビットを考えてみましょうint(これは新しいルールでも許可されており、これら2つの整数型の少なくとも1つにいくつかのパディングビットがあることを意味します)::

constexpr auto shift_by_window = [](auto x) {
    return x - static_cast<decltype(x)>(32767) - 1;
};
return static_cast<int16_t>(
    shift_by_window(
        static_cast<int17_t>(
            shift_by_window(
                static_cast<uint17_t>(value)
            )
        )
    )
);

可能な限り最大の16ビット符号なし値を解く

constexpr auto shift_by_window = [](auto x) {
    return x - static_cast<decltype(x)>(32767) - 1;
};
return int16_t(
    shift_by_window(
        int17_t(
            shift_by_window(
                uint17_t(65535)
            )
        )
    )
);

に簡略化

return int16_t(
    int17_t(
        uint17_t(65535) - uint17_t(32767) - 1
    ) -
    int17_t(32767) -
    1
);

に簡略化

return int16_t(
    int17_t(uint17_t(32767)) -
    int17_t(32767) -
    1
);

に簡略化

return int16_t(
    int17_t(32767) -
    int17_t(32767) -
    1
);

に簡略化

return int16_t(-1);

可能な限り最大の署名なしを入れて-1、成功を取り戻します!

例:shortと同じサイズint

shortがと同じサイズの場合int(最近のプラットフォームでは一般的ではありません)、汎整数拡張ルールはわずかに異なります。この場合、にshortプロモートしint、にunsigned shortプロモートしunsignedます。幸い、各結果を計算を実行するタイプに明示的にキャストしているため、問題のあるプロモーションは発生しません。一般性を失うことなく、16ビットshortと16ビットを考えてみましょうint

constexpr auto shift_by_window = [](auto x) {
    return x - static_cast<decltype(x)>(32767) - 1;
};
return static_cast<int16_t>(
    shift_by_window(
        static_cast<int16_t>(
            shift_by_window(
                static_cast<uint16_t>(value)
            )
        )
    )
);

可能な限り最大の16ビット符号なし値を解く

auto x = int16_t(
    uint16_t(65535) - uint16_t(32767) - 1
);
return int16_t(
    x - int16_t(32767) - 1
);

に簡略化

return int16_t(
    int16_t(32767) - int16_t(32767) - 1
);

に簡略化

return int16_t(-1);

可能な限り最大の署名なしを入れて-1、成功を取り戻します!

元の質問のように、警告だけを気にしint、気にunsignedしない場合はどうなりますか?

constexpr int cast_to_signed_integer(unsigned const value) {
    using result_limits = std::numeric_limits<int>;
    if constexpr (result_limits::min() + 1 != -result_limits::max()) {
        if (value == static_cast<unsigned>(result_limits::max()) + 1) {
            throw std::runtime_error("Cannot convert the maximum possible unsigned to a signed value on this system");
        }
    }
    if (value <= result_limits::max()) {
        return static_cast<int>(value);
    } else {
        constexpr int window = result_limits::min();
        return static_cast<int>(value + window) + window;
    }
}

ライブで見る

https://godbolt.org/z/74hY81

ここではその打ち鳴らす、GCC、およびICCがために何のコードを生成しない参照castcast_to_signed_integer_basic-O2-O3、とMSVCは、何のコードを生成しない/O2ソリューションが最適であるので、。


3

コンパイラに何をしたいかを明示的に伝えることができます。

int unsigned_to_signed(unsigned n) {
  if (n > INT_MAX) {
    if (n <= UINT_MAX + INT_MIN) {
      throw "no result";
    }
    return static_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1);
  } else {
    return static_cast<int>(n);
  }
}

gcc 4.7.2for x86_64-linuxg++ -O -S test.cpp)からコンパイル

_Z18unsigned_to_signedj:
    movl    %edi, %eax
    ret

UINT_MAXはタイプの表現でunsigned intあり、それによって全体static_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1)がそのタイプになります。しかし、それを修正することは可能であるはずであり、それでも同じようにコンパイルされることを期待しています。

2

場合は、x私たちの入力です...

場合はx > INT_MAX、我々は一定の見つけたいk、このような0< x - k*INT_MAX<をINT_MAX

これは簡単です- unsigned int k = x / INT_MAX;。次に、unsigned int x2 = x - k*INT_MAX;

私たちは今、キャストすることができますx2int、安全に。しましょうint x3 = static_cast<int>(x2);

の場合、UINT_MAX - k * INT_MAX + 1からのようなものを減算したいと思います。x3k > 0

さて、2の補数システムでは、次のようにx > INT_MAXなります。

unsigned int k = x / INT_MAX;
x -= k*INT_MAX;
int r = int(x);
r += k*INT_MAX;
r -= UINT_MAX+1;

UINT_MAX+1保証されているC ++ではゼロであり、intへの変換はnoopであり、減算したことに注意してください。k*INT_MAXしてから「同じ値」に加算し直しました。したがって、許容できるオプティマイザは、そのすべての不正行為を消去できるはずです。

それは問題を残すx > INT_MAXかどうか。さて、2つのブランチを作成します。1つはx > INT_MAXあり、もう1つはありません。ないものは、コンパイラがヌープに最適化する海峡キャストを行います。...が付いているものは、オプティマイザーが実行された後にヌープを実行します。スマートオプティマイザは、両方のブランチを同じものに認識し、ブランチを削除します。

問題:UINT_MAXがに比べて本当に大きい場合INT_MAX、上記は機能しない可能性があります。私はそれをk*INT_MAX <= UINT_MAX+1暗黙のうちに想定しています。

おそらく、次のようないくつかの列挙型でこれを攻撃することができます。

enum { divisor = UINT_MAX/INT_MAX, remainder = UINT_MAX-divisor*INT_MAX };

私が信じている2の補数システムで2と1になり(その数学が機能することが保証されていますか?それはトリッキーです...)、これらに基づいてロジックを実行し、2の補数以外のシステムで簡単に最適化します...

これにより、例外ケースも発生します。UINT_MAXが(INT_MIN-INT_MAX)よりもはるかに大きい場合にのみ可能であるため、何らかの方法で正確にその質問をするifブロックに例外コードを配置でき、従来のシステムで速度が低下することはありません。

それを正しく処理するために、これらのコンパイル時定数をどのように構築するかは正確にはわかりません。


UINT_MAXINT_MAX仕様では、すべての正の符号付き整数が符号なし整数として表現可能であることが保証されているため、に比べて小さくすることはできません。しかしUINT_MAX+1、すべてのシステムでゼロです。符号なし算術は常にモジュロUINT_MAX+1です。それでも、ここには実行可能なアプローチの核があるかもしれません...
Nemo

@Nemoちょうどこのスレッド以下、ので、私の潜在的に明白な疑問恩赦:あなたの文は"ですUINT_MAX+1?。'03 -specに設立されたすべてのsystem`上ゼロであるもしそうなら、私は下のおかげで探している必要があり、特定のサブセクションがある
WhozCraig

@WhozCraig:セクション3.9.1パラグラフ4:「符号なしと宣言された符号なし整数は、2 ^ nを法とする算術の法則に従うものとします。ここで、nは、その特定のサイズの整数の値表現のビット数です」と脚注を付けて「これは、結果の符号なし整数型で表現できない結果が、結果の符号なし整数型で表現できる最大値より1大きい数だけ減少するため、符号なし整数がオーバーフローしないことを意味します。」基本的にunsignedは、希望/期待どおりに機能するように指定されています。
ネモ

@Nemoありがとう。非常に高く評価。
whozCraig 2012年

1

std::numeric_limits<int>::is_moduloコンパイル時定数です。そのため、テンプレートの特殊化に使用できます。少なくともコンパイラがインライン化と一緒に動作する場合は、問題は解決しました。

#include <limits>
#include <stdexcept>
#include <string>

#ifdef TESTING_SF
    bool const testing_sf = true;
#else
    bool const testing_sf = false;
#endif

// C++ "extensions"
namespace cppx {
    using std::runtime_error;
    using std::string;

    inline bool hopefully( bool const c ) { return c; }
    inline bool throw_x( string const& s ) { throw runtime_error( s ); }

}  // namespace cppx

// C++ "portability perversions"
namespace cppp {
    using cppx::hopefully;
    using cppx::throw_x;
    using std::numeric_limits;

    namespace detail {
        template< bool isTwosComplement >
        int signed_from( unsigned const n )
        {
            if( n <= unsigned( numeric_limits<int>::max() ) )
            {
                return static_cast<int>( n );
            }

            unsigned const u_max = unsigned( -1 );
            unsigned const u_half = u_max/2 + 1;

            if( n == u_half )
            {
                throw_x( "signed_from: unsupported value (negative max)" );
            }

            int const i_quarter = static_cast<int>( u_half/2 );
            int const int_n1 = static_cast<int>( n - u_half );
            int const int_n2 = int_n1 - i_quarter;
            int const int_n3 = int_n2 - i_quarter;

            hopefully( n == static_cast<unsigned>( int_n3 ) )
                || throw_x( "signed_from: range error" );

            return int_n3;
        }

        template<>
        inline int signed_from<true>( unsigned const n )
        {
            return static_cast<int>( n );
        }
    }    // namespace detail

    inline int signed_from( unsigned const n )
    {
        bool const is_modulo = numeric_limits< int >::is_modulo;
        return detail::signed_from< is_modulo && !testing_sf >( n );
    }
}    // namespace cppp

#include <iostream>
using namespace std;
int main()
{
    int const x = cppp::signed_from( -42u );
    wcout << x << endl;
}


編集:非モジュラー整数マシンで発生する可能性のあるトラップを回避するようにコードを修正しました(存在することがわかっているのは1つだけ、つまり、古風に構成されたバージョンのUnisys Clearpathです)。簡単にするために、これは、そのようなマシン(つまり、Clearpath)で、値-2 n -1nint値のビット数)をサポートしないことによって行われます。実際には、この値はマシンでもサポートされません(つまり、符号と大きさまたは1の補数表現を使用)。


1

int型は少なくとも2バイトだと思うので、INT_MINとINT_MAXはプラットフォームによって異なる可能性があります。

基本的なタイプ

≤climits≥ヘッダー


デフォルトで「-mint8」で構成されている6809用のコンパイラを使用するように呪われています。intは8ビットです:-((これはVectrexの開発環境です)longは2バイト、long longは4バイト、私は短い...何であるか全くわから持っていない
グラハムTOAL

1

私のお金はmemcpyの使用にあります。まともなコンパイラは、それを最適化することを知っています。

#include <stdio.h>
#include <memory.h>
#include <limits.h>

static inline int unsigned_to_signed(unsigned n)
{
    int result;
    memcpy( &result, &n, sizeof(result));
    return result;
}

int main(int argc, const char * argv[])
{
    unsigned int x = UINT_MAX - 1;
    int xx = unsigned_to_signed(x);
    return xx;
}

私にとって(Xcode 8.3.2、Apple LLVM 8.1、-O3)、それは以下を生成します:

_main:                                  ## @main
Lfunc_begin0:
    .loc    1 21 0                  ## /Users/Someone/main.c:21:0
    .cfi_startproc
## BB#0:
    pushq    %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    ##DEBUG_VALUE: main:argc <- %EDI
    ##DEBUG_VALUE: main:argv <- %RSI
Ltmp3:
    ##DEBUG_VALUE: main:x <- 2147483646
    ##DEBUG_VALUE: main:xx <- 2147483646
    .loc    1 24 5 prologue_end     ## /Users/Someone/main.c:24:5
    movl    $-2, %eax
    popq    %rbp
    retq
Ltmp4:
Lfunc_end0:
    .cfi_endproc

1
符号なしのバイナリ表現がされ、これは、質問に答えていないではない署名した表現に一致するように標準で保証します。
TLW 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.