在Go中将货币从浮点数转换为整数的最佳方法是什么?

时间:2013-10-08 02:37:45

标签: go

在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)
}

3 个答案:

答案 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/