在进行 big.Int 类型的简单相互赋值过程中发生了浅拷贝, big.Int 类型数据存储的实体 []uint 并未发生变更,导致出现数据紊乱。

在Golang中,标准库提供了big包用来进行大数运算。为了研究它的用法,我编写了下边这个小程序来验证它的特性。程序的逻辑很简单,初始化两个大数变量 a = 1 b = 2 ,然后使用中间变量法对 a b 进行交换,交换完毕后再对中间变量加100,然后输出交换的结果。

这个程序足够简单,以至于简单到我们可以立马说出它的答案, a = 2, b = 1 但是当控制台中结果输出的那一刻发现事情并不简单。。。

package main
import (
	"fmt"
	"math/big"
func main() {
	// 初始化两个变量: a = 1, b = 2
	a := big.NewInt(1)
	b := big.NewInt(2)
	// 打印交换前的数值
	fmt.Printf("a = %v   b = %v\n", a, b)
	// 使用中间变量法进行交换
	tmp := a
	a = b
	b = tmp
	// 交换完成, 对中间变量加100
	tmp.Add(tmp, big.NewInt(100))
	// 打印交换后的结果
	fmt.Printf("a = %v    b = %v   tmp = %v\n", a, b, tmp)
a = 1   b = 2
a = 2   b = 101   tmp = 101

从结果中可以看出,a和b的内容确实进行了交换,但是对中间变量tmp加100的操作貌似也对变量b生效了。这是为什么呢?

在上述程序中,我们使用 big.NewInt() 函数对 a、b 进行了初始化,为此,我们找到该函数的实现:

// NewInt allocates and returns a new Int set to x.
func NewInt(x int64) *Int {
	return new(Int).SetInt64(x)

从实现上可以看出,该函数会返回一个 *Int 类型,也就是类型 Int 的指针。回头看我们的程序,对于 tmp := a 这条语句而言,实际上是将 a 所指向的地址存进了 tmp 变量中,后续对于 tmp 变量的一切操作实则就是对 a 变量的操作,所以 tmp.Add(tmp, big.NewInt(100)) 这条语句也对 b(交换前的a) 变量生效。

问题似乎得到了解决,接下来更改程序,验证猜想:

package main
import (
	"fmt"
	"math/big"
func main() {
	// 初始化两个变量: a = 1, b = 2
	a := big.NewInt(1)
	b := big.NewInt(2)
	// 打印交换前的数值
	fmt.Printf("a = %v   b = %v\n", a, b)
	// 使用中间变量法进行交换
	tmp := *a
	*a = *b
	*b = tmp
	// 交换完成, 对中间变量加100
	tmp.Add(&tmp, big.NewInt(100))
	// 打印交换后的结果
	fmt.Printf("a = %v    b = %v   tmp = %v\n", a, b, tmp)

在第二版程序中,对原来交换逻辑做了更改,将指针的赋值操作更改为对指针的指向做取值操作。

a = 1   b = 2
a = 2   b = 101  tmp = 101

然鹅。。。从输出结果来看,问题并没有得到解决。

为什么取值操作没有什么卵用呢?我们注意到这里的 abtmp 实际上是标准库中的 Int 类型,而非保留字 int 类型,所以是不是 Int 类型的实现导致我们的赋值操作无效呢?

// An Int represents a signed multi-precision integer.
// The zero value for an Int represents the value 0.
type Int struct {
	neg bool // sign
	abs nat  // absolute value of the integer
// An unsigned integer x of the form
//   x = x[n-1]*_B^(n-1) + x[n-2]*_B^(n-2) + ... + x[1]*_B + x[0]
// with 0 <= x[i] < _B and 0 <= i < n is stored in a slice of length n,
// with the digits x[i] as the slice elements.
// A number is normalized if the slice contains no leading 0 digits.
// During arithmetic operations, denormalized values may occur but are
// always normalized before returning the final result. The normalized
// representation of 0 is the empty or nil slice (length = 0).
type nat []Word
// A Word represents a single digit of a multi-precision unsigned integer.
type Word uint

如上,是标准库中 Int 类型的定义。从定义中可以看出,该struct共有两个成员变量:一个表示当前是否为负数的 neg 变量,一个表示当前数值绝对值的 abs 变量,而 abs 变量的类型是 nas 类型,该类型实际上是一个 []uint 类型,即uint的splice。在golang中,splice是一种引用类型,即对于splice类型进行的参数传递、赋值等操作实际上是对其引用的赋值以及传递。所以,在我们第二版程序中的赋值操作其实是执行了一次浅拷贝,即将成员变量 abs 的引用更改为了 a 变量的引用,当 ab 交换后 tmp 的成员变量 abs 依然执行交换前 a 变量的 abs 地址,所以 tmp 变量的变更其实还是对交换前 a 变量的变更。

知道了原因,解决这个问题的方案就有了,我们只需要使用 big.Int 提供的 Set() 方法,对其进行深拷贝即可。当然,在数据量比较大的时候,深拷贝固然安全,但是其性能消耗也是蛮大的,具体使用哪种方式还是需要读者根据实际使用场景进行决断。

package main
import (
	"fmt"
	"math/big"
func main() {
	// 初始化两个变量: a = 1, b = 2
	a := big.NewInt(1)
	b := big.NewInt(2)
	// 打印交换前的数值
	fmt.Printf("a = %v    b = %v\n", a, b)
	// 使用中间变量法进行交换
	tmp := big.NewInt(0)
	tmp.Set(a)
	a.Set(b)
	b.Set(tmp)
	// 交换完成, 对中间变量加100
	tmp.Add(tmp, big.NewInt(100))
	// 打印交换后的结果
	fmt.Printf("a = %v    b = %v   tmp = %v\n", a, b, tmp)
a = 1    b = 2
a = 2    b = 1   tmp = 101
                    结论先行在进行 big.Int 类型的简单相互赋值过程中发生了浅拷贝,big.Int 类型数据存储的实体 []uint 并未发生变更,导致出现数据紊乱。问题引入在Golang中,标准库提供了big包用来进行大数运算。为了研究它的用法,我编写了下边这个小程序来验证它的特性。程序的逻辑很简单,初始化两个大数变量 a = 1 和 b = 2,然后使用中间变量法对 a 和 b 进行交换,交换完毕后再...
Biggolang内置的*big.Float类型的简单,不可改变的包装器,其目的是提供更用户友好的API和不变性保证,但要以牺牲一些运行时性能为代价。
用法很简单:
 dec := big . NewDecimal ( 1.24 )
addend := big . NewDecimal ( 3.14 )
dec . Add ( addend ). String () // prints "4.38"
				
Go练习(大数) Go练习,试试go的大数类,bigInt就不分析了,分析的人很多(主要还是我菜)…具体讲讲怎么用 找了道题试试(洛谷p1009),求前n项的阶乘和(n<=50) 初始化一下大数数组,弄个大数的指针c记录计算结果,res用来计算就好了 package main import ( "fmt" "math/big" //大数类 var n int64 func main() { fmt.Scan(&n) a:=make([]big.Int,55) //初
math/big 作为 Go 语言提供的进行大数操作的官方库,在以太坊 Ethereum 项目中作为 currency 的类型表示得到了广泛的使用,这篇文章主要介绍该库的使用。 官方包解析 在官方的 math/big 包中,Int 类型定义如下: // An Int represents a signed multi-precision integer. // The zero v...
go语言中内置了big类型的数据,其包含很多常用的方法,比如比较两个大数是否相等,cmp,如果是-1表示前面的数字比较小,0表示相等,1表示前面的数字比较大。 newInt可以对数据进行初始化, 可以轻松从其他的类型的数据得到big类型的数据。 big在椭圆曲线加密中经常会用到。
nil 是 interface、function、pointer、map、slice 和 channel 类型变量的默认初始值 不可以用"=="比较类型: slice(切片)、map(key-value集合)、func(函数) map的比较可以使用reflect.DeepEqual() m1 := map[string]string{"one": "a", "two": "b"} m2 := map[string]string{"two": "b", "one": "a"} // 1.map类型
在进行以太币转账之前,需要比较big.Int格式的余额bigWeiBalance和转账金额bigWeiValue。遇到一个很傻的问题就是big.Int 没有 "<"">"的比较。 比较方法如下: enough := bigWeiBalance.Cmp(bigWeiValue) 原来,big.Int 类自带cmp方法 返回 1:前面的big.Int 实例大于cmp方法big.I...
f, bool := new(big.Float).SetString("100.02222") if bool == false { log.Error("err:SendTxAcceptRecord") //正常转大数 s1, boolSet := new(big.Float).SetString("1000000000...
net.Conn.Read 方法是 Go 语言标准库中 net.Conn 接口的一部分,用于从连接读取数据。它接受一个字节切片作为参数,并将连接中的数据读入该切片,返回实际读取的字节数和可能的错误。 使用方法: n, err := conn.Read(buf) 参数 buf 是存储读取数据的字节切片,n 是实际读取的字节数,err 是可能的错误。 如果在读取时遇到错误或连接已关闭,则 err 会返回 io.EOF 值。