質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

Shift演算について

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,517

ryohasegawa

score 119

このページで説明されているシフト演算について質問なんですが、下の図の理解であってますか?

イメージ説明

*図が汚くてわかりにくくてすいません。イラストレータの勉強も必要みたいです。

追記

イメージ説明

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

+8

質問の絵に描いてあるシフトは循環シフトというものです。CおよびC++の<<>>算術シフトであり、そのような動作になりません。

算術シフトは2の補数ビット列で整数が表現されていれば、ビットをシフトすることで演算されますが、C/C++では2の補数ビット列であるという保証はありません。そのため、<<>>が本当にビット列をシフトする処理になるのかはわかりません。また、符号あり・なし、正・負、越える越えないによって、動作が異なります。

演算 aの符号 aの値 結果
a << b unsigned 非負 a*2^b
a << b signed 非負 整数の最大値を超えない場合はa*2^b、越える場合は未定義
a << b signed 未定義
a >> b unsigned 非負 a/2^b
a >> b signed 非負 a/2^b
a >> b signed 実装依存(ほとんどの実装では最上位ビットを保持したままずらす)

※ ^は冪乗を表します。
※ 未定義の動作は何が起こるかは規格上決められていないものです。エラーで強制的に終了する場合もあれば、何も起きない場合もありえます。
※ 実装依存の動作は、実装(コンパイラや環境)によって異なる動作です。コンパイラや環境によってどうなるかが定まっています。

ほとんどの実装においては、整数は2の補数ビット列で表現されるため、<<0を右から埋めていく、>>はunsignedの場合は0を左から埋めていき、signedの場合は最上位ビットの同じ値(非負なら0、負なら1)を左から埋めていくという動作をします。しかし、上の表にある未定義や実装依存の動作について、全ての環境で同じようになるとは限りません。

なお、C/C++以外の言語は上の限りではありません。Javaなど>>>という演算子を別途用意している言語もあります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+4

追記2

raccyさん他の方が丁寧に回答くださっているので蛇足的ではありますが、C/C++についての自分の回答は間違いであることを改めて強調しておきたいと思います。
特定のC/C++コンパイラーの仕様にはシフト演算子の振る舞いが「他のコンパイラーでは違うかもしれない」との警告とともに明記されていたりしますが、それに依存したコードはもちろん可搬性を損なう結果になります。
Javaなど他の言語の中には「言語仕様でシフトの振る舞いが一意に決められていて可搬性のあるプログラムでそれを前提にできる」ものもあるのですが、C/C++ではそうではないわけです。
自分が言うのも間抜けですが、「仕様書で正確な意味を把握していないといつか痛い目にあう」ことの実例として教訓にしていただければと思います。

  • 自分のプログラムにバグを混入させてしまう
  • 間違った知識を他者へ広めてしまう(そして罪なきプログラマーを迷宮へいざなう・・・)

前者もそうですが後者が特に罪深いと思います。改めてお詫びいたします。

以下元の回答

ローテートとシフトを混同されているように思えます。図にかかれた演算はシフトではなくローテートと呼ばれます。

シフトおよびローテートの詳しい説明はwikipediaのご覧のページにあるとおりなので再度注意深く確認してみてください。

右シフトは単純にいうと符号付きの数を2で整数除算するのが算術右シフトで、負号なしの数を2で整数除算するのが論理右シフトということをまず覚えておくとよいと思います。

算術右シフトについて、それが二進数でなぜそうなるのかは、2の補数表現についてよく理解した上で考えるとわかると思います。(逆に2の補数表現を把握していないとピンとこないはずです)


訂正・補足1:Wikipediaのシフトの説明として算術シフト、論理シフトと2の補数表現を結び付けて説明したのですが、raccyさんの回答をご覧いただければわかるように自分の説明は適切とは言えませんでした。失礼しました!

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/03 23:49

    ありがとうございます。
    追記した画像の解釈ですか?

    キャンセル

  • 2017/07/04 00:30

    画像は論理シフトといえると思います。

    キャンセル

checkベストアンサー

+1

