簡體   English   中英

在Golang中將未知接口轉換為float64

[英]Converting unknown interface to float64 in Golang

所以我收到一個接口{},但我想以任何方式將其轉換為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")
    }
}

但我覺得我的方式錯了,是否有更好的方法呢?

Dave C使用reflect有一個很好的答案,我將它與下面的類型代碼進行比較。 首先,要做到你已經做得更簡潔的事情,你可以使用類型開關

    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 { ... } if i, ok := unk.(float64); ok { ... } 該案例的代碼可以作為i訪問float64 盡管沒有支撐,但是這些情況就像塊一樣:在每種casei的類型都不同,並且沒有C風格的漏洞。

另外,注意大型int64 (超過2 53在轉換為float64將被舍入 ,因此如果您將float64視為“通用”數字類型,請考慮其限制。

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


Dave C制作提到你避免,如果你使用寫出個別情況reflect ; 他的答案有代碼,甚至處理命名類型和指向合適類型的指針。 他還提到了處理字符串和可轉換為它們的類型。 做了一個天真的測試比較選項:

  • reflect版本傳遞給int每秒可以獲得大約1300萬次轉換; 除非您轉換數百萬件物品,否則開銷不會明顯。
  • 你可以編寫一個開關來處理一些常見的類型,然后再回過頭來reflect ; 至少在我下面的簡單測試中,它的轉換速度約為50M / s並分配較少,大概只有interface{}值而沒有reflect.Value
  • 只有數字類型的switch會失去一些靈活性,但可以避免分配,因為編譯器可以通過轉義分析證明之后不需要保留任何分配。

也就是說,如果你需要足夠的調整以關注這些差異,你應該在代碼的上下文中運行自己的測試。 例如,分配的成本可能會有所不同,具體取決於應用的總實時數據大小,GC設置(如GOGC)以及每個集合所花費的時間,並且您的代碼可能允許/阻止不同的優化(內聯等)。

代碼在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
*/

您可以使用反射包:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM