如何在Bash中获取mod结果的正数符号

8
当在bash中天真地使用模运算时,对于负数分子,余数得到了错误的符号(在我看来):
如果我写:
for i in {-5..5}; do echo $(( $i % 3 )) ; done

我得到了输出(作为一行)。
-2 -1 0 -2 -1 0 1 2 0 1 2

我该如何实现“正确”的行为?
1 2 0 1 2 0 1 2 0 1 2

3个回答

6
我知道这是一个老问题,但与其循环直到结果为正或启动Perl或Python,不如考虑以下方法:
使用以下命令:for i in {-5..5}; do echo $(( (($i % 3) + 3) % 3)) ; done 这将导致OP所需的输出结果。
这是因为第一个模数将结果带入-3到3的范围内,加上3,使结果在0到6的范围内,然后我们可以再次执行模数运算(加3对此没有影响)。
通常情况下: mod = ((a % b) + b) % b

3

将第一组结果加3,然后进行Mod 3操作:

$ for i in {-5..5}; do printf "%d " $(( (($i % 3) + 3) % 3 )) ; done
1 2 0 1 2 0 1 2 0 1 2

如果您知道最大范围,您可以添加一个足够大的3的倍数,使第一个模运算之前所有数字都为正数。
$ for i in {-5..5}; do printf "%d " $(( ($i + 3000000) % 3 )) ; done

然而,第一种方法更加简洁和通用。
最后,为了好玩:
positive_mod() {
  local dividend=$1
  local divisor=$2
  printf "%d" $(( (($dividend % $divisor) + $divisor) % $divisor ))
}

for i in {-5..5}; do
  printf "%d " $(positive_mod $i 3)
done

1
通常情况下,你可以将分子加3直到结果为正,然后再除以3。对于所有的k(j + k*3) % 3 == j % 3 - chepner
1
@chepner:你可以这样做,但你永远不会真正知道k需要多大。如果你在第一个模数之后完成整个操作,那么我们知道k=1 - Harvey
1
除非我弄错了,根据运算符优先级,您可以删除最里面的括号并使用$((($i%3 + 3)%3))。 - Fred
虽然我不太喜欢它,但我认为这是最好的选择。 - Mikael Fremling
@Fred: 我觉得你说的对。我当时注重的是清晰明了,而非简洁。 - Harvey

3
根据wikipedia,允许使用负号。
[当a mod n的结果不为零时仍存在符号歧义:余数有两种可能的选择,一种是负数,另一种是正数,商也有两种可能的选择。通常,在数论中,总是选择正余数,但编程语言的选择取决于a或n的符号。]
因此,这取决于编程语言的定义。由于bash显然采用了“负余数”的方式,您可以像这样转到perl:
for i in {-5..5}; do perl -le "print $i%3"; done

这样做的代价是为每个整数单独启动Perl解释器。

确实如此!由于OP似乎关心正确的数学,您可以考虑切换到类似于python的东西,在其中进行循环和所有操作。


1
这样做需要为每个整数单独启动Perl解释器,虽然它可以工作,但我认为稍微长一点的不使用Perl的解决方案对于性能和避免外部依赖是值得的。 - Fred
当我说错的时候,我是从数论的角度来看的。 - Mikael Fremling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接