2

Goで通貨を浮動小数点から整数に変換する最良の方法は何ですか?

------------- 質問の説明を追加 ----------

私の質問を少し拡張するために、以下は、0.004 の丸め値 (2 桁の通貨の場合) を追加することによって解決される「問題」として私が見ているものの例です。

私の知る限り、外部値はたとえば次のように保存されます。RDBMS の decimal、double、currency は、float として Go に「インポート」する必要があります。計算を実行するには、整数に変換するか、少なくともそれが 1 つの方法です。

以下の例では、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 への通貨変換のための次のコードは、これら 2 つの代替コード行を中心に展開します。

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

2 番目の選択肢の「fRound」で使用されている丸めは「0.004」です。

このテスト プログラム (1000 万回の反復) を実行すると、「0.004」が追加されていない場合、一貫して約 588,000 セントの丸め誤差が発生し、丸め数値を使用した差はゼロになります。

これはこの状況を処理する安全な方法ですか (文字列を使用したくありません)、またはより良い方法はありますか?

テストプログラム:

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

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()はおそらく、数値の非整数部分を切り捨てているだけです。

浮動小数点数の詳細については、たとえば float-point-gui.de を参照してください。

=>浮動小数点数に正確な値を格納しようとしないでください

于 2013-10-08T11:34:01.483 に答える