Go中负整数的模运算

24
我正在学习Go语言,我的编程背景是Python。
最近,我发现了一个与Python不同的%(取模)运算符的行为。与模运算和余数的定义相反,负整数对正整数取模的结果是负数。
例如:

Python

a, b, n = -5, 5, 3
for i in range(a, b):
    print(i%n)    

输出:

1
2
0
1
2
0
1
2
0
1

Go

a, b, n := -5, 5, 3
for i:=a; i<b; i++ {
    fmt.Println(i%n)
}

输出:

-2
-1
0
-2
-1
0
1
2
0
1

阅读了关于取模运算符和一些类似的问题,我理解这些差异是由相关语言的设计目标造成的。
在Go中是否有内置功能来复制Python的模数操作?
替代方案:是否有计算“模数”而不是“余数”的内部方法?
5个回答

19

请查看这位语言设计师发表的评论

目前定义存在以下几个原因:

  • % 的当前语义直接是 x86 架构的结果
  • 如果改变基本操作符 % 的含义而不更改其名称,将会让人感到困惑
  • 很容易从 % 结果计算出另一个模数

请注意,% 计算的是“余数”,而不是“模数”。

标准库中没有一个操作符或函数可以复制 Python 的模数运算。

可以编写一个函数来复制 Python 的模数运算:

func modLikePython(d, m int) int {
   var res int = d % m
   if ((res < 0 && m > 0) || (res > 0 && m < 0)) {
      return res + m
   }
   return res
}

请注意,在Python中,5 % -3的结果是-1,而这段代码也复制了这种行为。如果你不想要这种行为,请在if语句中删除||后面的第二部分。


谢谢您提供的参考。由于这个问题已经关闭,我忽略了它。我会更新我的问题以反映这一点。 - Kshitij Saraogi
4
你可以将Go语言中的模数代码简化为(i % n) + n) % n。在此处验证:https://play.golang.org/p/QT5PA9fltU - Liam Kelly

15
是否有一种内部方法可以计算“模数”而不是“余数”?
请注意,%计算的是“余数”,而不是“模数”。
这些引语有点误导。
查阅任何“模运算”的定义,大体上都会说它是除法后的余数。问题在于当我们说“余数”时,它意味着只有一个。当涉及负数时,可能会有多个不同的余数。在维基百科关于Remainder的页面上,它区分了最小正余数最小绝对余数。你还可以添加一个最小负余数(最小负数意味着负数,但最接近0)。
通常情况下,对于模运算符,如果它返回一个正值,那么它就是最小正余数,如果它返回一个负值,那么它就是最小负余数。返回值的符号可以通过多种方式确定。例如,给定c = a mod b,您可以定义c的符号为
- a的符号(Go中的%) - b的符号(Python中的%) - 始终为非负数
这是一个编程语言及其模运算实现的列表,按照此方式定义 https://en.wikipedia.org/wiki/Modulo_operation#In_programming_languages 以下是使用Go函数复制Python的%运算符的无分支方法。
func mod(a, b int) int {
    return (a % b + b) % b
}

再次强调,这遵循以下规则:

给定 c = a mod bc 的符号将与 b 的符号相同。 换句话说,模运算的结果与除数的符号相同。


0

在Q2中,您可以使用:

func modNeg(v, m int) int {
    return (v%m + m) % m
}

将输出:

modNeg(-1, 5) => 4
modNeg(-2, 3) => 0

0
在大多数情况下,只需将第二个数字添加到结果即可:
Python:
-8%6 => 4

Golang:

-8%6 + 6 => 4

所以函数将会是这样的:

func PyMod(d int, m int) int {
  d %= m
  if d < 0 { 
    d += m
  }
  return d
}

它适用于其他一些情况,例如a%-b,除了-a%b

但是,如果您希望它甚至适用于-a%-b,请按照以下方式操作:

func PyMod(d int, m int) int {
  // Add this condition at the top
  if d < 0 && m < 0 {
    return d % m
  } 
  d %= m
  if d < 0 { 
    d += m
  }
  return d
}

0

math/big 实现了欧几里得模运算:

package main
import "math/big"

func mod(x, y int64) int64 {
   bx, by := big.NewInt(x), big.NewInt(y)
   return new(big.Int).Mod(bx, by).Int64()
}

func main() {
   z := mod(-5, 3)
   println(z == 1)
}

https://golang.org/pkg/math/big#Int.Mod


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