dongtiannan0367 2013-10-08 02:37
浏览 114
已采纳

在Go中将货币从float转换为integer的最佳方法是什么?

What is the best way to convert a currency from float to integer in Go?

------------- Added Explanation of question ----------

To expand my question a little, the following is an example of what I see as the “problem” solved by adding a rounding value of 0.004 (for 2-decimal currencies).

As far as I know, external values stored as eg. decimal, double, currency in an RDBMS need to be “imported” to Go as a float. In order to perform calculations, they then need to be converted to integer, or at least that is one method.

In the example below, fVal1 emulates an amount imported from a database. To perform calculations on it, I want to convert it to an integer. The easiest way to do that appears to me to be to add a rounding figure as illustrated.

Example code:

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
", fVal1, f100, fResult)
var iResult1 int64 = int64(fResult)
fmt.Printf("as Integer unrounded = %d
", iResult1)
var iResult2 int64 = int64(fResult + fRound)
fmt.Printf("as Integer rounded = %d
", iResult2)

Console output:

19.080000 * 100.000000 as float = 1908.000000
as Integer unrounded = 1907
as Integer rounded = 1908

----------- end of addition to question -----------

I’ve implemented a small package to handle multiple currencies in Go, and essentially it revolves around the following bit of code (below).

When I discovered that there were rounding errors in converting between floats and integers, the first rounding factor that I tried was 0.004 (for 2 decimals), and it appeared to work OK.

Basically, the following bit of code for currency conversion from float to int revolves around these two alternative lines of code :

  var iCcyAmt1 int64 = int64(fCcyBase * fScale)
  var iCcyAmt2 int64 = int64((fCcyBase + fRound) * fScale)

The rounding being used in “fRound” in the second alternative is “0.004”.

Running this test program (10 million iterations) results in a consistent rounding error of about 588,000 cents where the “0.004” is not added, and zero difference using the rounding figure.

Is this a safe way to handle this situation (I don’t want to use strings), or is there a better way?

Test Program:

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("
Testing 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("
Diff without rounding = %d
", iCcyDiff1)
    fmt.Printf("Diff with rounding = %d
", iCcyDiff2)
}
  • 写回答

3条回答 默认 最新

  • doufangmu9087 2013-10-08 17:34
    关注

    You have to round (as you've done). And -as you've alluded to- the nature of floating points make this difficult, and you have to deal with some bad decimal results due to rounding errors. AFAIK, golang doesn't have a rounding function, so you'll need to implement it. I've seen this gist tweeted/mentioned a few times... and it seems to have been born in this 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
    }
    

    A couple of notes/resources (for others that show up later):

    • When converting a floating point via int64() the fractional part will be discarded.

    • When printing a floating point use the correct golang format. e.g. fmt.Printf("%.20f * %.20f as float = %.20f ", fVal1, f100, fResult)

    • Use an Integer for money. Avoid all of this by using pennies (and integer math in order to avoid the rounding) and divide by 100. i.e. use a large enough fixed size integer or math/big.Int and avoid the floating point arithmetic.

    Note: You can, alternatively, round using strconv (the OP wanted to avoid this):

    func RoundFloat(x float64, prec int) float64 {
        frep := strconv.FormatFloat(x, 'g', prec, 64)
        f, _ := strconv.ParseFloat(frep, 64)
        return f
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥15 (标签-UDP|关键词-client)
  • ¥15 关于库卡officelite无法与虚拟机通讯的问题
  • ¥15 qgcomp混合物线性模型分析的代码出现错误:Model aliasing occurred
  • ¥100 已有python代码,要求做成可执行程序,程序设计内容不多
  • ¥15 目标检测项目无法读取视频
  • ¥15 GEO datasets中基因芯片数据仅仅提供了normalized signal如何进行差异分析
  • ¥100 求采集电商背景音乐的方法
  • ¥15 数学建模竞赛求指导帮助
  • ¥15 STM32控制MAX7219问题求解答
  • ¥20 在本地部署CHATRWKV时遇到了AttributeError: 'str' object has no attribute 'requires_grad'