diff --git a/jsonapi.go b/jsonapi.go index 1741894e5..db2d94b0c 100644 --- a/jsonapi.go +++ b/jsonapi.go @@ -742,3 +742,36 @@ func IsValidIdType(id interface{}) bool { return false } } + +// JSONToAmount Safely converts a floating point value to an int. +// Clearly not all floating point numbers can be converted to ints (there +// is no one-to-one mapping, but bitcoin's json api returns most numbers as +// floats which are not safe to use when handling money. Since bitcoins can +// only be divided in a limited way, this methods works for the amounts returned +// by the json api. It is not for general use. +// This follows the method described at: +// https://en.bitcoin.it/wiki/Proper_Money_Handling_%28JSON-RPC%29 +func JSONToAmount(jsonAmount float64) (int64, error) { + var amount int64 + var err error + if jsonAmount > 1.797693134862315708145274237317043567981e+300 { + err := fmt.Errorf("Error %d is too large to convert", jsonAmount) + return amount, err + } + if jsonAmount < -1.797693134862315708145274237317043567981e+300 { + err := fmt.Errorf("Error %d is too small to convert", jsonAmount) + return amount, err + } + tempVal := 1e8 * jsonAmount + // So we round properly. float won't == 0 and if it did, that + // would be converted fine anyway. + if tempVal < 0 { + tempVal = tempVal - 0.5 + } + if tempVal > 0 { + tempVal = tempVal + 0.5 + } + // Then just rely on the integer truncating + amount = int64(tempVal) + return amount, err +} diff --git a/jsonapi_test.go b/jsonapi_test.go index 5fd5dcad2..74f7bc7b1 100644 --- a/jsonapi_test.go +++ b/jsonapi_test.go @@ -256,7 +256,7 @@ var idtests = []struct { } // TestIsValidIdType tests that IsValidIdType allows (and disallows the correct -// types. +// types). func TestIsValidIdType(t *testing.T) { for _, tt := range idtests { res := btcjson.IsValidIdType(tt.testId[0]) @@ -266,3 +266,34 @@ func TestIsValidIdType(t *testing.T) { } return } + +var floattests = []struct { + in float64 + out int64 + pass bool +}{ + {1.0, 100000000, true}, + {-1.0, -100000000, true}, + {0.0, 0, true}, + {0.00000001, 1, true}, + {-0.00000001, -1, true}, + {-1.0e307, 0, false}, + {1.0e307, 0, false}, +} + +// TestJSONtoAmount tests that JSONtoAmount returns the proper values. +func TestJSONtoAmount(t *testing.T) { + for _, tt := range floattests { + res, err := btcjson.JSONToAmount(tt.in) + if tt.pass { + if res != tt.out || err != nil { + t.Errorf("Should not fail: ", tt.in) + } + } else { + if err == nil { + t.Errorf("Should not pass: ", tt.in) + } + } + } + return +} diff --git a/test_coverage.txt b/test_coverage.txt index 7203dd6fb..ecd72d1dd 100644 --- a/test_coverage.txt +++ b/test_coverage.txt @@ -1,5 +1,6 @@ github.com/conformal/btcjson/jsonapi.go CreateMessage 100.00% (310/310) +github.com/conformal/btcjson/jsonapi.go JSONToAmount 100.00% (15/15) github.com/conformal/btcjson/jsonfxns.go jsonRpcSend 100.00% (7/7) github.com/conformal/btcjson/jsonfxns.go MarshallAndSend 100.00% (7/7) github.com/conformal/btcjson/jsonfxns.go GetRaw 100.00% (6/6) @@ -7,5 +8,5 @@ github.com/conformal/btcjson/jsonapi.go jsonWithArgs 100.00% (5/5) github.com/conformal/btcjson/jsonapi.go IsValidIdType 100.00% (3/3) github.com/conformal/btcjson/jsonapi.go RpcCommand 66.67% (18/27) github.com/conformal/btcjson/jsonapi.go readResultCmd 40.00% (20/50) -github.com/conformal/btcjson --------------- 90.60% (376/415) +github.com/conformal/btcjson --------------- 90.93% (391/430)