如何编写一个通用函数,可以接受任何数值类型?

5
从JavaScript和TypeScript转过来,我想试试Go并制作一个简单的计算器。由于int和float之间有差异,编写一个接受任意数字的函数,应该采用哪种方式为佳?
例如:
package main

func add(a float64, b float64) float64 {
  return a + b;
}

func main() {
  a := 1;
  b := 2;
  fmt.Println(add(1, 2)); // 3
  fmt.Println(add(a, b)); // Cannot use a (type int) as type float64 in argument to add
  fmt.Println(add(1.5, 3.2)); // 4.7
  fmt.Println(add(2.5, 2)); // 4.5
}

我需要将所有东西都转换为浮点数(因为它“覆盖”了整数范围),还是为每种类型创建单独的函数,比如addInt(a int, b int) intaddFloat(a float64, b float64) float64?或者可能有更优雅的方法吗?

7
请注意,浮点数不包含整数范围。对于大整数,可能不存在完全相等的浮点数值。然而,总的来说,解决方法是回到你实际的目标并完成它。一个简单的计算器不能工作在“任何类型的数字值”上。它只能处理你设计系统时选定的某种数字类型(通常是浮点或定点)。编写适合该类型的函数即可。 - Rob Napier
3个回答

24

Go 1.18及以上版本

随着Go 1.18引入了类型参数,这变得更加容易实现。

您可以定义一个以T为参数的函数,并使用一个接口约束条件来限制T为数字类型。

func add[T Number](a, b T) T {
    return a + b
}

约束Number可以使用golang.org/x/exp/constraints(仍处于实验阶段)来定义:

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float
}

在哪:< / p>

  • Numberconstraints.Integerconstraints.Float 类型集合的联合
  • constraints.Integer 是所有有符号和无符号整数类型的集合
  • constraints.Float 是浮点数类型的集合

这将允许您使用任何两个数字类型参数调用add。然后,在函数体中,您将能够使用约束中所有类型都支持的任何操作,对于数字,这还包括算术运算符。然后声明类似的函数很容易:

func multiply[T Number](a, b T) T {
    return a * b
}

请记住,参数必须具有相同的类型。无论泛型如何,您都不能使用不同的类型;根据规范Operators

[...] 操作数类型必须相同,除非操作涉及移位或未命名常量。

因此,我们的通用addmultiply函数仅定义了一个类型参数T。这意味着您也不能使用默认类型不兼容的未命名常量调用add函数:
add(2.5, 2) // won't compile

在这种情况下,编译器将从第一个参数2.5推断出T的类型,默认为float64,然后无法匹配2的类型,默认为int
完整程序:
package main

import (
    "fmt"

    "golang.org/x/exp/constraints"
)

type Number interface {
    constraints.Integer | constraints.Float
}

func main() {
    a := 1
    b := 2
    
    fmt.Println(add(1, 2))     // 3
    fmt.Println(add(a, b))     // 3
    fmt.Println(add(1.5, 3.2)) // 4.7
    // fmt.Println(add(2.5, 2)) // default type int of 2 does not match inferred type float64 for T
}

func add[T Number](a, b T) T {
    return a + b
}

游乐场:https://go.dev/play/p/rdqi3_-EdHp

警告:由于这些函数还处理浮点数,所以请记住浮点数可以容纳NaN值和无穷大。


关于复数

Go语言有预定义的complex64complex128类型。您也可以在Number约束中使用它们:

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float | constraints.Complex
}

这并不限制这些泛型函数的功能:整数和浮点数支持的算术运算符(仅限+-*/)和所有顺序运算符也适用于复杂类型。取余运算符%和位运算符仅适用于整数,并且仅适用于约束为constraints.Integer的类型参数。

请记住,这是一个实验性功能,可能会从语言中移除。 - Mateusz Drewniak

3

截至Go 1.17(泛型之前)。有关更新的解决方案,请参见其他答案


最简单的选项是在调用站点转换参数。

add(float64(a), float64(b))

我认为这暂时回答了问题,尽管可能会导致不精确。或许更简单的方法是将所有可能在未来添加的值定义为float64,以避免每次运行时的转换。另外,我有点遗憾没有泛型(似乎还没有?),但我也能理解增加该功能所带来的成本。 - Lavariet
1
“一切皆为浮点数”方法是JavaScript本身的工作方式,顺便说一下。https://developer.mozilla.org/en-US/docs/Glossary/Number 。泛型计划(并且松散地安排)添加到语言中。自从很久以前就一直有人要求,但该项目天生抵制大的变化,而泛型将是自Go 1以来最大的变化。 - Hymns For Disco

3
现在由于泛型的存在,这是可能的,但是非常繁琐,因为你必须手动在函数声明中指定每种数值类型。
// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](a, b I) (I, bool) {
    c := a + b
    if (c > a) == (b > 0) {
        return c, true
    }
    return c, false
}

你还可以定义一个接口,其中包含了详细的整数类型列表。
type Int interface {
    int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64
}

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I Int](a, b I) (I, bool) {
    c := a + b
    if (c > a) == (b > 0) {
            return c, true
    }
    return c, false
}

有一个constraints包,它提供了一种更DRY的方式来定义这样的函数,但它是实验性的,并且可能在某个时候从语言中移除,所以我不建议使用它。

1
尽管看起来可能很丑,但这个选项确实允许你使用I(1234)来转换为所选择的类型,而上述使用constraints的答案则不允许。 - ibarrond

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