本章中的配方包括:
- 将字符串转换为数字
- 比较浮点数
- 四舍五入浮点数
- 浮点算法
- 格式化数字
- 二进制、八进制、十进制和十六进制之间的转换
- 使用正确的复数进行格式化
- 生成随机数
- 运算复数
- 在度和弧度之间转换
- 取对数
- 生成校验和
数字通常是每个应用程序打印格式化数字、转换基本表示等过程中不可避免的一部分。本章介绍了许多您通常可以处理的操作。
检查 Go 是否正确安装。第一章与环境互动的检索戈朗版配方中的准备部分将帮助您。
此配方将向您展示如何将包含数字的字符串转换为数字类型(整型或浮点值)。
- 打开控制台,创建文件夹
chapter03/recipe01
。 - 导航到该目录。
- 创建具有以下内容的
main.go
文件:
package main
import (
"fmt"
"strconv"
)
const bin = "00001"
const hex = "2f"
const intString = "12"
const floatString = "12.3"
func main() {
// Decimals
res, err := strconv.Atoi(intString)
if err != nil {
panic(err)
}
fmt.Printf("Parsed integer: %d\n", res)
// Parsing hexadecimals
res64, err := strconv.ParseInt(hex, 16, 32)
if err != nil {
panic(err)
}
fmt.Printf("Parsed hexadecima: %d\n", res64)
// Parsing binary values
resBin, err := strconv.ParseInt(bin, 2, 32)
if err != nil {
panic(err)
}
fmt.Printf("Parsed bin: %d\n", resBin)
// Parsing floating-points
resFloat, err := strconv.ParseFloat(floatString, 32)
if err != nil {
panic(err)
}
fmt.Printf("Parsed float: %.5f\n", resFloat)
}
- 在终端执行
go run main.go
命令。 - 您将看到以下输出:
前面示例代码中的主要函数是包strconv
的ParseInt
函数。该函数由三个参数调用:输入、输入基数和位大小。基数决定如何解析数字。请注意,十六进制的基数(第二个参数)为 16,二进制的基数为 2。包strconv
的函数Atoi
实际上是以 10 为基数的ParseInt
函数。
ParseFloat
函数将字符串转换为浮点数。第二个参数是bitSize
的精度。bitSize = 64
将导致float64
。bitSize = 32
将导致float64
,但它可以在不改变其值的情况下转换为float32
由于浮点数的表示方式,在比较两个看似相同的数字时可能会出现不一致。与整数不同,IEEE 浮点数只是近似值。需要将数字转换成计算机可以存储在二进制中的形式,这会导致较小的精度或舍入偏差。例如,值 1.3 可以表示为 1.299999999。比较可以有一定的公差。要以任意精度比较数字,big
包在这里。
- 打开控制台,创建文件夹
chapter03/recipe02
。 - 导航到该目录。
- 创建具有以下内容的
tolerance.go
文件:
package main
import (
"fmt"
"math"
)
const da = 0.29999999999999998889776975374843459576368331909180
const db = 0.3
func main() {
daStr := fmt.Sprintf("%.10f", da)
dbStr := fmt.Sprintf("%.10f", db)
fmt.Printf("Strings %s = %s equals: %v \n", daStr,
dbStr, dbStr == daStr)
fmt.Printf("Number equals: %v \n", db == da)
// As the precision of float representation
// is limited. For the float comparison it is
// better to use comparison with some tolerance.
fmt.Printf("Number equals with TOLERANCE: %v \n",
equals(da, db))
}
const TOLERANCE = 1e-8
// Equals compares the floating-point numbers
// with tolerance 1e-8
func equals(numA, numB float64) bool {
delta := math.Abs(numA - numB)
if delta < TOLERANCE {
return true
}
return false
}
- 在终端执行
go run tolerance.go
命令。 - 您将看到以下输出:
- 创建具有以下内容的文件
big.go
:
package main
import (
"fmt"
"math/big"
)
var da float64 = 0.299999992
var db float64 = 0.299999991
var prec uint = 32
var prec2 uint = 16
func main() {
fmt.Printf("Comparing float64 with '==' equals: %v\n", da == db)
daB := big.NewFloat(da).SetPrec(prec)
dbB := big.NewFloat(db).SetPrec(prec)
fmt.Printf("A: %v \n", daB)
fmt.Printf("B: %v \n", dbB)
fmt.Printf("Comparing big.Float with precision: %d : %v\n",
prec, daB.Cmp(dbB) == 0)
daB = big.NewFloat(da).SetPrec(prec2)
dbB = big.NewFloat(db).SetPrec(prec2)
fmt.Printf("A: %v \n", daB)
fmt.Printf("B: %v \n", dbB)
fmt.Printf("Comparing big.Float with precision: %d : %v\n",
prec2, daB.Cmp(dbB) == 0)
}
- 在终端运行
go run big.go
执行代码。 - 您将看到以下输出:
第一种不使用任何内置包(步骤1-5)的浮点数比较方法需要使用所谓的EPSILON
常量。这是选择两个数之间的足够小的δ(差)来考虑值相等的值。δ常数可能在 1e-8 量级,这通常足够精确。
第二个选项更复杂,但对于进一步处理浮点数也更有用。软件包math/big
提供Float
类型,可针对给定精度进行配置。该软件包的优点是,其精度可能远高于float64
型的精度。出于说明目的,小精度值用于显示给定精度下的舍入和比较。
请注意,使用 16 位精度时,da
和db
数字相等,而使用 32 位精度时不相等。可从big.MaxPrec
常数获得最大配置精度。
必须正确地将浮点数舍入为整数或特定精度。最常见的错误是将浮点型 AutoT0Ep 转换为整数类型,并将其视为处理好。
例如,可以将数字 3.9999 转换为整数,并期望它成为值为 4 的整数。实际结果是 3。在编写本书时,Go(1.9.2)的当前版本不包含Round
功能。然而,在版本 1.10 中,Round
功能已经在math
包中实现。
- 打开控制台,创建文件夹
chapter03/recipe03
。 - 导航到该目录。
- 创建具有以下内容的
round.go
文件:
package main
import (
"fmt"
"math"
)
var valA float64 = 3.55554444
func main() {
// Bad assumption on rounding
// the number by casting it to
// integer.
intVal := int(valA)
fmt.Printf("Bad rounding by casting to int: %v\n", intVal)
fRound := Round(valA)
fmt.Printf("Rounding by custom function: %v\n", fRound)
}
// Round returns the nearest integer.
func Round(x float64) float64 {
t := math.Trunc(x)
if math.Abs(x-t) >= 0.5 {
return t + math.Copysign(1, x)
}
return t
}
- 在终端运行
go run round.go
执行代码。 - 您将看到以下输出:
将浮点转换为整数实际上只是截断浮点值。假设值 2 表示为 1.999999;在本例中,输出为 1,这不是您所期望的。
舍入浮点数的正确方法是使用还考虑小数部分的函数。常用的四舍五入方法是从零开始减半(也称为商业四舍五入)。简单来说,如果数字包含小数部分大于或等于 0.5 的绝对值,则该数字向上舍入,否则向下舍入。
**在函数Round
中,包math
的函数Trunc
截断数字的小数部分。然后,提取数字的小数部分。如果该值超过 0.5 的限制,则添加与整数值具有相同符号的值 1。
Go 版本 1.10 使用了示例中提到的功能的更快实现。在 1.10 版中,您只需调用math.Round
函数即可获得整数。
正如前面的配方中所述,浮点数的表示也使算法复杂化。一般来说,内置float64
上的操作就足够了。如果需要更高的精度,math/big
包将发挥作用。这个食谱将告诉你如何处理这个问题。
- 打开控制台,创建文件夹
chapter03/recipe04
。 - 导航到该目录。
- 创建具有以下内容的
main.go
文件:
package main
import (
"fmt"
"math/big"
)
const PI = `3.1415926535897932384626433832795028841971693
993751058209749445923078164062862089986280348253
421170679821480865132823066470938446095505822317
253594081284811174502841027019385211055596446229
4895493038196`
const diameter = 3.0
const precision = 400
func main() {
pi, _ := new(big.Float).SetPrec(precision).SetString(PI)
d := new(big.Float).SetPrec(precision).SetFloat64(diameter)
circumference := new(big.Float).Mul(pi, d)
pi64, _ := pi.Float64()
fmt.Printf("Circumference big.Float = %.400f\n",
circumference)
fmt.Printf("Circumference float64 = %.400f\n", pi64*diameter)
sum := new(big.Float).Add(pi, pi)
fmt.Printf("Sum = %.400f\n", sum)
diff := new(big.Float).Sub(pi, pi)
fmt.Printf("Diff = %.400f\n", diff)
quo := new(big.Float).Quo(pi, pi)
fmt.Printf("Quocient = %.400f\n", quo)
}
- 在终端运行
go run main.go
执行代码。 - 您将看到以下输出:
big
软件包支持高精度浮点运算。前面的示例演示了对数字的基本操作。请注意,代码将该操作与float64
类型和big.Float
类型进行比较。
通过高精度处理数字,使用big.Float
类型至关重要。当big.Float
转换回内置float64
类型时,高精度丢失
大的包包含更多的Float
类型的操作。参见文件(https://golang.org/pkg/math/big/#Float 有关更多详细信息,请参阅此软件包的。
浮点数的比较和舍入在比较浮点数和舍入浮点数配方中提到。
如果将数字转换为字符串,则通常需要对其进行合理的格式化。数字的格式表示数字是用给定的数字打印的,由数字和小数组成。也可以选择值的表示形式。然而,与此密切相关的一个问题是数字格式的本地化。例如,某些语言使用逗号分隔的零。
- 打开控制台,创建文件夹
chapter03/recipe05
。 - 导航到该目录。
- 创建具有以下内容的
format.go
文件:
package main
import (
"fmt"
)
var integer int64 = 32500
var floatNum float64 = 22000.456
func main() {
// Common way how to print the decimal
// number
fmt.Printf("%d \n", integer)
// Always show the sign
fmt.Printf("%+d \n", integer)
// Print in other base X -16, o-8, b -2, d - 10
fmt.Printf("%X \n", integer)
fmt.Printf("%#X \n", integer)
// Padding with leading zeros
fmt.Printf("%010d \n", integer)
// Left padding with spaces
fmt.Printf("% 10d \n", integer)
// Right padding
fmt.Printf("% -10d \n", integer)
// Print floating
// point number
fmt.Printf("%f \n", floatNum)
// Floating-point number
// with limited precision = 5
fmt.Printf("%.5f \n", floatNum)
// Floating-point number
// in scientific notation
fmt.Printf("%e \n", floatNum)
// Floating-point number
// %e for large exponents
// or %f otherwise
fmt.Printf("%g \n", floatNum)
}
- 在主终端运行
go run format.go
执行代码。 - 您将看到以下输出:
- 创建具有以下内容的文件
localized.go
:
package main
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
const num = 100000.5678
func main() {
p := message.NewPrinter(language.English)
p.Printf(" %.2f \n", num)
p = message.NewPrinter(language.German)
p.Printf(" %.2f \n", num)
}
- 在主终端运行
go run localized.go
执行代码。 - 您将看到以下输出:
代码示例显示了最常用的整数和浮点数选项。
Go 中的格式来自 C 的printf
函数。所谓的verbs
用于定义数字的格式。例如,动词可以是%X
,它实际上是值的占位符。
除了基本的格式之外,还有一些与当地习俗相关的格式规则。对于格式,根据区域设置,golang.org/x/text/message
包可能会有所帮助。请参阅本配方中的第二个代码示例。这样,就可以对数字格式进行本地化。
有关所有格式选项,请参阅fmt
包。strconv
软件包在您希望在不同的基础上格式化数字时也很有用。下面的配方描述了数字转换的可能性,但作为副作用,提供了如何在不同的基础上格式化数字的选项。
在某些情况下,整数值可以用十进制以外的表示形式表示。使用strconv
包可以轻松地完成这些表示之间的转换。
- 打开控制台,创建文件夹
chapter03/recipe06
。 - 导航到该目录。
- 创建具有以下内容的
convert.go
文件:
package main
import (
"fmt"
"strconv"
)
const bin = "10111"
const hex = "1A"
const oct = "12"
const dec = "10"
const floatNum = 16.123557
func main() {
// Converts binary value into hex
v, _ := ConvertInt(bin, 2, 16)
fmt.Printf("Binary value %s converted to hex: %s\n", bin, v)
// Converts hex value into dec
v, _ = ConvertInt(hex, 16, 10)
fmt.Printf("Hex value %s converted to dec: %s\n", hex, v)
// Converts oct value into hex
v, _ = ConvertInt(oct, 8, 16)
fmt.Printf("Oct value %s converted to hex: %s\n", oct, v)
// Converts dec value into oct
v, _ = ConvertInt(dec, 10, 8)
fmt.Printf("Dec value %s converted to oct: %s\n", dec, v)
//... analogically any other conversion
// could be done.
}
// ConvertInt converts the given string value of base
// to defined toBase.
func ConvertInt(val string, base, toBase int) (string, error) {
i, err := strconv.ParseInt(val, base, 64)
if err != nil {
return "", err
}
return strconv.FormatInt(i, toBase), nil
}
- 在主终端运行
go run convert.go
执行代码。 - 您将看到以下输出:
strconv
包提供了ParseInt
和FormatInt
两个函数,可以说它们是互补函数。函数ParseInt
能够解析任何基表示形式的整数。另一方面,函数FormatInt
可以将整数格式化为任何给定的基
最后,可以将整数的字符串表示形式解析为内置的int64
类型,然后将解析后的整数字符串格式化为给定的基表示形式。
当为用户显示消息时,如果句子更人性化,交互会更愉快。Go 包golang.org/x/text
是扩展包,包含以正确方式格式化复数的此功能。
如果您还没有扩展包,执行go get -x golang.org/x/text
获取扩展包。
- 打开控制台,创建文件夹
chapter03/recipe07
。 - 导航到该目录。
- 创建具有以下内容的
plurals.go
文件:
package main
import (
"golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func main() {
message.Set(language.English, "%d items to do",
plural.Selectf(1, "%d", "=0", "no items to do",
plural.One, "one item to do",
"<100", "%[1]d items to do",
plural.Other, "lot of items to do",
))
message.Set(language.English, "The average is %.2f",
plural.Selectf(1, "%.2f",
"<1", "The average is zero",
"=1", "The average is one",
plural.Other, "The average is %[1]f ",
))
prt := message.NewPrinter(language.English)
prt.Printf("%d items to do", 0)
prt.Println()
prt.Printf("%d items to do", 1)
prt.Println()
prt.Printf("%d items to do", 10)
prt.Println()
prt.Printf("%d items to do", 1000)
prt.Println()
prt.Printf("The average is %.2f", 0.8)
prt.Println()
prt.Printf("The average is %.2f", 1.0)
prt.Println()
prt.Printf("The average is %.2f", 10.0)
prt.Println()
}
-
在主终端运行
go run plurals.go
执行代码。 -
您将看到以下输出:
包golang.org/x/text/message
包含函数NewPrinter
,该函数接受语言标识并创建格式化的 I/O,与fmt
包相同,但具有基于性别和复数形式翻译消息的能力。
message
包的Set
功能增加了翻译和复数选择。复数形式本身是根据通过Selectf
函数设置的规则选择的。Selectf
函数根据plural.Form
或选择器生成具有规则的catalog.Message
类型。
前面的示例代码使用plural.One
和plural.Other
表单以及=x, <x
选择器。这些与格式动词%d
匹配(也可以使用其他动词)。选择第一个匹配案例。
有关选择器和表单的更多信息,请参阅golang.org/x/text/message
包的文档。
这个食谱展示了如何生成随机数。此功能由math/rand
包提供。math/rand
生成的随机数被认为是加密不安全的,因为序列在给定种子下是可重复的。
要生成加密安全的数字,应使用crypto/rand
包。这些序列是不可重复的。
- 打开控制台,创建文件夹
chapter03/recipe08
。 - 导航到该目录。
- 创建具有以下内容的
rand.go
文件:
package main
import (
crypto "crypto/rand"
"fmt"
"math/big"
"math/rand"
)
func main() {
sec1 := rand.New(rand.NewSource(10))
sec2 := rand.New(rand.NewSource(10))
for i := 0; i < 5; i++ {
rnd1 := sec1.Int()
rnd2 := sec2.Int()
if rnd1 != rnd2 {
fmt.Println("Rand generated non-equal sequence")
break
} else {
fmt.Printf("Math/Rand1: %d , Math/Rand2: %d\n", rnd1, rnd2)
}
}
for i := 0; i < 5; i++ {
safeNum := NewCryptoRand()
safeNum2 := NewCryptoRand()
if safeNum == safeNum2 {
fmt.Println("Crypto generated equal numbers")
break
} else {
fmt.Printf("Crypto/Rand1: %d , Crypto/Rand2: %d\n",
safeNum, safeNum2)
}
}
}
func NewCryptoRand() int64 {
safeNum, err := crypto.Int(crypto.Reader, big.NewInt(100234))
if err != nil {
panic(err)
}
return safeNum.Int64()
}
- 在主终端运行
go run rand.go
执行代码。 - 您将看到以下输出:
前面的代码提供了两种生成随机数的方法。第一个选项使用math/rand
包,这是加密不安全的,允许我们使用具有相同种子编号的Source
生成相同序列。这种方法通常用于测试。这样做的原因是为了序列的再现性。
第二种选择是使用crypto/rand
包,即加密安全的选择。API 使用Reader
提供加密强伪随机生成器的实例。包本身有默认的Reader
,通常基于基于系统的随机数生成器。
复数通常用于科学应用和计算。Go 将复数实现为基本类型。复数的具体操作是math/cmplx
包的一部分。
- 打开控制台,创建文件夹
chapter03/recipe09
。 - 导航到该目录。
- 创建具有以下内容的
complex.go
文件:
package main
import (
"fmt"
"math/cmplx"
)
func main() {
// complex numbers are
// defined as real and imaginary
// part defined by float64
a := complex(2, 3)
fmt.Printf("Real part: %f \n", real(a))
fmt.Printf("Complex part: %f \n", imag(a))
b := complex(6, 4)
// All common
// operators are useful
c := a - b
fmt.Printf("Difference : %v\n", c)
c = a + b
fmt.Printf("Sum : %v\n", c)
c = a * b
fmt.Printf("Product : %v\n", c)
c = a / b
fmt.Printf("Product : %v\n", c)
conjugate := cmplx.Conj(a)
fmt.Println("Complex number a's conjugate : ", conjugate)
cos := cmplx.Cos(b)
fmt.Println("Cosine of b : ", cos)
}
- 在主终端运行
go run complex.go
执行代码。 - 您将看到以下输出:
基本运算符是为基元类型complex
实现的。关于复数的其他操作由math/cmplx
包提供。如果需要高精度的操作,则不存在big
实现
另一方面,复数可以实现为实数,虚部用big.Float
类型表示。
三角运算和几何操作通常以弧度进行;将这些转换为度总是很有用的,反之亦然。本食谱将向您展示如何处理这些单位之间转换的一些技巧。
- 打开控制台,创建文件夹
chapter03/recipe10
。 - 导航到该目录。
- 创建具有以下内容的
radians.go
文件:
package main
import (
"fmt"
"math"
)
type Radian float64
func (rad Radian) ToDegrees() Degree {
return Degree(float64(rad) * (180.0 / math.Pi))
}
func (rad Radian) Float64() float64 {
return float64(rad)
}
type Degree float64
func (deg Degree) ToRadians() Radian {
return Radian(float64(deg) * (math.Pi / 180.0))
}
func (deg Degree) Float64() float64 {
return float64(deg)
}
func main() {
val := radiansToDegrees(1)
fmt.Printf("One radian is : %.4f degrees\n", val)
val2 := degreesToRadians(val)
fmt.Printf("%.4f degrees is %.4f rad\n", val, val2)
// Conversion as part
// of type methods
val = Radian(1).ToDegrees().Float64()
fmt.Printf("Degrees: %.4f degrees\n", val)
val = Degree(val).ToRadians().Float64()
fmt.Printf("Rad: %.4f radians\n", val)
}
func degreesToRadians(deg float64) float64 {
return deg * (math.Pi / 180.0)
}
func radiansToDegrees(rad float64) float64 {
return rad * (180.0 / math.Pi)
}
- 在主终端运行
go run radians.go
执行代码。 - 您将看到以下输出:
Go 标准库不包含任何具有将弧度转换为度的功能的软件包,反之亦然。但至少 Pi 常量是math
包的一部分,因此可以按照示例代码所示进行转换。
前面的代码还介绍了使用其他方法定义自定义类型的方法。这些都简化了 handy API 的值转换。
对数用于科学应用以及数据可视化和测量。内置的math
包包含常用的对数基数。使用这些,你可以得到所有的基础。
- 打开控制台,创建文件夹
chapter03/recipe11
。 - 导航到该目录。
- 创建具有以下内容的
log.go
文件:
package main
import (
"fmt"
"math"
)
func main() {
ln := math.Log(math.E)
fmt.Printf("Ln(E) = %.4f\n", ln)
log10 := math.Log10(-100)
fmt.Printf("Log10(10) = %.4f\n", log10)
log2 := math.Log2(2)
fmt.Printf("Log2(2) = %.4f\n", log2)
log_3_6 := Log(3, 6)
fmt.Printf("Log3(6) = %.4f\n", log_3_6)
}
// Log computes the logarithm of
// base > 1 and x greater 0
func Log(base, x float64) float64 {
return math.Log(x) / math.Log(base)
}
- 在主终端运行
go run log.go
执行代码。 - 您将看到以下输出:
标准包math
包含所有常用对数的函数,因此您可以轻松获得二进制、十进制和自然对数。参见Log函数,该函数通过助手定义的公式计算y与x的任何对数:
标准库中对数的内部实现自然基于近似。此功能可以在$GOROOT/src/math/log.go
文件中看到。
哈希或所谓的校验和是快速比较任何内容的最简单方法。此配方演示如何创建文件内容的校验和。出于演示目的,将使用 MD5 哈希函数。
-
打开控制台,创建文件夹
chapter03/recipe12
。 -
导航到该目录。
-
创建具有以下内容的
content.dat
文件:
This is content to check
- 创建具有以下内容的
checksum.go
文件:
package main
import (
"crypto/md5"
"fmt"
"io"
"os"
)
var content = "This is content to check"
func main() {
checksum := MD5(content)
checksum2 := FileMD5("content.dat")
fmt.Printf("Checksum 1: %s\n", checksum)
fmt.Printf("Checksum 2: %s\n", checksum2)
if checksum == checksum2 {
fmt.Println("Content matches!!!")
}
}
// MD5 creates the md5
// hash for given content encoded in
// hex string
func MD5(data string) string {
h := md5.Sum([]byte(data))
return fmt.Sprintf("%x", h)
}
// FileMD5 creates hex encoded md5 hash
// of file content
func FileMD5(path string) string {
h := md5.New()
f, err := os.Open(path)
if err != nil {
panic(err)
}
defer f.Close()
_, err = io.Copy(h, f)
if err != nil {
panic(err)
}
return fmt.Sprintf("%x", h.Sum(nil))
}
- 在主终端运行
go run checksum.go
执行代码。 - 您将看到以下输出:
- 创建具有以下内容的
sha_panic.go
文件:
package main
import (
"crypto"
)
func main() {
crypto.SHA1.New()
}
- 在主终端运行
go run sha_panic.go
执行代码。 - 您将看到以下输出:
crypto
包包含著名散列函数的实现。MD5
散列函数位于crypto/md5
包中。crypto
包中的每个哈希函数实现Hash
接口,注意Hash
包含Write
方法。使用Write
方法,它可以用作Writer
。这可以在FileMD5
功能中看到。Hash
的Sum
方法接受 byte slice 的参数,由此产生的散列应该放在这里。
当心这个。Sum
方法不计算参数的散列,而是将散列计算到参数中。
另一方面,package 函数md5.Sum
可以直接用于生成散列。在这种情况下,Sum
函数的参数是计算出的散列值中的参数。
当然,crypto
包也实现了SHA
变体和其他哈希函数。它们通常以相同的方式使用。散列函数可以通过crypto
包常量crypto.Hash
(例如crypto.MD5.New()
)访问,但这样,具有给定函数的包也必须链接到内置二进制文件(可以使用空白导入,import _ "crypto/md5"
),否则对New
的调用将死机。
hash
包本身包含 CRC 校验和等。**