go编程技巧(金额计算)

233 阅读3分钟

首先复习一下基础,golang中的数字类型有整型int,浮点型float

整型

int分为无符号,有符号俩种,具体又分类为8,32,64等

package main

import (
    "fmt"
    "math"
)

func main() {
    // uint 最大值
    var maxUInt int = math.MaxUint8
    fmt.Printf("uint 最大值: %d\n", maxUInt)

    // int 最大值和最小值
    var maxInt int = math.MaxInt
    var minInt int = math.MinInt
    fmt.Printf("int 最大值: %d\n", maxInt)
    fmt.Printf("int 最小值: %d\n", minInt)

    // int32 最大值和最小值
    var maxInt32 int32 = math.MaxInt32
    var minInt32 int32 = math.MinInt32
    fmt.Printf("int32 最大值: %d\n", maxInt32)
    fmt.Printf("int32 最小值: %d\n", minInt32)

    // int64 最大值和最小值
    var maxInt64 int64 = math.MaxInt64
    var minInt64 int64 = math.MinInt64
    fmt.Printf("int64 最大值: %d\n", maxInt64)
    fmt.Printf("int64 最小值: %d\n", minInt64)
}
uint  最大值: 255
int   最大值: 9223372036854775807
int   最小值: -9223372036854775808
int32 最大值: 21474836472^311
int32 最小值: -2147483648-2^31
int64 最大值: 9223372036854775807
int64 最小值: -9223372036854775808

浮点型

float分为单精度float32和双精度float64
实际范围是基于IEEE 754标准的单、双精度浮点数表示
IEEE754 浮点数:简读+案例=秒懂_ieee754浮点数的计算

float的弊端

1. 精度问题

package main

import "fmt"

func main() {
    a := 0.1
    b := 0.2
    sum := a + b
    fmt.Println(sum) // 输出 0.30000000000000004 而不是 0.3
}
0.30000000000000004

2. 舍入问题

package main

import (
    "fmt"
)

func main() {
    a := 1.0000001
    b := 1.0000002
    difference := a - b
    fmt.Println(difference) // 预期输出 0.0000001
}
-9.999999983634211e-08

3. 比较问题

package main

import (
    "fmt"
)

func main() {
    a := float64(0.1) + float64(0.2)
    b := float64(0.3)
    fmt.Println(a == b)
}
false

4. 累计误差

package main

import (
    "fmt"
)

func main() {
    a := float64(0.1)
    for i := 0; i < 10; i++ {
       a += float64(0.1)
    }
    fmt.Println(a)
}
1.0999999999999999

如何解决上述问题,引出下文 如何计算金额

金额如何计算

我们的要求是计算时不损失精度的加法、减法、乘法,除法指定精度

库: github.com/shopspring/decimal

原理

数据结构

// number = value * 10 ^ exp
type Decimal struct {
    value *big.Int
    exp int32
}

type Int struct {
    neg bool // sign
    abs nat  // absolute value of the integer
}

type nat []Word

type Word uint

加法

转换为 value * 10 ^ exp形式的计数法,然后底层使用SDK的大数加减法进行计算

x + y == x + y
(-x) + (-y) == -(x + y)
x + (-y) == x - y == -(y - x)
(-x) + y == y - x == -(x - y)
z.neg = len(z.abs) > 0 && neg // 0 has no sign
func addVV(z, x, y []Word) (c Word)
func addVW(z, x []Word, y Word) (c Word)
func subVV(z, x, y []Word) (c Word)
func subVW(z, x []Word, y Word) (c Word)

减法

x - (-y) == x + y
(-x) - y == -(x + y)
x - y == x - y == -(y - x)
(-x) - (-y) == y - x == -(x - y)

乘法

x * y == x * y
x * (-y) == -(x * y)
(-x) * y == -(x * y)
(-x) * (-y) == x * y
如果2个数的最小长度小于40位使用基础乘法
func addMulVVW(z, x []Word, y Word) (c Word)

除法

// div returns q, r such that q = ⌊u/v⌋ and r = u%v = u - q·v.
func (z nat) div(z2, u, v nat) (q, r nat)

拓展1: 一块钱分给3个人怎么分

惯性思维 1/3=0.33 0.33*3 = 0.99 剩下0.01消失了

正确操作 2个人拿走: 1/3=0.33 最后一个人拿走 1-0.33*(2)=0.34

拓展2: 淘宝的满减券怎么一回事

淘宝上满减后退货退的金额怎么算?退款会退到哪里?
举个例子:
满减券在淘宝中是按照比例进行抵扣的,例如有一张满300减50的券。购买第一件商品100元,第二件商品200元。

那么实际核销时会显示
第一件商品抵扣50*(100/(100+200))=16.66
第二件商品抵扣50-16.66=33.34

n件商品依此类推 附图

image.png