为了账号安全,请及时绑定邮箱和手机立即绑定

IEEE 754 二进制浮点数对于金钱而言不精确

IEEE 754 二进制浮点数对于金钱而言不精确

Go
凤凰求蛊 2023-07-26 17:02:32
当我使用math.Floor浮点变量时遇到问题(向下舍入/截断精度部分)。我怎样才能正确地做到这一点?package mainimport (    "fmt"    "math")func main() {    var st float64 = 1980    var salePrice1 = st * 0.1 / 1.1    fmt.Printf("%T:%v\n", salePrice1, salePrice1) // 179.9999    var salePrice2 = math.Floor(st * 0.1 / 1.1)    fmt.Printf("%T:%v\n", salePrice2, salePrice2) // 179}游乐场:https://play.golang.org/p/49TjJwwEdEJ输出:float64:179.99999999999997float64:179我期望 的输出为1980 * 0.1 / 1.1,180但实际输出为179。
查看完整描述

2 回答

?
慕工程0101907

TA贡献1887条经验 获得超5个赞

原问题:


golang中的楼层数不正确


当我将 Math.Floor 与 float 变量一起使用时遇到问题(向下舍入/截断精度部分)。我怎样才能正确地做到这一点?


package main


import (

    "fmt"

    "math"

)


func main() {

    var st float64 = 1980

    var salePrice1 = st * 0.1 / 1.1

    fmt.Printf("%T:%v\n", salePrice1, salePrice1)

    var salePrice2 = math.Floor(st * 0.1 / 1.1)

    fmt.Printf("%T:%v\n", salePrice2, salePrice2)

}

我预计 1980 * 0.1 / 1.1 的产量是 180,但实际产量是 179。”


操场:


输出:


float64:179.99999999999997

float64:179

XY 问题询问您尝试的解决方案,而不是您的实际问题:XY 问题。


显然,这是一个金钱计算salePrice1。货币计算使用精确的十进制计算,而不是不精确的二进制浮点计算。


对于货币计算,使用整数。例如,


package main


import "fmt"


func main() {

    var st int64 = 198000 // $1980.00 as cents


    fmt.Printf("%[1]T:%[1]v\n", st)

    fmt.Printf("$%d.%02d\n", st/100, st%100)


    var n, d int64 = 1, 11

    fmt.Printf("%d, %d\n", n, d)


    var salePrice1 int64 = (st * n) / d // round down


    fmt.Printf("%[1]T:%[1]v\n", salePrice1)

    fmt.Printf("$%d.%02d\n", salePrice1/100, salePrice1%100)


    var salePrice2 int64 = ((st*n)*10/d + 5) / 10 // round half up


    fmt.Printf("%[1]T:%[1]v\n", salePrice2)

    fmt.Printf("$%d.%02d\n", salePrice2/100, salePrice2%100)


    var salePrice3 int64 = (st*n + (d - 1)) / d // round up


    fmt.Printf("%[1]T:%[1]v\n", salePrice1)

    fmt.Printf("$%d.%02d\n", salePrice3/100, salePrice3%100)

}

游乐场:https://play.golang.org/p/HbqVJUXXR-N


输出:


int64:198000

$1980.00

1, 11

int64:18000

$180.00

int64:18000

$180.00

int64:18000

$180.00


查看完整回答
反对 回复 2023-07-26
?
千巷猫影

TA贡献1829条经验 获得超7个赞

试试这个:


    st := 1980.0

    f := 0.1 / 1.1

    salePrice1 := st * f

    salePrice2 := math.Floor(salePrice1)

    fmt.Println(salePrice2) // 180

这是一个很大的话题:

对于会计系统:答案是浮点错误缓解。


(注意:一种缓解技术是使用int64、uint64、 或big.Int)


请参阅:

每个计算机科学家应该了解的浮点运算 https://en.wikipedia.org/wiki/Double- precision_floating-point_format https://en.wikipedia.org/wiki/IEEE_floating_point


让我们从以下开始:


fmt.Println(1.0 / 3.0) // 0.3333333333333333

IEEE 754 二进制表示:


fmt.Printf("%#X\n", math.Float64bits(1.0/3.0)) // 0X3FD5555555555555

1.1 的 IEEE 754 二进制表示:


fmt.Printf("%#X\n", math.Float64bits(1.1))        // 0X3FF199999999999A

fmt.Printf("%#X\n", math.Float64bits(st*0.1/1.1)) // 0X40667FFFFFFFFFFF

现在,让:


st := 1980.0

f := 0.1 / 1.1

IEEE 754 的二进制表示为f:


fmt.Printf("%#X\n", math.Float64bits(f)) // 0X3FB745D1745D1746

和:


salePrice1 := st * f

fmt.Println(salePrice1) // 180

fmt.Printf("%#X\n", math.Float64bits(salePrice1)) // 0X4066800000000000

salePrice2 := math.Floor(salePrice1)

fmt.Printf("%#X\n", math.Float64bits(salePrice2)) // 0X4066800000000000

在计算机上使用浮点数与使用笔和纸不同(浮点计算错误):


    var st float64 = 1980

    var salePrice1 = st * 0.1 / 1.1

    fmt.Println(salePrice1) // 179.99999999999997

salePrice1是 179.99999999999997 而不是 180.0,因此小于或等于 179.99999999999997 的整数值是 179:


请参阅文档func Floor(x float64) float64:


Floor 返回小于或等于 x 的最大整数值。


看:


    fmt.Println(math.Floor(179.999))       // 179

    fmt.Println(math.Floor(179.5 + 0.5))   // 180

    fmt.Println(math.Floor(179.999 + 0.5)) // 180

    fmt.Println(math.Floor(180.0))         // 180


查看完整回答
反对 回复 2023-07-26
  • 2 回答
  • 0 关注
  • 125 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信