• ベストアンサー

C言語で確実に論理右シフトをする方法。

算術右シフトと論理右シフトをC言語にて実装しようとしています。 『ハッカーのたのしみ』にある論理右シフトを用いて算術右シフトを作る方法を使おうと思っているのですが、C言語で確実に論理右シフトするにはどうすればよいのでしょうか。 int x; (unsigned int) x >>= n; とするとgccではゼロ拡張してくれるようなのですが、 use of cast expressions as lvalues is deprecated というWarningが出ます。 この方法が確実とは思えませんし、 unsigned int xx; xx = x; も処理系依存と聞きます。 出来ればWarningが出ない形で解決したいのですが、どうすればよいのでしょうか。 教えてください。

noname#245945
noname#245945

質問者が選んだベストアンサー

  • ベストアンサー
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

ああ, よく考えれば「最初から unsigned で全部処理をする」というのが最も正しい解のような気がします. それはさておき, 右シフト演算子の左オペランドが signed で負数の場合, 結果は「実装定義」です. 未定義ではありません. ついでにいうと, signed → unsigned のキャストは 6.3.1.3節のパラグラフ2 にあるように変換されます. 本当は JIS を参照するのが正しいんだけど ISO しか持ってないのでこっちを参照すると If the new type is unisgned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type. となっていて, signed int → unsigned int の場合には実質的に「もともとが正ならそのまま, 負なら UINT_MAX+1 を加えた値」となります. ということで 2の補数を使っていればたいていはビットパターンが変化しません. 逆に unsigned → signed の場合は「signed で表現できればそのまま, 表現できなければ実装定義」となります. ちなみに C++ だと signed → unsigned の変換は「2^n を法として最小の整数」で, 2の補数を使ってる場合には同じことですね. ん~, 「unsigned int xx; xx = x; も処理系依存」というのも, 本当にここまで考えて書いているのかなぁ....

noname#245945
質問者

お礼

しつこく付き合わせてしまい申し訳ありません。 未定義ではなく実装定義ですね。 日本語が理解できていなかったようです orz。 unsigned int xx; xx = x; はxとxxのビットパターンが同じになるかどうか不安という意味で書きました。 今回のことで、一度仕様書を通読する機会を持つ必要を感じました。 土方志望では無いのですが、せめて人並みには使えるように頑張ろうと思います。

その他の回答 (5)

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.6

案外、共用体を使って、 typedef union {  int s;  unsigned u; } xx; xx.s = x; xx.u >>= n; x = xx.s とするのが一番楽な気がします。 もちろん、もともと全部unsignedで扱えるのであれば、その方が簡単ですが。

noname#245945
質問者

お礼

ありがとうございます。 なるほど、それならば安心して書けます。 目から鱗でした。

noname#25383
noname#25383
回答No.4

Warningが出るのは、左辺値をcastしているからだと思います。 質問の実装をするのであれば、 int x; unsigned int x_tmp; x_tmp = (unsigned int)x; x_tmp >>= n; としてみては如何でしょうか。 ただ、m_11さんがおっしゃるとおり、処理系に依存するので、必ずしも成功するとは限りません。

noname#245945
質問者

お礼

左辺値をキャストすれば怒られるのも当然でした。 ご指摘頂きありがとうございました。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.3

あれ? 真意が伝わっていない気が.... ISO C の規格によれば, (右オペランドが非負かつ左オペランドのビット幅未満の値を持っているという前提のもとで) 左オペランドが unsigned なら全ての処理系において予測可能な結果となりますが, signed の場合には (右シフトかつ負のとき) 実装定義の結果となったり (左シフトでその結果が表現できる値を越える場合には) 未定義動作となります. ということで, 左オペランドを signed のまま扱うのは危険ですからなんとかして unsigned にする必要があります. これは, 普通の環境では「単に unsigned にキャストするだけ」で実現できます. 正確には, 「signed の表現が全ビット幅を用いた 2の補数表現」かつ「可能な全ビットパターンに対して unsigned で値が有効」であれば実現可能です. ということで, 普通の環境であれば x = (int)((unsigned)x >> n); で x の値を右に nビット論理シフトすることができます. 普通じゃない環境, 例えば負数を表現するのに「1の補数」を使ってみたり「符号+絶対値」であったりするような環境ではうまくいかないので, そういう環境 (現存するかどうかは知りません) も考慮するなら「努力と根性」が待っています. もちろんこんな環境を考慮するくらいなら x >> 1 すら危険ですが.

noname#245945
質問者

お礼

signedで負数を右シフトしたときの結果は"未定義"だったのですね。 算術シフトか、論理シフトのどちらかになるものと勘違いしていました。 さすがに、負数表現に2の補数を用いない機械を相手にする気はありませんので、 x = (int)((unsigned int) x >> n); で良いと聞いて安心しました。 かなり広範囲の環境で、unsignedへのキャストはキャスト前と同じビットパターンになることが確実との情報も有益でした。 ありがとうございました。

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.2

1ビット右シフトした結果に対して、0x7fffffffでANDをとられてはいかがでしょうか。

noname#245945
質問者

お礼

なるほど、 上記の操作をした後、残り分をシフトすれば確実に論理右シフトしたことになりますね。 目から鱗でした。 ありがとうございます。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

う~ん, ひたすら頑張るんだったら x が正か負かで場合分けせざるをえないんじゃないかなぁ. 普通の環境なら x = (int)((unsigned)x >> n); で十分だろうけど.

noname#245945
質問者

お礼

ありがとうございます。 やはり、1本ずつビットを下ろして行くしかないのでしょうか。

