在Go中将货币从浮动转换为整数的最佳方法是什么?
-------------添加问题解释----------
为了稍微扩展我的问题,以下是我所看到的“问题”的一个例子,它通过添加0.004的舍入值(对于2位小数货币)来解决。
据我所知,外部值存储为例如。 RDBMS中的decimal,double,currency需要作为float“导入”Go。为了执行计算,它们需要转换为整数,或者至少是一种方法。
在下面的示例中,fVal1模拟从数据库导入的数量。要对它执行计算,我想将其转换为整数。在我看来,最简单的方法是添加一个如图所示的舍入数字。
示例代码:
var fVal1 float64 = 19.08
var f100 float64 = 100.0
var fRound float64 = 0.004
var fResult float64 = fVal1 * f100
fmt.Printf("%f * %f as float = %f\n", fVal1, f100, fResult)
var iResult1 int64 = int64(fResult)
fmt.Printf("as Integer unrounded = %d\n", iResult1)
var iResult2 int64 = int64(fResult + fRound)
fmt.Printf("as Integer rounded = %d\n", iResult2)
控制台输出:
19.080000 * 100.000000 as float = 1908.000000
as Integer unrounded = 1907
as Integer rounded = 1908
-----------问题的结尾-----------
我已经实现了一个小程序包来处理Go中的多种货币,基本上它围绕下面的代码(下面)。
当我发现浮点数和整数之间的转换存在舍入误差时,我尝试的第一个舍入因子是0.004(2位小数),它似乎工作正常。
基本上,从float到int的货币转换的以下代码围绕这两个替代代码行:
var iCcyAmt1 int64 = int64(fCcyBase * fScale)
var iCcyAmt2 int64 = int64((fCcyBase + fRound) * fScale)
第二种选择中“fRound”中使用的舍入为“0.004”。
运行此测试程序(1000万次迭代)会导致一致的舍入误差约为588,000美分,其中未添加“0.004”,使用舍入数字则为零差异。
这是一种处理这种情况的安全方法(我不想使用字符串),还是有更好的方法?
测试程序:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var (
fRound float64 = 0.004
fScale float64 = 100.0
iCcyTotBase int64
iCcyTot1 int64
iCcyTot2 int64
)
const I_ITERS int = 10000000
rand.Seed(time.Now().UTC().UnixNano())
fmt.Printf("\nTesting Float to Integer (%d iterations)"+
" .........", I_ITERS)
for i := 0; i < I_ITERS; i++ {
var iCcyBase int64 = int64(999 + rand.Intn(9999999))
var fCcyBase float64 = float64(iCcyBase) / fScale
var iCcyAmt1 int64 = int64(fCcyBase * fScale)
var iCcyAmt2 int64 = int64((fCcyBase + fRound) * fScale)
iCcyTotBase += iCcyBase
iCcyTot1 += iCcyAmt1
iCcyTot2 += iCcyAmt2
}
var iCcyDiff1 int64 = iCcyTot1 - iCcyTotBase
var iCcyDiff2 int64 = iCcyTot2 - iCcyTotBase
fmt.Printf("\nDiff without rounding = %d\n", iCcyDiff1)
fmt.Printf("Diff with rounding = %d\n", iCcyDiff2)
}
答案 0 :(得分:3)
你必须围绕(正如你所做的那样)。而且 - 你已经提到过 - 浮点数的性质使得这很困难,而且由于舍入错误你必须处理一些错误的十进制结果。 AFAIK,golang没有舍入功能,因此您需要实现它。我已经看过this gist推文/提到了几次......它似乎已经出现在这个thread中。
func RoundViaFloat(x float64, prec int) float64 {
var rounder float64
pow := math.Pow(10, float64(prec))
intermed := x * pow
_, frac := math.Modf(intermed)
intermed += .5
x = .5
if frac < 0.0 {
x=-.5
intermed -=1
}
if frac >= x {
rounder = math.Ceil(intermed)
} else {
rounder = math.Floor(intermed)
}
return rounder / pow
}
一些笔记/资源(对于稍后出现的其他笔记):
通过int64()
小数部分转换浮点数时
将被丢弃。
打印浮点时,请使用正确的golang format。例如fmt.Printf("%.20f * %.20f as float = %.20f\n", fVal1, f100, fResult)
使用整数来赚钱。通过使用便士(和整数数学以避免舍入)并除以100来避免所有这一切。即使用足够大的固定大小整数或math / big.Int并避免浮点运算。
注意:您可以使用strconv round (OP希望避免 此):
func RoundFloat(x float64, prec int) float64 {
frep := strconv.FormatFloat(x, 'g', prec, 64)
f, _ := strconv.ParseFloat(frep, 64)
return f
}
答案 1 :(得分:1)
这里的问题是,无法存储您在float中给出的值的精确表示。你正在做的Printf()
是误导性的,它为你的价值四舍五入。试试这个:
package main
import "fmt"
func main() {
a := 19.08
b := 100.0
c := a * b
fmt.Printf("%.20f * %.20f as float = %.20f\n", a, b, c)
}
给出了:
19.07999999999999829470 * 100.00000000000000000000 as float = 1907.99999999999977262632
现在int64()
可能只是切断了数字的非整数部分。
要了解有关浮点数的更多信息,请参阅floating-point-gui.de。
=&GT; 永远不要尝试以浮点数存储精确值
答案 2 :(得分:0)
我开发了一个基于int64的十进制类,用于处理浮点数,字符串解析,JSON等的资金。
它将金额存储为64位整数美分。可以通过浮动轻松创建或转换回浮动。
也可以在DB中存储。
https://github.com/strongo/decimal
package example
import "github.com/strongo/decimal"
func Example() {
var amount decimal.Decimal64p2; print(amount) // 0
amount = decimal.NewDecimal64p2(0, 43); print(amount) // 0.43
amount = decimal.NewDecimal64p2(1, 43); print(amount) // 1.43
amount = decimal.NewDecimal64p2FromFloat64(23.100001); print(amount) // 23.10
amount, _ = decimal.ParseDecimal64p2("2.34"); print(amount) // 2.34
amount, _ = decimal.ParseDecimal64p2("-3.42"); print(amount) // -3.42
}
适用于我的债务跟踪器应用https://debtstracker.io/