どういう風に理解しているかが甚だ疑問ですが(書いた図からすると、微妙に勘違いしているようにみえる)、シフト演算はその名の通り、ビット位置をずらす演算です。
まず左シフトから説明しますか。左シフトはその名の通り、各ビットの値を上位ビット方向に移す演算です。移した結果、空いた最下位ビットには必ず「0」が入ります(質問者さんが書いた図だと、最上位ビットの値をここに移すかのように書いていますが、そんなことはしません。最下位ビットは「0」固定です)。
次に右シフト。こちらは各ビットを下位ビット方向にずらしますが、結果的に空いた最上位ビットには、最上位ビットの扱いによって埋められる値が変わってきます(まあ、早い話がsignedunsignedかで変わります)。signedつまり、符号として扱う場合、符号と同じ値が埋められます。つまり、「0」(=正数)であれば「0」が、「1」(=負数)であれば「1」が埋められます。一方、unsignedつまり、符号として扱わない場合は固定で「0」が埋められます(質問者さんが書かれた図だと最下位ビットの値を最上位に移すかのように書かれていますが、決して最下位ビットの値を最上位ビットに移すということはしません)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/04 01:02 編集

    0111 LEFT-SHIFT
    = 1110
    これは、0が切り捨てられたから、新たに0を入れたという事ですか?
    もし、
    1000 LEFT-SHIFT
    だったら、
    =0001
    という事ですか?

    キャンセル

  • 2017/07/04 01:47

    あ~、これは例が良くないような気がするなぁ(簡単に説明しようとして、単純な例を挙げた感じ)。算術シフトの説明の最初にあるように、左シフトの場合は空いた位置には「0」が埋まり、溢れたビットの値は単に切り捨てられるだけになります(と言うか、左シフトの説明をここに入れるのかとちょっと疑問)。
    「算術」という言葉の意味を考えましょう。と言うか、raccyさんがその辺りを解説してくれていますけど。
    (乱暴な言い方をするならば、中学で負数を正数で割ると負数になると教わりましたよね。それと同じことです)

    キャンセル

  • 2017/07/04 01:54

    左シフトは、1だろうが0だろうが切り捨て、新しく入る数字は絶対に0という事ですか?

    キャンセル

+1

シフトして空いたところはゼロ埋めです。
逆に溢れたところは切り捨てられます。
赤の四角は間違いです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/03 23:57

    https://ja.wikipedia.org/wiki/ビット演算
    このページのRIGHT-SHIFTの説明が間違っているように思えるんですが、どうですか?

    キャンセル

  • 2017/07/04 00:03

    上位ビットを何で埋めるかはarchiverさんの説明を読むと分かります。

    キャンセル

  • 2017/07/04 00:34

    ありがとうございます。

    キャンセル

-1

ええっとまぁ、現実的なお話をしましょう。

 C/C++ Shift演算原則1(でっち上げ)

Signedな整数型に対しShift演算原則を行ってはいけない。

理由: 未定義動作などに遭遇するから

 まとめ

C/C++においてはUnsignedな整数型に対するShift演算のみ理解すればよい。使わないものは知らなくて良い。え?それでも知りたい?理解したい?それはコードが書きたいんじゃなくて規格書を読みたいんだな。よろしい、
C++er は“合法”だとか“違法”だとか言いたくて仕方がないけれど、結局どういう意味? それより適合・適格・○○動作・○○規則・診断不要いろいろの関係が謎
を読んでくるが良い。C++ 警察との戦いの始まりだ。

 質問への回答

論理シフト は理解できました。
算術シフト の符号ビットが入るというのがまだ難しいです。
0111 LEFT-SHIFT
= 1110
これは、0が切り捨てられたから、新たに0を入れたわけではないという事ですか?
もし、
1000 LEFT-SHIFT
だったら、
=0001
という事ですか?

そもそも算術Shift、つまりはSignedな整数型に対しShift演算をしてはいけないので、知る必要がない。
質問文では4bitのSignedな整数型を仮定しているようだが(1byte = 4bitな処理系か?)、raccyさんが書いているとおりその場合未定義動作なので鼻から悪魔をだしても良い

 あらためてshift演算子の覚えるべき挙動

raccyさんが書いているとおりでいいんだけどもう一度書いておく。

 operator <<

template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr>
T lshift1(T a, T b)
{
  return a << b;
}

template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr>
T lshift2(T a, T b)
{
  for(T i = 0; i < b; ++i) a *= 2;
  return a;
}

この2つの関数lshift1/lshift2は同じ結果を示します。

 operator >>

template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr>
T rshift1(T a, T b)
{
  return a >> b;
}

template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr>
T rshift2(T a, T b)
{
  for(T i = 0; i < b; ++i) a /= 2;
  return a;
}

この2つの関数rshift1/rshift2は同じ結果を示します。

 C/C++以外の言語ではどうか

C/C++はもう何回も言ったとおり、算術シフトを使う機会はありませんがC#などでは使えます。

その場合の挙動ですが、上記のfor文で代替実装しているものと同じ挙動だと考えてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/04 16:57

    算術Shift は実際行えないという事ですか?

    キャンセル

  • 2017/07/04 17:00

    >算術Shift は実際行えない

    C/C++においてはほぼそれに近い状況ですね。

    キャンセル

  • 2017/07/04 17:02

    わかりました。
    論理シフトは理解できたので、今回の質問で収穫は合ったのでよかったです。
    ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る