Re: [go-nuts] converting from interface{} to numeric type

2,982 views
Skip to first unread message

Maxim Khitrov

unread,
Jun 4, 2012, 6:01:07 PM6/4/12
to Holger Knauer, golan...@googlegroups.com
switch v.(type) {
case int, int8, int16, int32, int64, ...:
...
}

On Mon, Jun 4, 2012 at 5:42 PM, Holger Knauer
<holger...@googlemail.com> wrote:
> I've written a function to be called from templates which is supposed to do
> number formatting according to a given language (to choose appropriate runes
> for the decimal separator and number grouping).
> Inside the template it's supposed to be invoked like this
> {{fmtDecimal "en" 2 .SomeValueOfPotentiallyAnyNumericType}}
>
> The fmtDecimal function internally calls a function with this signature to
> do the real string formatting:
> func Number(amount float64, decimals int, l i18n.Language) string
>
> What I came up with for fmtDecimal looks like this:
>
> func fmtDecimal(args ...interface{}) string {
> if len(args) != 3 {
> return "fmtDecimal expects three arguments: language, decimals, value"
> }
> if l, ok := args[0].(string); ok {
> lang := i18n.Language(l)
> if decimals, ok := args[1].(int); ok {
>
> if arg, ok := args[2].(int); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(int8); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(int16); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(int32); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(int64); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(uint); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(uint8); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(uint16); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(uint32); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(uint64); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(float32); ok {
> return format.Number(float64(arg), decimals, lang)
> }
> if arg, ok := args[2].(float64); ok {
> return format.Number(arg, decimals, lang)
> }
> return "fmtNumber/fmtDecimal expects a numerical value to format"
> }
> return "fmtNumber/fmtDecimal expect second argument to be of type int"
>
> }
> return "fmtNumber/fmtDecimal expect first argument to be a string"
> }
>
>
> I wonder if there's any more elegant, less verbose way of converting an
> interface{} supposed to represent one of the available numerical types to
> the float64 type needed?
>
> Thanks,
> Holger

Rob 'Commander' Pike

unread,
Jun 4, 2012, 6:03:24 PM6/4/12
to Holger Knauer, golan...@googlegroups.com

Kevin Ballard

unread,
Jun 4, 2012, 6:11:48 PM6/4/12
to Maxim Khitrov, Holger Knauer, golan...@googlegroups.com
You can't compress all the types together like that, because you need to cast the actual value, but you can still use a type switch.

switch n := v.(type) {
case int:
return format.Number(float64(n), decimals, lang)
case int8:
return format.Number(float64(n), decimals, lang)
// …

Alternatively you could use reflect and use only 3 cases that way

rv := reflect.ValueOf(arg)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return format.Number(float64(reflect.Int()), decimals, lang)
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return format.Number(float64(reflect.Uint()), decimals, lang)
case reflect.Float32, reflect.Float64:
return format.Number(rv.Float(), decimals, lang)
default:
return "fmtNumber/fmtDecimal expects a numerical value to format"
}

Incidentally, why are you overloading the return value to be either the correct result or an error string? You should probably be returning (string, error) instead.

-Kevin

Maxim Khitrov

unread,
Jun 4, 2012, 6:23:35 PM6/4/12
to Kevin Ballard, Holger Knauer, golan...@googlegroups.com
This seems to work fine:

http://play.golang.org/p/vDeMKLEM_D

Maxim Khitrov

unread,
Jun 4, 2012, 6:26:28 PM6/4/12
to Kevin Ballard, Holger Knauer, golan...@googlegroups.com
Nevermind, I see what you mean.

Holger Knauer

unread,
Jun 4, 2012, 6:39:07 PM6/4/12
to golan...@googlegroups.com, Maxim Khitrov, Holger Knauer
Thanks a bunch, that really helped.

I find the solution via reflection to be the most elegant, but will actually use the type switch in my code, as I suspect reflection to have a greater performance overhead and the type switch is really nicely readable already.

 
Incidentally, why are you overloading the return value to be either the correct result or an error string? You should probably be returning (string, error) instead.

 That has simple pragmatic reasons. The function will never be called from anywhere else but templates (I'd call func Number directly everywhere else) and I prefer template execution not be interrupted. The error message is long enough to easily spot it once it appears instead of a number during the first test of the template.

Kevin Ballard

unread,
Jun 4, 2012, 6:48:57 PM6/4/12
to Holger Knauer, golan...@googlegroups.com, Maxim Khitrov
On Monday, June 4, 2012 at 3:39 PM, Holger Knauer wrote:
Thanks a bunch, that really helped.

I find the solution via reflection to be the most elegant, but will actually use the type switch in my code, as I suspect reflection to have a greater performance overhead and the type switch is really nicely readable already.
Yeah I'd probably go with the type switch case myself. It feels kinda weird because the body of every case is identical, but in fact the generated code is different for the type conversion.
 
Incidentally, why are you overloading the return value to be either the correct result or an error string? You should probably be returning (string, error) instead.

 That has simple pragmatic reasons. The function will never be called from anywhere else but templates (I'd call func Number directly everywhere else) and I prefer template execution not be interrupted. The error message is long enough to easily spot it once it appears instead of a number during the first test of the template.
Why not just make it panic then? Rename it to mustFmtDecimal() or something.

-Kevin
Reply all
Reply to author
Forward
0 new messages