【问题标题】:Converting unknown interface to float64 in Golang在 Golang 中将未知接口转换为 float64
【发布时间】:2014-01-13 02:23:20
【问题描述】:

所以我收到了一个接口{},但我想以任何可能的方式将其转换为 float64 或在不可能的情况下返回错误。

这就是我正在做的事情:

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

但我觉得我做错了,有更好的方法吗?

【问题讨论】:

    标签: floating-point go typeconverter


    【解决方案1】:

    Dave C 使用reflect 给出了一个很好的答案,我将在下面将其与逐个类型的代码进行比较。首先,要更简洁地执行您已经在执行的操作,您可以使用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")
        }
    

    case float64: 就像您的 if i, ok := unk.(float64); ok { ... }。该案例的代码可以访问float64 作为i。尽管没有大括号,但情况就像块一样:i 的类型在每个 case 下都不同,并且没有 C 样式的失败。

    另外,请注意 large int64s (over 253) will be rounded 转换为 float64,因此如果您将 float64 视为“通用”数字类型,请考虑其局限性。

    http://play.golang.org/p/EVmv2ibI_j 的 Playground 中就是一个例子。


    Dave C 提到,如果您使用 reflect,您可以避免写出个别案例;他的答案有代码,甚至可以处理命名类型和指向合适类型的指针。他还提到了处理字符串和可转换为它们的类型。在进行了比较选项的简单测试之后:

    • 传递 reflect 版本的 int 可以让我每秒进行大约 1300 万次转换;除非您要转换数百万个项目,否则开销并不明显。
    • 您可以编写一个开关来处理一些常见类型,然后回退到reflect至少在我下面的简单测试中它以大约 50M 转换/秒的速度进行并且分配更少,大概只有 interface{} 没有reflect.Value 的值。
    • 仅在数字类型上的switch 会失去一些灵活性,但可以避免分配,因为编译器可以通过escape analysis 证明之后无需再分配任何东西。

    也就是说,如果您需要进行足够的调整以关心这些差异,您可能应该在代码的上下文中运行自己的测试。例如,分配可能会产生不同的成本,具体取决于您的应用程序的总实时数据大小、像 GOGC 这样的 GC 设置以及每次收集所需的时间,并且您的代码可能允许/阻止与我的示例不同的优化(内联等)。

    代码为on the Playground及以下:

    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
    */
    

    【讨论】:

    • 谢谢,我不知道你可以切换类型
    • 可耻的是不能只为 int 变体case int, int32, int64: return float64(t)提供一个案例@
    • 不同的类型可能会编译成不同的机器码,所以我明白他们为什么这样做了。
    • 请注意,枚举所有内置数字类型不仅会变得冗长且烦人,而且即使是最简单的自定义类型(例如type myFloat float64)也会失败。您应该使用反射的ConvertibleTo,(可能会额外尝试解析任何可转换为字符串的内容)。
    • @DaveC 更新了答案。反射成本在相对方面已经足够我认为仍然值得描述这些选项,但我试图澄清它仍然不会引起注意,除非你进行数百万次调用。
    【解决方案2】:

    您可以为此使用反射包:

    import "reflect"
    
    var floatType = reflect.TypeOf(float64(0))
    
    func getFloat(unk interface{}) (float64, error) {
        v := reflect.ValueOf(unk)
        v = reflect.Indirect(v)
        if !v.Type().ConvertibleTo(floatType) {
            return 0, fmt.Errorf("cannot convert %v to float64", v.Type())
        }
        fv := v.Convert(floatType)
        return fv.Float(), nil
    }
    

    可在 Go Playground 中运行:http://play.golang.org/p/FRM21HRq4o

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-04
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 2015-01-26
      相关资源
      最近更新 更多