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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

790閲覧

Shift演算について

ryohasegawa

総合スコア437

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

0クリップ

投稿2017/07/03 14:18

編集2017/07/03 14:48

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

イメージ説明

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

###追記
イメージ説明

yumetodo👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

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

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

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

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

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

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

投稿2017/07/03 15:14

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

###追記2

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

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

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

以下元の回答

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

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

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

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


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

投稿2017/07/03 14:36

編集2017/07/03 23:16
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryohasegawa

2017/07/03 14:49

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

2017/07/03 15:30

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

0

ベストアンサー

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

投稿2017/07/03 14:38

archiver

総合スコア1557

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryohasegawa

2017/07/03 14:49 編集

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

2017/07/03 14:55 編集

いえ追記前です。「ずらす」ということは分かっているようですが、最終的に空く最上位ビット、最下位ビットの埋め方の解釈が間違っているように見えます。 KSwordOfHasteさんが指摘しているように、ローテート(=循環)と混同しているように見えます。
ryohasegawa

2017/07/03 14:55

追記の図の解釈も間違ってますか?
archiver

2017/07/03 14:57

前後しちゃいましたね。追記前の解釈の方が怪しいです。 追記後もおおむね合っていますが、きちんとした解釈にはあと少しです。
ryohasegawa

2017/07/03 15:02

追記の解釈で合ってますか。 wikiの説明が間違っているようで、自分で変な解釈をしていたみたいです。
archiver

2017/07/03 15:04

wikiの解説をよく見ましょう。「算術シフト」と「論理シフト」に分かれています。それぞれのシフトの内容を見比べてください。答えが見えてくるはずです。 (というか、KSwordOfHasteさんと私が答えを書いていますけど)
ryohasegawa

2017/07/03 15:09

今回は算術シフトですよね?
archiver

2017/07/03 15:18

>今回は算術シフトですよね? それは、この場で質問者さんが意図しているシフト演算のことですか?はっきり言って、これだけではわかりません。それは、算術シフトと論理シフトは、右シフト演算において、用途をはっきりさせた上で使うものだからです。 こういう切り返しをするということは、算術シフトと論理シフトがどういったものかというのが理解できていないものだと考えられます。wikiにも書かれていますが、それでもわかりにくいのであれば、C言語の書籍を見るか、初心者向けのC言語解説サイトを見たほうが良いです。
ryohasegawa

2017/07/03 15:22

wikiには 0111 LEFT-SHIFT = 1110 1011 RIGHT-SHIFT = 1101 と記述されていると思うのですが、 _Victorique__ さんの回答は「1011は0101になります。」と頂いているので、答えが違うんですね・・・
archiver

2017/07/03 15:36

wikiで図解入りで説明されているのが「算術シフト」、_Victorique__さんがコメントの中で答えてくださっているのが「論理シフト」になります(wikiにも略式ながら、論理シフトの説明がありますよ)。 繰り返しますが、右シフトにおいては、(符号ビット=最上位ビットを)どう扱うかで結果が変わってきます。画像データ周りを勉強しているようですが、ビット操作は頻繁に使うことになると思います。なので、この辺りの話(シフトに限らず)はキチンと押さえておいた方が良いです。 (基本情報処理技術者試験の解説書を見るのも一つの手ですね。ビットの扱いについては情報処理の基礎にもなっているので、そっちを見たほうがいいかもしれません)
ryohasegawa

2017/07/03 15:52 編集

算術シフト は溢れて、切り捨てられた数字が符号ビットがが左端にコピーされるのですか? 0が切り捨てられたら0が、1が切り捨てられたら1が新しく挿入されるって事ですか?
ryohasegawa

2017/07/03 15:52

論理シフト は空いた所には、1が捨てられも0が捨てられても、新しく挿入される値は0という事ですか?
archiver

2017/07/03 15:57

算術シフトにおいて、切り捨てられた数字は関係ありません。右シフトして空いたところには符号ビットと同じ値が入ります。 論理シフトはその理解で良いです。
ryohasegawa

2017/07/03 16:01

論理シフト は理解できました。 算術シフト の符号ビットが入るというのがまだ難しいです。
ryohasegawa

2017/07/03 16:47 編集

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

2017/07/03 16:47

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

2017/07/03 16:54

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

0

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

投稿2017/07/03 14:25

_Victorique__

総合スコア1392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryohasegawa

2017/07/03 14:27

LEFTの方は理解できました。RIGHTの方が理解できません
_Victorique__

2017/07/03 14:31

右シフトの場合、右端のビットが溢れるので切り捨てられます。なので1011は0101になります。
ryohasegawa

2017/07/03 14:39

1011は0101ですか? wikiとは数字が異なるんですが・・・
_Victorique__

2017/07/03 14:47

ただビット全部を右にずらすだけですよ。 1011 >> 1 = 0101 1←溢れた右端のビットは切り捨て
ryohasegawa

2017/07/03 14:48

wikiが間違ってますか?
ryohasegawa

2017/07/03 14:49

また、追記した画像の解釈ですかね?
_Victorique__

2017/07/03 15:03

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

2017/07/03 15:34

ありがとうございます。
guest

0

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

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 <<

cpp

1template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr> 2T lshift1(T a, T b) 3{ 4 return a << b; 5} 6 7template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr> 8T lshift2(T a, T b) 9{ 10 for(T i = 0; i < b; ++i) a *= 2; 11 return a; 12}

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

operator >>

cpp

1template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr> 2T rshift1(T a, T b) 3{ 4 return a >> b; 5} 6 7template<typename T, std::enable_if_t<std::is_unsigned<T>::value, std::nullptr_t> = nullptr> 8T rshift2(T a, T b) 9{ 10 for(T i = 0; i < b; ++i) a /= 2; 11 return a; 12}

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

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

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

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

投稿2017/07/03 16:54

編集2017/07/03 16:55
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryohasegawa

2017/07/04 07:57

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

2017/07/04 08:00

>算術Shift は実際行えない C/C++においてはほぼそれに近い状況ですね。
ryohasegawa

2017/07/04 08:02

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問