2

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

------------- 增加了问题的解释 ----------

为了稍微扩展我的问题,以下是我认为通过添加 0.004 的舍入值(对于 2 位十进制货币)解决的“问题”的示例。

据我所知,外部值存储为例如。RDBMS 中的十进制、双精度、货币需要作为浮点数“导入”到 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)
}
4

3 回答 3

3

你必须四舍五入(就像你所做的那样)。而且-正如您所暗示的-浮点的性质使这变得困难,并且由于舍入错误,您必须处理一些糟糕的十进制结果。AFAIK,golang没有舍入功能,因此您需要实现它。我已经在推特上看到过这个要点/提到了几次......而且它似乎是在这个线程中诞生的。

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 格式。例如fmt.Printf("%.20f * %.20f as float = %.20f\n", fVal1, f100, fResult)

  • 使用整数来赚钱。通过使用便士(和整数数学以避免四舍五入)并除以 100 来避免所有这些。即使用足够大的固定大小整数或 math/big.Int 并避免浮点运算。

注意:您也可以使用 strconv 进行入(OP 希望避免这种情况):

func RoundFloat(x float64, prec int) float64 {
    frep := strconv.FormatFloat(x, 'g', prec, 64)
    f, _ := strconv.ParseFloat(frep, 64)
    return f
}
于 2013-10-08T17:34:31.987 回答
1

这里的问题是,不可能将您给出的值的精确表示存储在浮点数中。你正在做的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示例。

=>永远不要尝试将精确值存储在浮点数中

于 2013-10-08T11:34:01.483 回答
0

我开发了一个基于 int64 的十进制类来处理处理浮点数、字符串解析、JSON 等的钱。

它将金额存储为 64 位整数美分。可以很容易地从浮点数创建或转换回浮点数。

也便于存储在数据库中。

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/

于 2017-04-05T08:56:39.957 回答