ビット単位シフトとBashの最大整数


16

これは探索の質問です。つまり、この質問が何であるかは完全にはわかりませんが、Bashの最大の整数に関するものだと思います。とにかく、表向きに定義します。

$ echo $((1<<8))
256

ビットをシフトして整数を生成しています。どこまで行けますか?

$ echo $((1<<80000))
1

これまでのところ、どうやら。(1は予想外であり、私はそれに戻ります。)しかし、

$ echo $((1<<1022))
4611686018427387904

まだポジティブです。ただし、これではありません:

$ echo $((1<<1023))
-9223372036854775808

そしてさらに一歩先へ、

$ echo $((1<<1024))
1

なぜ1ですか?そして、なぜ次の?

$ echo $((1<<1025))
2
$ echo $((1<<1026))
4

誰かがこのシリーズを分析したいですか?

更新

私のマシン:

$ uname -a
Linux tomas-Latitude-E4200 4.4.0-47-generic #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

-9223372036854775808 = 0xF333333333333334。それは面白い格好のエッジケースです。もちろん、4611686018427387904 = 0x4000000000000000です。シフトするビット数に何らかのラップアラウンドが発生していると思われます。とにかく、なぜこれをしているのですか?
CVn

6
@MichaelKjörling娯楽のため;-p
トマス

2
@MichaelKjörlingいいえ、そうではありません。-9223372036854775808は0x8000000000000000になります。チェック時に最後の数字を省略しました:-922337203685477580は0xF333333333333334になります。
hvd

回答:


27

Bash intmax_t算術演算に変数を使用します。システムでは、これらの長さは64ビットなので、次のようになります。

$ echo $((1<<62))
4611686018427387904

これは

100000000000000000000000000000000000000000000000000000000000000

バイナリ(1の後に62 0が続く)。もう一度シフトします。

$ echo $((1<<63))
-9223372036854775808

これは

1000000000000000000000000000000000000000000000000000000000000000

バイナリ(63 0)、2の補数演算。

最大の表現可能な整数を取得するには、1を減算する必要があります。

$ echo $(((1<<63)-1))
9223372036854775807

これは

111111111111111111111111111111111111111111111111111111111111111

バイナリで。

ilkkachu答えで指摘したように、シフトは64ビットx86 CPU で64を法とするオフセット(RCLまたはを使用しているかどうか)を取りSHLます。

$ echo $((1<<64))
1

はと同等$((1<<0))です。このように$((1<<1025))ある$((1<<1))$((1<<1026))あります$((1<<2))...

型定義と最大値はにありstdint.hます。システムで:

/* Largest integral types.  */
#if __WORDSIZE == 64
typedef long int                intmax_t;
typedef unsigned long int       uintmax_t;
#else
__extension__
typedef long long int           intmax_t;
__extension__
typedef unsigned long long int  uintmax_t;
#endif

/* Minimum for largest signed integral type.  */
# define INTMAX_MIN             (-__INT64_C(9223372036854775807)-1)
/* Maximum for largest signed integral type.  */
# define INTMAX_MAX             (__INT64_C(9223372036854775807))

1
いいえ、それらは必要-です<<。バイナリの優先順位はの優先順位よりも高くなっています。
cuonglm

1
@cuonglmハァッ、zshでテストするのにぴったりです。ありがとうございます!
スティーブンキット

@cuonglmとStephen。まあ、それは良い編集です。echo $((1<<63-1))私に与えます4611686018427387904
トマス

@tomas yup、bashはC演算子の優先順位を使用し、zshにはデフォルトで$((1<<63-1))equalsがあり$(((1<<63)-1))ます。
スティーブンキット

Stephen Kittとtomasの両方に感謝します。
バレンティンB.

4

2.05b のCHANGESファイルからbash

j。シェルは、長いのではなく、マシンがサポートする最大の整数サイズ(intmax_t)で演算を実行するようになりました。

x86_64マシンでintmax_tは、符号付き64ビット整数に対応します。したがって、-2^63との間の意味のある値を取得できます2^63-1。その範囲外では、ラップアラウンドが発生します。


Nitpick:との間に-2^63あり2^63-1ます。
公称動物

4

1024でシフトすると1になります。これは、シフト量がビット数(64)を法として事実上取得されるためです1024 === 64 === 01025 === 65 === 1ます。です。

1シフト値が(少なくとも)64になる前に上位ビットがローエンドにラップアラウンドしないため、a以外の何かをシフトすると、ビットローテーションではないことが明確になります。

$ printf "%x\n" $(( 5 << 63 )) $(( 5 << 64 ))
8000000000000000
5

