找出 Bash 算术可以处理的最大数字?

找出 Bash 算术可以处理的最大数字?

我怎样才能让我的脚本确定自己的最大数字?

我查看了我的环境变量,发现这两个看起来很有希望:

~# declare -p BASH_VERSINFO HOSTTYPE
declare -ar BASH_VERSINFO=([0]="5" [1]="0" [2]="11" [3]="1" [4]="release" [5]="x86_64-slackware-linux-gnu")
declare -- HOSTTYPE="x86_64"

...但我真的可以相信解析这些,以便得出 Bash 算术中最大数字是多少的结论吗?必须有更好的方法,以编程方式。有什么建议么?

答案1

Bash 算术使用有符号数。

所以快速的答案是:

((MAX=(1<<63)-1))

但既然您希望脚本不知道它所运行的系统的位数,那么让我们继续。

蛮力就是,在循环中不断加 1,直到达到溢出为负数的点。但这可能需要数年时间! :-) 一种更快、更优雅的方法是使用简单的位移位。

让我们找到符号位,即让我们找到1最高有效位中的数字,以及所有其他位中的零,无论它们有多少。一旦我们得到了这个数字,我们只需1从中减去它,我们就会得到最大的有符号数。

# MIN -- the smallest signed number 0x8000...00  (it equals MAX+1)
# MAX -- the largest signed number  0x7Fff...FF  <-- what we are looking for

MIN=1; until (( (MIN<<=1) < 0 )) ;do :;done
((MAX=MIN-1))

echo $MAX

Result:
9223372036854775807

或者,这是一个没有循环的单行。我们将数字的十六进制表示形式放入变量中,然后在将其传递给内置函数时通过变量扩展屏蔽符号位printf

printf -v MAX %x -1 && printf -v MAX %d 0x${MAX/f/7}

echo $MAX

Result:
9223372036854775807

在与我的位数不同的机器上,结果将是不同的数字。

仅出于说明目的,就我而言:

printf "MAX %X  %d\nMIN %X %d\n" $MAX $MAX $MIN $MIN
MAX 7FFFFFFFFFFFFFFF  9223372036854775807
MIN 8000000000000000 -9223372036854775808

关于 MIN 的一点旁注:您可能希望限制自己使用 MIN ((MIN=-MAX)),否则您偶尔会遇到一些算术运算的问题。

((MIN=-MAX)) ; printf "MIN %X %d\n" $MIN $MIN
MIN 8000000000000001 -9223372036854775807

答案2

您可以通过两步文本操作操作获得最大数量。

TL;博士:您需要bash做的就是:

printf -v ff %x -1
printf -v max %d "0x${ff/#?/7}"

而最小值可以通过以下附加操作获得:

# this uses the shell's own arithmetic engine
min="$((max+1))"

要回答OP的评论,请注意,它作为内置命令bash实现printf,并且默认情况下它倾向于其内置命令,因此它不会尝试调用printf通常作为独立可执行文件使用的外部命令$PATH

此外,如上所述使用的bash自己的内置函数printf不会产生任何额外的进程。相反,它在 shell 的当前执行环境中运行,其中创建变量$ff$max按照方便的-v选项指定。


与上面相同,但采用 POSIX 兼容的语法将是:

ff="$(printf %x -1)"
max="$(printf %d "0x7${ff#?}")"

# to obtain the minimum number in shells that only
# support integer numbers you can just do like
# said for `bash`
min="$((max+1))"

# else for shells defaulting to floating-point numbers
# (such as ksh93) you might instead do one text-manipulation
# operation on top of the arithmetic addition
min="$(printf -- -%u "$((max+1))")"
# of course such result would only apply to the shell's
# integer capacities, not to its floating-point capacities

请注意,使用上面的 POSIX 兼容语法可能为每个命令替换生成一个(临时)进程,这取决于特定 shell 是否优化printf类似的简单命令。然而,即使 shell 确实为每个这样的进程生成一个进程printf,它也是通过简单地分叉自身来实现的,因此它仍然依赖于自己的算术能力,而不是其他任意 shell 的算术能力。

这是假设 shell 实现printf内置,就像bash许多其他 shell 一样。未作为内置实现的 shell将使用可用的printf外部命令(如果有),就外部命令、操作系统的 C 库和 CPU 自身的能力而言,这将产生有效的结果,但可能不一定与printf$PATHprintf壳牌自己的算术能力,例如mksh即使在 64 位机器上也使用 32 位算术的情况。值得注意的是mksh,可能不会实现printf为内置命令,或者可能仍然更喜欢外部命令,并且如果尚未编译为内置命令,则可能不会实现为内置ash命令busyboxprintf

答案3

我坐在 64 位机器上,但如何让我的脚本自行确定这一点?

请注意,这是一个与 bash 算术中最大数字是什么不同的问题。你不能用它来确定你的机器的位数,并且你的机器的位数并不能决定 Bash 整数的大小。

Bash 数字始终是 64 位,即使在 32 位机器上也是如此。 (或者可能更宽,在一台比 ISO C 要求的最低 64 位更宽的奇异机器上long long。它们甚至可能是一个补码或符号/数值(在这种情况下,您将拥有
min = -max, 而不是 2 的补码)-max - 1),如果 Bash 可移植到不使用 2 的补码的 C 实现,根据评论者的说法,Bash 在内部使用long longunsigned long long,所以在设计运行时测试方法时要记住这一点。)


出于好奇,我在运行 Bash 3.2.39 的旧 32 位 Debian 系统上对此进行了测试。 LL3 的方法显示printf %x -1打印 16 fs(即 8 个字节,64 位)。

我通过将 INT64_MAX 增加到 INT64_MIN 来测试 bash 数学

$ uname -a
Linux <non-updated kernel version hidden to protect the guilty> i686 GNU/Linux

$ echo $((9223372036854775807 + 1))
-9223372036854775808

很明显,Bash 的源代码使用int64_tor long long(或者希望uint64_t在包装时避免 C 未定义的行为,除非它们构建为定义该行为),而不是像32 位机器上的 32 位那样的gcc -fwrapv类型。long

在 32 位机器上进行 64 位加法只会使编译器在有进位标志的机器上使用 2 条指令,例如add+ adc(带进位加法),或者在没有进位标志的机器上使用第 3 条指令。因此,语言提供 64 位类型是完全正常的,而对于只有一种类型的高级语言来说,使用良好的宽类型是完全正常的。

相关内容