37941


C言語で、ビット演算を使い絶対値を返す関数について調べていた所ネットで以下の様に作成できることがわかりました。

[code]

    int myabs(int b) {
        return (b ^ (b >> 31)) - (b >> 31);
    }
    
[/code]

確かに実際に↑の関数を書いたコードで試してみると絶対値が求められるのですが仕組みがどうも分かりません。

例えば `b`が `0x00000001` の場合,

`(b >> 31)` = `0x00000000`,  
`(b ^ (b >> 31))` = `0xFFFFFFFE`,  
`(b ^ (b >> 31)) - (b >> 31)` = `0xFFFFFFFE` となりませんか？


CC BY-SA 3.0
2017-09-14T12:54:11.847
37939
2017-09-17T10:05:48.650
25350
question
c
C言語でビット演算を利用して絶対値を返す関数の仕組みが理解できない


b が 0x00000001 の場合、

[code]

    (b ^ (b >> 31)) = 0x00000001
    
[/code]

ですね。

* * *

以下、`myabs()` の説明です。

絶対値を求めるなら、

  * 元の数値が 0以上ならそのまま
  * 元の数値が負数なら符号を反転

とすれば良いわけです。ここに、2の補数の

  * 全てのビットを反転して +1 すると値の符号が反転する。
  * 最上位ビットは符号である。

という特徴を活かします。

`b >> 31` は、最上位ビットで他の31ビットを塗りつぶします。  
つまり、

  * `b` が 0 以上なら 0
  * `b` が負数なら -1 (全てのビットが 1)

となります。

従って `(b ^ (b >> 31))` は

  * `b` が 0 以上なら何も起きない (`b >> 31` が 0 だから)
  * `b` が負数なら `b` のビットが全て反転する (`b >> 31` は全てのビットが 1 だから)

ということになります。  
また `- (b >> 31)` の部分ですが、これは

  * `b` が 0 以上なら 0 (`b >> 31` が 0 だから)
  * `b` が負数なら +1 (`b >> 31` が -1 だから)

となります。

以上の処理により、

  * `b` が 0 以上なら何もせず `b` のまま
  * `b` が負数ならビットを反転して +1 する (つまり符号を反転する)

という処理が実現できていることになり、絶対値が求まります。


CC BY-SA 3.0
2017-09-14T13:25:04.333
37941
2017-09-14T13:32:09.337
2017-09-14T13:32:09.337
5288
5288
37939
answer


本題に関しては解決済みなので、言語規格書 JIS X 3010:2003 を引き引き、重箱の隅をつついてみます。

符号付き整数を２の補数以外の方法で表記してかまいません (6.2.6.2) 。

`int` は符号付き（なので絶対値を取ることに意味がある）ですが、符号付き負数の右シフトの挙動は処理系定義です (6.5.7)

`int` は 16bit 以上が必要とされています (5.2.4.2.1)
。一方でシフト演算子の右オペランドが整数型の幅以上の場合は未定義動作とされています (6.5.7) 。実際に `int` が 16bit
である処理系は存在します。

`int` に詰め物ビットがあっても良いことになっています。詰め物ビットの値は未規定つまり何が入っているかは不明です (6.2.6.2)

なので言語規格書的には `-1 >> 31`
の結果が「全てのビットが１の数」であるとは限りません。その意味で提示コードは規格厳密合致とは呼べないものとなっています。

規格書にはそのように書かれていますが、現実問題としてオイラが過去に使ったことのある全ての CPU 全てのコンパイラで  
\- ２の補数を採用していました。  
-「負数の右シフト」の挙動は 1/2 (符号ビットを右にコピーし、符号ビット自体はそのまま残す) でした。  
\- `int` が 16bit な処理系は今でも存在します (組み込み系など)  
\- `int` に詰め物ビットがあるものはありませんでした。

ということで、提示コードは「一般的な処理系では問題ないが、特殊な処理系では実用する前に一応動作確認しておくべし」
(処理系のマニュアルを読んで問題ないことを確認してから使うべし) ということになりそうです。

* * *

追記

２の補数系、詰め物なしならば `int` が何ビットでも良いように直すとしたら

[code]

    #include <limits.h>
    #define INT_BIT ((sizeof(int)/sizeof(char))*CHAR_BIT)
    return b ^ (b>>(INT_BIT-1)) - (b>>(INT_BIT-1));
    
[/code]

ってとこですかね？ `sizeof(char)` は `1` と決まっているので

[code]

    #define INT_BIT (sizeof(int)*CHAR_BIT)
    
[/code]

でも良いわけですが。


CC BY-SA 3.0
2017-09-15T00:11:54.987
37947
2017-09-15T04:30:27.373
2017-09-15T04:30:27.373
8589
8589
37939
answer


題意からは外れるようですが、こんな方法もあるということでd^^

[code]

    #include <stdio.h>
    
    int abs1(int n)
    {
        return n > 0 ? n:(n ^ -1) + 1;
    }
    
    int main()
    {
        for( int i= -10; i <= 10; i++ ){
            printf("%d\n",abs1(i));
        }
        return 0;
    }
    
[/code]

usr~/test >./a.out  
10  
9  
8  
7  
6  
5  
4  
3  
2  
1  
0  
1  
2  
3  
4  
5  
6  
7  
8  
9  
10


CC BY-SA 3.0
2017-09-17T08:01:24.187
38003
2017-09-17T10:05:48.650
2017-09-17T10:05:48.650
25438
25438
37939
answer