web-dev-qa-db-ja.com

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

これは調査の質問です。つまり、この質問が何であるかは完全にはわかりませんが、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
17
user147505

バッシュ 演算に_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
_

バイナリで。

ilkkachanswer で指摘されているように、64ビットx86CPU(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))
_
27
Stephen Kitt

シフト量は実質的にビット数(64)を法としてモジュレートされるため、1024だけシフトすると1になります。したがって、1024 === 64 === 0および1025 === 65 === 1となります。

1以外のものをシフトすると、ビットのローテーションではないことが明確になります。これは、シフト値が(少なくとも)64になる前に上位ビットがローエンドに回り込むことがないためです。

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

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

4
ilkkachu

CHANGES 2.05bのbashファイルから:

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

X86_64マシンではintmax_tは、符号付き64ビット整数に対応します。したがって、-2^63および2^63-1。その範囲外では、ラップアラウンドが適用されます。

4
Satō Katsura

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

整数表現が循環するまで(ほとんどのシェルのデフォルト)。
通常、64ビット整数は_2**63 - 1_でラップアラウンドします。
それは、12月の_0x7fffffffffffffff_または_9223372036854775807_です。

その数「+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: インテル®64およびIA-32アーキテクチャソフトウェア開発者マニュアル:ボリューム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ステップ未満なので、遅くなりません。

bash

最初に、_2^n_(1ビットセットの後にゼロが続く)形式の最大の整数が必要です。 nextシフトによって数値が負になるまで左にシフトすることで、これを行うことができます。

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

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

2
Isaac