この動作はシステムに依存している可能性があります。bashのコードはスティーブンにリンクされている右側の値のいずれかのチェックを行わずに、単純にシフトを示しています。私の記憶が正しければ、x86プロセッサはシフト値の下位6ビット(64ビットモード)のみを使用するため、動作は直接機械語からのものである可能性があります。また、ビット幅以上のシフトはCでも明確に定義されていないと思います(gccそのことを警告しています)。


2

ビットをシフトして整数を生成します。どこまで行けますか?

整数表現がラップされるまで(ほとんどのシェルのデフォルト)。
通常、64ビット整数はで折り返し2**63 - 1ます。
こと0x7fffffffffffffff922337203685477580712月インチ

その数「+1」は負になります。

これはと同じです1<<63、したがって:

$ echo "$((1<<62)) $((1<<63)) and $((1<<64))"
4611686018427387904 -9223372036854775808 and 1

その後、プロセスが再び繰り返されます。

$((1<<80000)) $((1<<1022)) $((1<<1023)) $((1<<1024)) $((1<<1025)) $((1<<1026))

結果はmod 64、シフト値[a]に依存します。

[a] From:Intel®64 and IA-32 Architectures Software Developer's Manual:Volume 2カウントは5ビットにマスクされます(64ビットモードでREX.Wが使用されている場合は6ビット)。カウント範囲は0〜31(または64ビットモードとREX.Wが使用される場合は63)に制限されます。

また:それは覚えて$((1<<0))います1

$ for i in 80000 1022 1023 1024 1025 1026; do echo "$((i%64)) $((1<<i))"; done
 0 1
62 4611686018427387904
63 -9223372036854775808
 0 1
 1 2
 2 4

そのため、64の倍数にどれだけ近いかに依存します。

制限のテスト:

最大の正(および負)の整数である堅牢なテスト方法は、各1ビットを順番にテストすることです。とにかく、ほとんどのコンピューターで64未満のステップで、遅すぎることはありません。

バッシュ

最初に、フォームの最大の整数2^n(1ビットにゼロが続く)が必要です。次のシフトで数値が負になるまで左にシフトすることで、「ラップアラウンド」とも呼ばれます。

a=1;   while ((a>0));  do ((b=a,a<<=1))  ; done

どこ b結果はあります:ループに失敗した最後のシフトの前の値。

次に、次の記号に影響を与えるものを見つけるためにあらゆることを試みる必要がありますe

c=$b;d=$b;
while ((c>>=1)); do
      ((e=d+c))
      (( e>0 )) && ((d=e))
done;
intmax=$d

最大整数(intmax)は、の最後の値から得られますd

マイナス側(未満0)では、すべてのテストを繰り返しますが、折り返さずにビットを0にできる場合はテストします。

すべてのステップを印刷するテスト全体はこれです(bashの場合):

#!/bin/bash
sayit(){ printf '%020d 0x%016x\n' "$1"{,}; }
a=1;       while ((a>0)) ; do((b=a,a<<=1))              ; sayit "$a"; done
c=$b;d=$b; while((c>>=1)); do((e=d+c));((e>0))&&((d=e)) ; sayit "$d"; done;
intmax=$d
a=-1;      while ((a<0)) ; do((b=a,a<<=1))              ; sayit "$b"; done;
c=$b;d=$b; while ((c<-1)); do((c>>=1,e=d+c));((e<0))&&((d=e)); sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

sh

ほとんどすべてのシェルに変換:

#!/bin/sh
printing=false
sayit(){ "$printing" && printf '%020d 0x%016x\n' "$1" "$1"; }
a=1;       while [ "$a" -gt 0  ];do b=$a;a=$((a<<1)); sayit "$a"; done
c=$b;d=$b; while c=$((c>>1)); [ "$c" -gt 0 ];do e=$((d+c)); [ "$e" -gt 0 ] && d=$e ; sayit "$d"; done;
intmax=$d
a=-1;      while [ "$a" -lt 0  ];do b=$a;a=$((a<<1)); sayit "$b"; done;
c=$b;d=$b; while [ "$c" -lt -1 ];do c=$((c>>1));e=$((d+c));[ "$e" -lt 0 ] && d=$e ; sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

多くのシェルで上記を実行すると、
すべて(bash 2.04およびmkshを除く2**63 -1)がこのコンピューターで()までの値を受け入れました。

attシェルを報告するのは興味深いです:

$ attsh --version
version         sh (AT&T Research) 93u+ 2012-08-01

$((2^63))ただし、kshではなくの値にエラーを出力しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.