按位移位和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。我怀疑您正在对要转移的位数进行某种折回。你为什么要这么做?
CVn16年

6
@MichaelKjörling娱乐

2
@MichaelKjörling不,不是。-9223372036854775808将为0x8000000000000000。检查时,您省略了最后一位数字:-922337203685477580为0xF333333333333334。
播放

Answers:


27

Bash 使用intmax_t变量进行算术运算。在您的系统上,这些长度为64位,因此:

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

这是

100000000000000000000000000000000000000000000000000000000000000

二进制(1后跟62 0)。再次移动:

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

这是

1000000000000000000000000000000000000000000000000000000000000000

以二进制(63 0s),以二进制补码算术运算。

要获得最大的可表示整数,您需要减去1:

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

这是

111111111111111111111111111111111111111111111111111111111111111

以二进制形式。

正如ilkkachu答案所指出的那样,在64位x86 CPU(无论使用RCL还是SHL)上,移位以64为模进行偏移,这解释了您所看到的行为:

$ 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
不,您需要它们,Binary的-优先级高于<<
cuonglm '16

1
@cuonglm呵呵,适合我在zsh上进行测试...再次感谢!
斯蒂芬·基特

@cuonglm和斯蒂芬。好吧,这是一个很好的编辑。echo $((1<<63-1))给我4611686018427387904

@tomas yup,bash使用C运算符优先级,zsh默认情况下具有其自己的$((1<<63-1))equals $(((1<<63)-1))
史蒂芬·基特

非常感谢您,Stephen Kitt和tomas,这是一个很好的知识,一个很好的问题和一个非常棘手的答案。
Valentin B.

4

从2.05b 的CHANGES文件中bash

j。现在,shell将使用机器支持的最大整数大小(intmax_t)而不是long来执行算术运算。

在x86_64机器上,intmax_t对应于有符号的64位整数。因此您在-2^63和之间获得了有意义的值2^63-1。超出该范围,您将获得环绕效果。


Nitpick:介于-2^63和之间2^63-1,包括。
名义动物

4

由1024移位给出了一个,因为偏移量被有效地取模位(64)的数目,所以1024 === 64 === 0,和1025 === 65 === 1

移位除a以外的其他内容1可以很明显地看出这不是位旋转,因为在移位值为(至少)为64之前,高位不会绕到低端:

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

此行为可能取决于系统。Stephen链接到bash代码仅显示了平移,而没有检查右手的值。如果我没有记错的话,x86处理器仅使用移位值的后六位(在64位模式下),因此其行为可能直接来自机器语言。另外,我认为在C中也没有明确定义移位超过位宽的情况(gcc对此发出警告)。


2

通过移动一位产生一个整数。我可以走多远?

直到整数表示形式回绕为止(大多数shell中是默认值)。
64位整数通常在处环绕2**63 - 1
0x7fffffffffffffff9223372036854775807在十二月。

该数字“ +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]来自:英特尔®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的倍数的接近程度。

测试极限:

测试最大正(和负)整数的有效方法是依次测试每一位。无论如何,对于大多数计算机而言,它的步数不到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

翻译成几乎所有的shell:

#!/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"

对许多shell运行以上命令,
所有(bash 2.04和mksh除外2**63 -1)在此计算机中接受的值最多为()。

报告att外壳很有趣:

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

$((2^63)),而不是ksh的值上显示错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.