将未知接口转换为float64在Golang中。

huangapple go评论68阅读模式
英文:

Converting unknown interface to float64 in Golang

问题

所以我收到一个interface{}类型的值,但我想以任何可能的方式将其转换为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 ... // 其他整数类型
    } 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")
    }
}

但我觉得我可能走错了方向,有没有更好的方法来做到这一点?

英文:

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?

答案1

得分: 29

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
// ...其他情况...
default:
        return math.NaN(), errors.New("getFloat: unknown value is of incompatible type")
}

case float64:类似于你的if i, ok := unk.(float64); ok { ... }。该情况下的代码可以将float64作为i访问。尽管没有大括号,但case就像块一样:在每个case下,i的类型是不同的,并且没有C风格的穿透。

此外,请注意,将大于253int64转换为float64时会四舍五入,因此如果你将float64视为“通用”数字类型,请考虑其限制。

在Playground上有一个示例,网址是http://play.golang.org/p/EVmv2ibI_j。


Dave C提到,如果使用reflect,你可以避免编写单独的情况,他的答案中有代码,甚至处理了适合类型的命名类型和指针。他还提到处理字符串和可转换为字符串的类型。在进行了一个简单的测试后,比较了以下选项:

  • reflect版本传递给int,每秒约可进行1300万次转换;除非你要转换数百万个项目,否则不会注意到开销。
  • 你可以编写一个switch来处理一些常见类型,然后回退到reflect至少在我下面的简单测试中,它的速度约为5000万次转换/秒,并且分配的内存较少,可能只有interface{}值而没有reflect.Value
  • 仅对数字类型进行switch会失去一些灵活性,但可以避免分配,因为编译器可以通过逃逸分析证明在之后不需要保留任何分配的内容。

也就是说,如果你需要调整足够多以关心这些差异,那么你可能应该在你的代码环境中运行自己的测试。例如,分配的成本可能因应用程序的总活动数据大小、GC设置(如GOGC)以及每个集合所需的时间而有所不同,而且你的代码可能允许/阻止不同的优化(内联等)。

以下是代码,也可以在Playground上找到:https://play.golang.org/p/v-QrbeOWtz

package main

/* 要实际运行计时,你需要从你的计算机上运行,而不是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
*/

希望对你有所帮助!

英文:

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(&quot;getFloat: unknown value is of incompatible type&quot;)
}

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 int64s (over 2<sup>53</sup>) 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:

  • Passing the reflect version an int gets me about 13 million conversions a second; the overhead isn't noticeable unless you're converting millions of items.
  • You can write a switch to handle some common types then fall back to 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.
  • A 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 (
&quot;errors&quot;
&quot;fmt&quot;
&quot;math&quot;
&quot;reflect&quot;
&quot;runtime&quot;
&quot;strconv&quot;
&quot;time&quot;
)
var floatType = reflect.TypeOf(float64(0))
var stringType = reflect.TypeOf(&quot;&quot;)
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(&quot;Can&#39;t convert %v to float64&quot;, 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(&quot;cannot convert %v to float64&quot;, v.Type())
}
fv := v.Convert(floatType)
return fv.Float(), nil
}
var errUnexpectedType = errors.New(&quot;Non-numeric type could not be converted to float&quot;)
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(&amp;m1)
start := time.Now()
for i := 0; i &lt; 1e6; i++ {
getFloatReflectOnly(i)
}
fmt.Println(&quot;Reflect-only, 1e6 runs:&quot;)
fmt.Println(&quot;Wall time:&quot;, time.Now().Sub(start))
runtime.ReadMemStats(&amp;m2)
fmt.Println(&quot;Bytes allocated:&quot;, m2.TotalAlloc-m1.TotalAlloc)
runtime.ReadMemStats(&amp;m1)
start = time.Now()
for i := 0; i &lt; 1e6; i++ {
getFloat(i)
}
fmt.Println(&quot;\nReflect-and-switch, 1e6 runs:&quot;)
fmt.Println(&quot;Wall time:&quot;, time.Since(start))
runtime.ReadMemStats(&amp;m2)
fmt.Println(&quot;Bytes allocated:&quot;, m2.TotalAlloc-m1.TotalAlloc)
runtime.ReadMemStats(&amp;m1)
start = time.Now()
for i := 0; i &lt; 1e6; i++ {
getFloatSwitchOnly(i)
}
fmt.Println(&quot;\nSwitch only, 1e6 runs:&quot;)
fmt.Println(&quot;Wall time:&quot;, time.Since(start))
runtime.ReadMemStats(&amp;m2)
fmt.Println(&quot;Bytes allocated:&quot;, 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
*/

答案2

得分: 13

你可以使用reflect包来实现这个功能:

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("无法将%v转换为float64", v.Type())
    }
    fv := v.Convert(floatType)
    return fv.Float(), nil
}

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

英文:

You can use the reflect package for this:

import &quot;reflect&quot;
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(&quot;cannot convert %v to float64&quot;, v.Type())
}
fv := v.Convert(floatType)
return fv.Float(), nil
}

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

答案3

得分: 0

我正在使用这个包。

> Cast是一个用于在一致且简单的方式中转换不同Go类型的库。
> Cast提供了简单的函数,可以轻松地将数字转换为字符串,将接口转换为布尔值等。当存在明显的转换时,Cast会智能地进行转换。它不会尝试猜测你的意图,例如,只有在字符串表示为整数(如“8”)时,才能将字符串转换为整数。Cast是为Hugo开发的,Hugo是一个使用YAML、TOML或JSON作为元数据的网站引擎。

https://github.com/spf13/cast

英文:

i am using from this package

> Cast is a library to convert between different go types in a consistent and easy way.
> Cast provides simple functions to easily convert a number to a string, an interface into a bool, etc. Cast does this intelligently when an obvious conversion is possible. It doesn’t make any attempts to guess what you meant, for example you can only convert a string to an int when it is a string representation of an int such as “8”. Cast was developed for use in Hugo, a website engine which uses YAML, TOML or JSON for meta data

https://github.com/spf13/cast

huangapple
  • 本文由 发表于 2013年12月25日 07:23:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/20767724.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定