bash
Shell 的算术评估功能设置了限制。该手册简要介绍了Shell算术的这一方面,但指出:
评估以固定宽度的整数完成,不检查溢出,尽管陷阱会被除以0并标记为错误。运算符及其优先级,关联性和值与C语言相同。
它所指的是哪个固定宽度整数,实际上是关于所使用的数据类型(以及为何超出此范围的详细信息),但是极限值是以/usr/include/limits.h
这种方式表示的:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
一旦知道了,就可以像下面这样确认事实:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
这是一个64位整数,在算术评估的上下文中直接在shell中转换:
# echo $(((2**63)-1)); echo $((2**63)); echo $(((2**63)+1)); echo $((2**64))
9223372036854775807 //the practical usable limit for your everyday use
-9223372036854775808 //you're that much "away" from 2^64
-9223372036854775807
0
# echo $((9223372036854775808+9223372036854775807))
-1
因此,在2 63和2 64 -1之间,您将得到负整数,显示您与ULONG_MAX的距离为1。当评估达到该限制并溢出时,无论按什么顺序,您都不会收到警告,并且该评估的一部分会重置为0,这可能会产生一些异常行为,例如,右联想幂运算:
echo $((6**6**6)) 0 // 6^46656 overflows to 0
echo $((6**6**6**6)) 1 // 6^(6^46656) = 6^0 = 1
echo $((6**6**6**6**6)) 6 // 6^(6(6^46656)) = 6^(6^0) = 6^1
echo $((6**6**6**6**6**6)) 46656 // 6^(6^(6^(6^46656))) = 6^6
echo $((6**6**6**6**6**6**6)) 0 // = 6^6^6^1 = 0
...
使用sh -c 'command'
不会改变任何东西,因此我必须假设这是正常且合规的输出。现在,我认为我对算术范围和极限以及它在表达式评估外壳中的含义有了基本但具体的理解,我认为我可以快速了解Linux中其他软件使用的数据类型。我使用了一些bash
必须补充此命令输入的资源:
{ shopt -s globstar; for i in /path/to/source_bash-4.2/include/**/*.h /usr/include/**/*.h; do grep -HE '\b(([UL])|(UL)|())LONG|\bFLOAT|\bDOUBLE|\bINT' $i; done; } | grep -iE 'bash.*max'
bash-4.2/include/typemax.h:# define LLONG_MAX TYPE_MAXIMUM(long long int)
bash-4.2/include/typemax.h:# define ULLONG_MAX TYPE_MAXIMUM(unsigned long long int)
bash-4.2/include/typemax.h:# define INT_MAX TYPE_MAXIMUM(int)
有与多个输出if
语句,我可以像搜索命令awk
太等我通知我用正则表达式没有关于任意精度的工具,我有诸如捕捉到任何bc
和dc
。
问题
awk
当算术评估溢出时,不警告您(例如评估2 ^ 1024时)的原因是什么?为什么最终用户评估某些东西时2 63和2 64 -1 之间的负整数会暴露给最终用户?- 我读过某个地方的某些UNIX风格可以交互更改ULONG_MAX?有谁听说过吗?
- 如果有人随意更改in中无符号整数最大值的值
limits.h
,然后重新编译bash
,我们会发生什么?
注意
我想更清楚地说明我所看到的,因为这是非常简单的经验性资料。我注意到的是:
- (a)任何给出<2 ^ 63-1的评估都是正确的
- (b)任何给定=> 2 ^ 63至2 ^ 64的求值都将给出负整数:
- 该整数的范围是x到y。x = -9223372036854775808和y = 0。
考虑到这一点,类似于(b)的评估可以表示为2 ^ 63-1加上x..y内的值。例如,如果我们确实被要求评估(2 ^ 63-1)+100 002(但可以是小于(a)中的任何数字),则得到-9223372036854675807。我只是在说显而易见的话,但这也意味着以下两个表达式:
- (2 ^ 63-1)+ 100002 AND;
- (2 ^ 63-1)+(LLONG_MAX-{外壳为((2 ^ 63-1)+ 100002)提供了什么,即-9223372036854675807}),请使用正值;
- (2 ^ 63-1)+(9223372036854775807-9223372036854675807 = 100 000)
- = 9223372036854775807 + 100000
确实非常接近。第二个表达式是(2 ^ 63-1)+ 100 002之外的“ 2”,即我们要评估的内容。这就是我得到的负整数,表示您与2 ^ 64的距离有多远。我的意思是说,有了这些负整数并知道极限,那么您无法在bash shell的x..y范围内完成评估,但是您可以在其他地方进行评估-在这种情况下,数据最多可以使用2 ^ 64(我可以添加将其放在纸上或在bc中使用)。除此之外,当达到极限时,行为类似于6 ^ 6 ^ 6的行为,如下文Q中所述。
bc
,例如:$num=$(echo 6^6^6 | bc)
。不幸的是,bc
插入了换行符,因此您必须num=$(echo $num | sed 's/\\\s//g')
事后才这样做。如果在管道中执行此操作,则可以使用实际的换行符,尽管使用sed还是很尴尬的num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
。无论哪种情况,您现在都可以使用一个整数,例如num2=$(echo "$num * 2" | bc)
。