2013-12-24 2 views
4

Итак, я получаю интерфейс {}, но я хочу каким-либо образом преобразовать его в float64 или вернуть ошибку, если это невозможно.Преобразование неизвестного интерфейса в float64 в Golang

Вот что я делаю:

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

Но я чувствую, что я собираюсь об этом неправильном пути, есть лучший способ сделать это?

ответ

12

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.


Dave C упоминает вас может избегать выписывая отдельные случаи, если вы используете reflect; его ответ имеет код и даже обрабатывает названные типы и указатели на подходящие типы. Он также упоминает, как обрабатывать строки и типы, конвертируемые в них. После выбора наивных вариантов тестирования:

  • Передача reflect версии a int дает мне около 3 миллионов конверсий в секунду; накладные расходы не заметны, если вы не конвертируете миллионы предметов.
  • Вы можете написать переключатель для обработки некоторых распространенных типов, а затем вернуться к reflect; это немного быстрее для обычных типов (9 м/с), но сохраняет гибкость кода reflect.

Код 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(37) 
    } 
    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(37) 
    } 
    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(37) 
    } 
    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: 297.335642ms 
Bytes allocated: 16006648 

Reflect-and-switch, 1e6 runs: 
Wall time: 110.40057ms 
Bytes allocated: 8001144 

Switch only, 1e6 runs: 
Wall time: 15.069157ms 
Bytes allocated: 64 
*/ 
+0

Спасибо, я был не зная, что вы можете переключаться с типами – Rhaokiel

+0

У позора не может быть только один случай для вариантов int 'case int, int32, int64: return float64 (t)' –

+0

Различные типы потенциально могут быть скомпилированы для разных машинных кодов, поэтому я могу понять, почему они это сделали. – twotwotwo

6

Вы можете использовать пакет поразмышлять для этого:

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 
} 

Runnable в Go Playground: http://play.golang.org/p/FRM21HRq4o

Смежные вопросы