【问题标题】:What is the best way to convert a currency from float to integer in Go?在 Go 中将货币从浮点数转换为整数的最佳方法是什么?
【发布时间】:2013-10-08 02:37:44
【问题描述】:

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

【问题讨论】:

  • 看看“数学/大”?这可能就是你想要的。 (即使我仍然没有完全理解你的问题)
  • 我也不明白你的问题。浮点操作中存在 no 舍入错误,因为出现不可预知的错误。写下您期望从“float to int”转换中获得的数学属性。根据您的要求,您将不得不使用 big.Rat 或可能坚持使用浮动处理。
  • 看看blog.stata.com/tag/precision/#section7 。它提到了几件事: 1. “2011 年美国的赤字为 1.5 万亿美元。以浮动形式存储,这个数额的(最大)误差为 ±2-24*1.5e+12 = ±89,406.97 美元。” 2、当需要精度时,“货币数据应转换为整数(便士)并存储为整数”。显然这是美国特有的,但应该适用于大多数货币。
  • 我用另一个例子扩展了我的问题,以说明我想要实现的目标。
  • 实际上,我永远不会将货币数据存储在浮点数中。我总是为此使用定点算术。例如,如果 1 美分的精度就足够了,我会将 123 美元存储为 12300(美分)。使用浮点运算给你带来麻烦。

标签: go


【解决方案1】:

你必须四舍五入(正如你所做的那样)。而且-正如您所暗示的-浮点的性质使这变得困难,并且由于舍入错误,您必须处理一些糟糕的十进制结果。 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
}

【讨论】:

  • 谢谢,我会看看那个信息。
  • 感谢您提供所需的信息,即“通过 int64() 转换浮点数时,小数部分将被丢弃。”基于该信息 AFAIK 在转换货币时(允许不同的货币格式,也允许负数)EG:“var fAmt float64 = 19.08; fmt.Printf("%f rounded = %d", fAmt, int64((fAmt*float64(100.0))+float64(0.5)))"
  • 没问题。我很高兴你拨通了它。
【解决方案2】:

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

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

【讨论】:

  • 我不想在浮点数中存储确切的数字。我确实了解花车是如何工作的。但是,如果存储在浮点数中的数字只是简单地从双精度数、小数或货币转换而来,那么从浮点数转换为整数是可能的,并且基本上所有想要做的就是将它们转换回来。当尝试使用这些浮点数进行计算时,就无法转换回来。在我的示例中,我通过添加 0.004 来做到这一点。我展示的示例使用 1000 万次浮点迭代来实现。我只想从例如知道最好的方法。一个关系型数据库。
  • @BrianOh 也许你的问题不够清楚。如果您可以将其简化为一些小问题,则可以考虑删除问题中的文本并重新措辞。我相信你可以在这里得到很多有用的答案,因为有很多专家在场。
  • 好的,谢谢。我发布比可能需要的更多细节的原因是因为我之前被告知总是发布一个详细的示例。也许我应该保持简短,这样人们就不必阅读稍微冗长的解释。每个人对如何提出问题都有不同的看法,不同的问题需要不同的呈现方式。然而,我确实认为这将是一个已知问题。
【解决方案3】:

我开发了一个基于 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/

【讨论】:

    猜你喜欢
    • 2010-09-12
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    相关资源
    最近更新 更多