関連するQ&A

  • 算術シフトと論理シフトの違いは?

    「コンピュータはなぜ動くのか 知っておきたいハードウエア&ソフトウエアの基礎知識」書籍内のP61のところにある「表3.1 Z80 CPUの主な命令」の表の「演算命令」の欄に 「SLA:算術左シフトをする」 「SRA:算術右シフトをする」 「SRL:論理右シフトをする」 というのがあるのですが、この「算術シフト」と「論理シフト」はどう違うのでしょうか? 「シフト」と言うと 「データを右にずらす。」 「データを左にずらす。」 というイメージがあるのですが、これと似たようなものなんでしょうか? わかりやすく教えてください。できればわかりやすい「例え」で教えてくれたほうが幸いです。 よろしくお願いいたします。

  • C言語のビットシフトの質問

    C言語の右ビットシフトで、以下のプログラムの結果について、納得できずにいます。 dを右にシフトすると、上位ビットには0が入るのではないでしょうか? よろしくお願いします。 プログラム unsigned char d='0'; printf("%c\n", d); printf("%c\n", d>>5); 結果 0 『

  • C90とC99の計算結果の違い?

    C言語の質問です。 gcc version 4.3.2 (Ubuntu 4.3.2-1ubuntu12) 以下のプログラムをgccでコンパイル・実行すると(1)に入り,"a"が出力されます。unsigned intの計算なのでラップアラウンドが生じtest=4294716272となるのは私の期待どおりです。 ただ、gcc -std=c99でコンパイル・実行すると(2)に入り,"b"が出力されました。c99でコンパイル・実行すると計算結果がなぜ異なっているのかが分かりません。 long test = 0; unsigned int x = 184; unsigned int y = 251208; test = (x-y); if(test == 4294716272){ printf("a");// (1) }else if(test == -251024){ printf("b"); // (2) }

  • シフト演算の結果の型

    シフト演算の結果の型は、左オペランドの型ということを聞きました。 この左オペランドの型というのは算術型変換をしてからのか、する前の型なのかどちらなのでしょうか? 例えば int a = -2; unsigned int b = 1; a >> b このときの結果の型は何になるのでしょうか? 算術型変換が起きる前のintになるのか? それとも算術型変換が起きるた後のunsigned intになるのか? 例文としてはおかしい点もあるかもしれませんが よろしくお願いします。

  • C言語とC++の相違点?

    printf("sizeof(int)= %u\n", (unsigned)sizeof(int)); printf("sizeof(unsigned)= %u\n", (unsigned)sizeof(unsigned)); の結果がVisual C++では共に4なのですが、C言語の扱っている本では 共に2になっています。 これが単なるミスプリなのか、C++とC言語の違いなのか教えていただけますか? 回答よろしくお願いします。

  • 算術シフトについて

    10010010(2)を 左に二桁の算術シフト演算について いくらやってもテキストの解答と合いません(10100100) どうやったらでるのでしょうか? また左右の論理シフト演算、右算術シフト演算 の結果はそれぞれどうなるのでしょうか? また

  • C言語のコードについて

    C言語の問題なのですか、作成したのですが内容がわからないです。 コードをわかりやすく解説していただけると嬉しいです。 #include <stdio.h> void printBinary(unsigned char num) { int i ; /*①上位ビットから順に表示する*/ for(i = 7 ; i >= 0; i--) { /*②シフトとマスクを使用しています。*/ printf("%d", (num>>i) &0x01 ); } printf("\n"); } int main(void) { unsigned char num1 = 0xD2;/*11010010*/ unsigned char num2 = 0x5E;/*01011110*/ printf("0xD2 : "); printBinary(num1); printf("0x5E : "); printBinary(num2); return 0; }

  • R8Cマイコンの乗算

    R8C34Mを使っています。 例えば下記のコードを実行したら  unsigned long kai;  unsigned int xx;  unsigned int yy; xx = 0x07d0;  //10進で2000 yy = 0x0064;  //10進で100 kai = xx * yy; kaiは 0x30d40 となるはずが、0x0d40 でした。 kai = (unsigned long)xx * yy; と書き直したら、うまくいきました。 答えが2バイトを超えるのが分かっていたのでkaiはlong長にしました。 どうして右辺もキャストでlong長にしないといけないのでしょうか? また、下記のようにkaiをint長、xx,yyをchar長にした場合は、 kaiは意図する値の0x4e20 になりました。  unsigned int kai;  unsigned char xx;  unsigned char yy; xx = 0x00c8;  //10進で200 yy = 0x0064;  //10進で100 kai = xx * yy; どうして後者の場合はうまくできて、前者の場合はキャストしないといけないのでしょうか? この辺ご存知の方ご教授願います。

  • 算術型変換について

    c言語で算術型変換がどのように行われているかの質問です。 例えばintが16bitでlongが32bitの環境であるとします。 そして以下のようなプログラムがあるとします。 unsigned long x = 100; signed int y = 1; unsigned int z = 5; long test = x + (y - z); 1、 このときまずy-zについて算術型変換が起きてyとzがunsigned intとなり、 y-z=65532となる。 そしてx/(y-z)について算術型変換が起き、xと(y-z)がunsigned longとり、 x+(y-z)=65632となるのでしょうか? 2、 もしくはx+(y-z)のすべてに算術型変換を起こしてから計算を行うのでしょうか? y-zの結果の型をunsigned longとして計算し、y-z=4294967292になるのでしょうか? 文章がうまくまとめられていないのですが、どちらになるのでしょうか?

  • シフト演算で質問です

    1バイトの 10010111を右に1シフトすると2^-1になるはずですが 151から75となり 2^-1になっていません。 なぜそうなるのでしょうか? これでは正確な演算を行うことができません。。。 よろしくお願いします! あと、論理シフトと算術シフトの使い分けもできれば教えていただきたいです!