So I am receiving an interface{}, but I want to in any way possible convert it to a float64 or return an error if not possible.
Here's what I'm doing:
func getFloat(unk interface{}) (float64, error) {
if v_flt, ok := unk.(float64); ok {
return v_flt, nil
} else if v_int, ok := unk.(int); ok {
return float64(v_int), nil
} else if v_int, ok := unk.(int16); ok {
return float64(v_int), nil
} else ... // other integer types
} else if v_str, ok := unk.(string); ok {
v_flt, err := strconv.ParseFloat(v_str, 64)
if err == nil {
return v_flt, nil
}
return math.NaN(), err
} else if unk == nil {
return math.NaN(), errors.New("getFloat: unknown value is nil")
} else {
return math.NaN(), errors.New("getFloat: unknown value is of incompatible type")
}
}
But I feel like I'm going about it the wrong way, is there a better way to do this?
Dave C has a good answer using reflect
, and I'll compare that to type-by-type code below. First, to do what you were already doing more concisely, you can use the type switch:
switch i := unk.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
// ...other cases...
default:
return math.NaN(), errors.New("getFloat: unknown value is of incompatible type")
}
The case float64:
is like your if i, ok := unk.(float64); ok { ... }
. Code for that case can access the float64
as i
. Despite the lack of braces the cases act like blocks: i
's type is different under each case
and there is no C-style fallthrough.
Also, note large int64
s (over 253) will be rounded when converted to float64
, so if you're thinking of float64
as a "universal" number type, take its limitations into account.
An example of that is in the Playground at http://play.golang.org/p/EVmv2ibI_j.
Dave C mentions you can avoid writing out individual cases if you use reflect
; his answer has code, and even handles named types and pointers to suitable types. He also mentions handling strings and types convertible to them. After doing a naïve test comparing options:
reflect
version an int gets me about 13 million conversions a second; the overhead isn't noticeable unless you're converting millions of items. reflect
; at least in my simple test below it goes at ~50M conversion/s and allocates less, presumably only the interface{}
value without a reflect.Value
.switch
only over number types loses some flexibility, but can avoid allocation since the compiler can prove through escape analysis that nothing needs to remain allocated after.That said, if you need to tune enough that you care about these differences, you should probably run your own test in the context of your code. For example, the allocations can have varying costs depending on your app's total live data size, GC settings like GOGC, and how long each collection takes, and your code might allow/prevent different optimizations (inlining, etc.) than my sample.
The code is on the Playground and below:
package main
/* To actually run the timings, you need to run this from your machine, not the Playground */
import (
"errors"
"fmt"
"math"
"reflect"
"runtime"
"strconv"
"time"
)
var floatType = reflect.TypeOf(float64(0))
var stringType = reflect.TypeOf("")
func getFloat(unk interface{}) (float64, error) {
switch i := unk.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
case int32:
return float64(i), nil
case int:
return float64(i), nil
case uint64:
return float64(i), nil
case uint32:
return float64(i), nil
case uint:
return float64(i), nil
case string:
return strconv.ParseFloat(i, 64)
default:
v := reflect.ValueOf(unk)
v = reflect.Indirect(v)
if v.Type().ConvertibleTo(floatType) {
fv := v.Convert(floatType)
return fv.Float(), nil
} else if v.Type().ConvertibleTo(stringType) {
sv := v.Convert(stringType)
s := sv.String()
return strconv.ParseFloat(s, 64)
} else {
return math.NaN(), fmt.Errorf("Can't convert %v to float64", v.Type())
}
}
}
func getFloatReflectOnly(unk interface{}) (float64, error) {
v := reflect.ValueOf(unk)
v = reflect.Indirect(v)
if !v.Type().ConvertibleTo(floatType) {
return math.NaN(), fmt.Errorf("cannot convert %v to float64", v.Type())
}
fv := v.Convert(floatType)
return fv.Float(), nil
}
var errUnexpectedType = errors.New("Non-numeric type could not be converted to float")
func getFloatSwitchOnly(unk interface{}) (float64, error) {
switch i := unk.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
case int32:
return float64(i), nil
case int:
return float64(i), nil
case uint64:
return float64(i), nil
case uint32:
return float64(i), nil
case uint:
return float64(i), nil
default:
return math.NaN(), errUnexpectedType
}
}
func main() {
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
start := time.Now()
for i := 0; i < 1e6; i++ {
getFloatReflectOnly(i)
}
fmt.Println("Reflect-only, 1e6 runs:")
fmt.Println("Wall time:", time.Now().Sub(start))
runtime.ReadMemStats(&m2)
fmt.Println("Bytes allocated:", m2.TotalAlloc-m1.TotalAlloc)
runtime.ReadMemStats(&m1)
start = time.Now()
for i := 0; i < 1e6; i++ {
getFloat(i)
}
fmt.Println("\nReflect-and-switch, 1e6 runs:")
fmt.Println("Wall time:", time.Since(start))
runtime.ReadMemStats(&m2)
fmt.Println("Bytes allocated:", m2.TotalAlloc-m1.TotalAlloc)
runtime.ReadMemStats(&m1)
start = time.Now()
for i := 0; i < 1e6; i++ {
getFloatSwitchOnly(i)
}
fmt.Println("\nSwitch only, 1e6 runs:")
fmt.Println("Wall time:", time.Since(start))
runtime.ReadMemStats(&m2)
fmt.Println("Bytes allocated:", m2.TotalAlloc-m1.TotalAlloc)
}
/*
Reflect-only, 1e6 runs:
Wall time: 79.853582ms
Bytes allocated: 16002696
Reflect-and-switch, 1e6 runs:
Wall time: 20.921548ms
Bytes allocated: 8000776
Switch only, 1e6 runs:
Wall time: 3.766178ms
Bytes allocated: 32
*/