diff --git a/cvt.go b/cvt.go index 7e758e9..848097d 100644 --- a/cvt.go +++ b/cvt.go @@ -142,3 +142,29 @@ func Int(v interface{}, def ...int) int { return 0 } + +// Float64 convert an interface to a float64 type, with default value +func Float64(v interface{}, def ...float64) float64 { + if v, err := Float64E(v); err == nil { + return v + } + + if len(def) > 0 { + return def[0] + } + + return 0 +} + +// Float32 convert an interface to a float32 type, with default value +func Float32(v interface{}, def ...float32) float32 { + if v, err := Float32E(v); err == nil { + return v + } + + if len(def) > 0 { + return def[0] + } + + return 0 +} diff --git a/cvt_test.go b/cvt_test.go index d1d5d12..28e142c 100644 --- a/cvt_test.go +++ b/cvt_test.go @@ -2,6 +2,7 @@ package cvt_test import ( "fmt" + "math" "testing" "github.com/stretchr/testify/assert" @@ -1051,3 +1052,192 @@ func TestInt_BaseLine(t *testing.T) { assert.Equal(t, tt.expect, v, msg) } } + +func TestFloat64_HasDefault(t *testing.T) { + tests := []struct { + input interface{} + def float64 + expect float64 + }{ + // supported value, def is not used, def != expect + {int(8), 1, 8}, + {int8(8), 1, 8}, + {int16(8), 1, 8}, + {int32(8), 1, 8}, + {int64(8), 1, 8}, + {uint(8), 1, 8}, + {uint8(8), 1, 8}, + {uint16(8), 1, 8}, + {uint32(8), 1, 8}, + {uint64(8), 1, 8}, + {float32(8.31), 1, 8.31}, + {float64(8.31), 1, 8.31}, + {"8", 2, 8}, + {"8.00", 2, 8}, + {"8.01", 2, 8.01}, + {int(-8), 1, -8}, + {int8(-8), 1, -8}, + {int16(-8), 1, -8}, + {int32(-8), 1, -8}, + {int64(-8), 1, -8}, + {float32(-8.31), 1, -8.31}, + {float64(-8.31), 1, -8.31}, + {int64(math.MaxInt64), 1, float64(math.MaxInt64)}, + {uint64(math.MaxUint64), 1, float64(math.MaxUint64)}, + {"-8", 1, -8}, + {"-8.01", 1, -8.01}, + {true, 2, 1}, + {false, 2, 0}, + {nil, 2, 0}, + {aliasTypeInt_0, 2, 0}, + {&aliasTypeInt_0, 2, 0}, + {aliasTypeInt_1, 2, 1}, + {&aliasTypeInt_1, 2, 1}, + {aliasTypeString_0, 2, 0}, + {&aliasTypeString_0, 2, 0}, + {aliasTypeString_1, 2, 1}, + {&aliasTypeString_1, 2, 1}, + {aliasTypeString_8d15, 2, 8.15}, + {&aliasTypeString_8d15, 2, 8.15}, + {aliasTypeString_8d15_minus, 1, -8.15}, + {&aliasTypeString_8d15_minus, 1, -8.15}, + + // unsupported value, def == expect + {"10a", 1.11, 1.11}, + {"a10a", 1.11, 1.11}, + {"8.01a", 1.11, 1.11}, + {"8.01 ", 1.11, 1.11}, + {"hello", 1.11, 1.11}, + {testing.T{}, 1.11, 1.11}, + {&testing.T{}, 1.11, 1.11}, + {[]int{}, 1.11, 1.11}, + {[]string{}, 1.11, 1.11}, + {[...]string{}, 1.11, 1.11}, + {map[int]string{}, 1.11, 1.11}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], def[%+v], expect[%+v]", i, tt.input, tt.def, tt.expect) + + v := cvt.Float64(tt.input, tt.def) + assert.Equal(t, tt.expect, v, msg) + } +} + +func TestFloat64_BaseLine(t *testing.T) { + tests := []struct { + input interface{} + expect float64 + }{ + {"8.01a", 0}, + {testing.T{}, 0}, + {&testing.T{}, 0}, + {[]int{}, 0}, + {[]int{1, 2, 3}, 0}, + {[]string{}, 0}, + {[]string{"a", "b", "c"}, 0}, + {[...]string{}, 0}, + {map[int]string{}, 0}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v]", i, tt.input, tt.expect) + + v := cvt.Float64(tt.input) + assert.Equal(t, tt.expect, v, msg) + } +} + +func TestFloat32_HasDefault(t *testing.T) { + tests := []struct { + input interface{} + def float32 + expect float32 + }{ + // supported value, def is not used, def != expect + {int(8), 1, 8}, + {int8(8), 1, 8}, + {int16(8), 1, 8}, + {int32(8), 1, 8}, + {int64(8), 1, 8}, + {uint(8), 1, 8}, + {uint8(8), 1, 8}, + {uint16(8), 1, 8}, + {uint32(8), 1, 8}, + {uint64(8), 1, 8}, + {float32(8.31), 1, 8.31}, + {float64(8.31), 1, 8.31}, + {"8", 2, 8}, + {"8.00", 2, 8}, + {"8.01", 2, 8.01}, + {int(-8), 1, -8}, + {int8(-8), 1, -8}, + {int16(-8), 1, -8}, + {int32(-8), 1, -8}, + {int64(-8), 1, -8}, + {float32(-8.31), 1, -8.31}, + {float64(-8.31), 1, -8.31}, + {int(math.MaxInt32), 1, float32(math.MaxInt32)}, + {"-8", 1, -8}, + {"-8.01", 1, -8.01}, + {true, 2, 1}, + {false, 2, 0}, + {nil, 2, 0}, + {aliasTypeInt_0, 2, 0}, + {&aliasTypeInt_0, 2, 0}, + {aliasTypeInt_1, 2, 1}, + {&aliasTypeInt_1, 2, 1}, + {aliasTypeString_0, 2, 0}, + {&aliasTypeString_0, 2, 0}, + {aliasTypeString_1, 2, 1}, + {&aliasTypeString_1, 2, 1}, + {aliasTypeString_8d15, 2, 8.15}, + {&aliasTypeString_8d15, 2, 8.15}, + {aliasTypeString_8d15_minus, 1, -8.15}, + {&aliasTypeString_8d15_minus, 1, -8.15}, + + // unsupported value, def == expect + {"10a", 1.11, 1.11}, + {"a10a", 1.11, 1.11}, + {"8.01a", 1.11, 1.11}, + {"8.01 ", 1.11, 1.11}, + {"hello", 1.11, 1.11}, + {testing.T{}, 1.11, 1.11}, + {&testing.T{}, 1.11, 1.11}, + {[]int{}, 1.11, 1.11}, + {[]string{}, 1.11, 1.11}, + {[...]string{}, 1.11, 1.11}, + {map[int]string{}, 1.11, 1.11}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], def[%+v], expect[%+v]", i, tt.input, tt.def, tt.expect) + + v := cvt.Float32(tt.input, tt.def) + assert.Equal(t, tt.expect, v, msg) + } +} + +func TestFloat32_BaseLine(t *testing.T) { + tests := []struct { + input interface{} + expect float32 + }{ + {"8.01a", 0}, + {testing.T{}, 0}, + {&testing.T{}, 0}, + {[]int{}, 0}, + {[]int{1, 2, 3}, 0}, + {[]string{}, 0}, + {[]string{"a", "b", "c"}, 0}, + {[...]string{}, 0}, + {map[int]string{}, 0}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v]", i, tt.input, tt.expect) + + v := cvt.Float32(tt.input) + assert.Equal(t, tt.expect, v, msg) + } +} diff --git a/cvte.go b/cvte.go index c5ba24c..d3a4dba 100644 --- a/cvte.go +++ b/cvte.go @@ -10,7 +10,8 @@ import ( ) var convErr = errors.New("convert failed") -var formatOutOfLimit = "%w, out of max limit value(%d)" +var formatOutOfLimitInt = "%w, out of max limit value(%d)" +var formatOutOfLimitFloat = "%w, out of max limit value(%f)" var formatExtend = "%v, %w" // BoolE convert an interface to a bool type @@ -78,7 +79,7 @@ func Uint32E(val interface{}) (uint32, error) { return 0, e } if v > math.MaxUint32 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint32"), uint32(math.MaxUint32)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "uint32"), uint32(math.MaxUint32)) } return uint32(v), nil @@ -91,7 +92,7 @@ func Uint16E(val interface{}) (uint16, error) { return 0, e } if v > math.MaxUint16 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint16"), uint16(math.MaxUint16)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "uint16"), uint16(math.MaxUint16)) } return uint16(v), nil @@ -104,7 +105,7 @@ func Uint8E(val interface{}) (uint8, error) { return 0, e } if v > math.MaxUint8 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint8"), uint8(math.MaxUint8)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "uint8"), uint8(math.MaxUint8)) } return uint8(v), nil @@ -117,7 +118,7 @@ func UintE(val interface{}) (uint, error) { return 0, e } if v > uint64(^uint(0)) { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint"), ^uint(0)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "uint"), ^uint(0)) } return uint(v), nil @@ -176,7 +177,7 @@ func Int32E(val interface{}) (int32, error) { return 0, e } if v > math.MaxInt32 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "int32"), int32(math.MaxInt32)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "int32"), int32(math.MaxInt32)) } return int32(v), nil @@ -189,7 +190,7 @@ func Int16E(val interface{}) (int16, error) { return 0, e } if v > math.MaxInt16 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "int16"), int16(math.MaxInt16)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "int16"), int16(math.MaxInt16)) } return int16(v), nil @@ -202,7 +203,7 @@ func Int8E(val interface{}) (int8, error) { return 0, e } if v > math.MaxInt8 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "int8"), int8(math.MaxInt8)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "int8"), int8(math.MaxInt8)) } return int8(v), nil @@ -215,7 +216,7 @@ func IntE(val interface{}) (int, error) { return 0, e } if strconv.IntSize == 32 && v > math.MaxInt32 { - return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "int"), int32(math.MaxInt32)) + return 0, fmt.Errorf(formatOutOfLimitInt, newErr(val, "int"), int32(math.MaxInt32)) } return int(v), nil @@ -257,6 +258,59 @@ func convInt64(val interface{}) (int64, error) { return 0, convErr } +// Float64E convert an interface to a float64 type +func Float64E(val interface{}) (float64, error) { + v, _, rv := Indirect(val) + + switch vv := v.(type) { + case nil: + return 0, nil + case bool: + if vv { + return 1, nil + } + return 0, nil + case string: + vvv, err := strconv.ParseFloat(vv, 64) + if err == nil { + return vvv, nil + } + case []byte: + vvv, err := strconv.ParseFloat(string(vv), 64) + if err == nil { + return vvv, nil + } + case uint, uint8, uint16, uint32, uint64, uintptr: + return float64(rv.Uint()), nil + case int, int8, int16, int32, int64: + return float64(rv.Int()), nil + case float32: + // use fmt to fix float32 -> float64 precision loss + // eg: cvt.Float64E(float32(8.31)) + vvv, err := strconv.ParseFloat(fmt.Sprintf("%f", vv), 64) + if err == nil { + return vvv, nil + } + case float64: + return vv, nil + } + + return 0, convErr +} + +// Float32E convert an interface to a float32 type +func Float32E(val interface{}) (float32, error) { + v, e := Float64E(val) + if e := catch("float32", val, e); e != nil { + return 0, e + } + if v > math.MaxFloat32 { + return 0, fmt.Errorf(formatOutOfLimitFloat, newErr(val, "float32"), float32(math.MaxFloat32)) + } + + return float32(v), nil +} + // Indirect returns the value with base type func Indirect(a interface{}) (val interface{}, k reflect.Kind, v reflect.Value) { if a == nil { @@ -318,6 +372,7 @@ func newErr(val interface{}, t string) error { return fmt.Errorf("unable to convert %#v of type %T to %s", val, val, t) } +// catching an error and return a new func catch(t string, val interface{}, e error) error { if e != nil { if errors.Is(e, convErr) { diff --git a/cvte_test.go b/cvte_test.go index 7b43716..137fc58 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -1029,3 +1029,182 @@ func TestIntE(t *testing.T) { assert.Equal(t, tt.expect, v, msg) } } + +func TestFloat64E(t *testing.T) { + tests := []struct { + input interface{} + expect float64 + isErr bool + }{ + {int(8), 8, false}, + {int8(8), 8, false}, + {int16(8), 8, false}, + {int32(8), 8, false}, + {int64(8), 8, false}, + {uint(8), 8, false}, + {uint8(8), 8, false}, + {uint16(8), 8, false}, + {uint32(8), 8, false}, + {uint64(8), 8, false}, + {float32(8.31), float64(8.31), false}, + {float64(8.31), float64(8.31), false}, + {true, 1, false}, + {false, 0, false}, + {int(-8), -8, false}, + {int8(-8), -8, false}, + {int16(-8), -8, false}, + {int32(-8), -8, false}, + {int64(-8), -8, false}, + {float32(-8.31), -8.31, false}, + {float32(-8.3190), -8.3190, false}, + {float64(-8.31), -8.31, false}, + {"-8", -8, false}, + {"-8.01", -8.01, false}, + {"8", 8, false}, + {"8.00", 8, false}, + {"8.01", 8.01, false}, + {[]byte("-8"), -8, false}, + {[]byte("-8.01"), -8.01, false}, + {[]byte("8"), 8, false}, + {[]byte("8.00"), 8, false}, + {[]byte("8.01"), 8.01, false}, + {int64(math.MaxInt64), float64(math.MaxInt64), false}, + {uint64(math.MaxUint64), float64(math.MaxUint64), false}, + {nil, 0, false}, + {aliasTypeInt_0, 0, false}, + {&aliasTypeInt_0, 0, false}, + {aliasTypeInt_1, 1, false}, + {&aliasTypeInt_1, 1, false}, + {aliasTypeString_0, 0, false}, + {&aliasTypeString_0, 0, false}, + {aliasTypeString_1, 1, false}, + {&aliasTypeString_1, 1, false}, + {aliasTypeString_8d15, 8.15, false}, + {&aliasTypeString_8d15, 8.15, false}, + {aliasTypeString_8d15_minus, -8.15, false}, + {&aliasTypeString_8d15_minus, -8.15, false}, + {aliasTypeBool_true, 1, false}, + {&aliasTypeBool_true, 1, false}, + {aliasTypeBool_false, 0, false}, + {&aliasTypeBool_false, 0, false}, + + // errors + {"10a", 0, true}, + {"a10a", 0, true}, + {"8.01a", 0, true}, + {"8.01 ", 0, true}, + {"hello", 0, true}, + {testing.T{}, 0, true}, + {&testing.T{}, 0, true}, + {[]int{}, 0, true}, + {[]string{}, 0, true}, + {[...]string{}, 0, true}, + {map[int]string{}, 0, true}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], expect[%v], isErr[%v]", i, tt.input, tt.expect, tt.isErr) + + v, err := cvt.Float64E(tt.input) + if tt.isErr { + assert.Error(t, err, msg) + continue + } + + assert.NoError(t, err, msg) + assert.Equal(t, tt.expect, v, msg) + + // Non-E test with no default value: + v = cvt.Float64(tt.input) + assert.Equal(t, tt.expect, v, msg) + } +} + +func TestFloat32E(t *testing.T) { + tests := []struct { + input interface{} + expect float32 + isErr bool + }{ + {int(8), 8, false}, + {int8(8), 8, false}, + {int16(8), 8, false}, + {int32(8), 8, false}, + {int64(8), 8, false}, + {uint(8), 8, false}, + {uint8(8), 8, false}, + {uint16(8), 8, false}, + {uint32(8), 8, false}, + {uint64(8), 8, false}, + {float32(8.31), float32(8.31), false}, + {float64(8.31), float32(8.31), false}, + {true, 1, false}, + {false, 0, false}, + {int(-8), -8, false}, + {int8(-8), -8, false}, + {int16(-8), -8, false}, + {int32(-8), -8, false}, + {int64(-8), -8, false}, + {float32(-8.31), -8.31, false}, + {float64(-8.31), -8.31, false}, + {"-8", -8, false}, + {"-8.01", -8.01, false}, + {"8", 8, false}, + {"8.00", 8, false}, + {"8.01", 8.01, false}, + {[]byte("-8"), -8, false}, + {[]byte("-8.01"), -8.01, false}, + {[]byte("8"), 8, false}, + {[]byte("8.00"), 8, false}, + {[]byte("8.01"), 8.01, false}, + {int(math.MaxInt32), float32(math.MaxInt32), false}, + {nil, 0, false}, + {aliasTypeInt_0, 0, false}, + {&aliasTypeInt_0, 0, false}, + {aliasTypeInt_1, 1, false}, + {&aliasTypeInt_1, 1, false}, + {aliasTypeString_0, 0, false}, + {&aliasTypeString_0, 0, false}, + {aliasTypeString_1, 1, false}, + {&aliasTypeString_1, 1, false}, + {aliasTypeString_8d15, 8.15, false}, + {&aliasTypeString_8d15, 8.15, false}, + {aliasTypeString_8d15_minus, -8.15, false}, + {&aliasTypeString_8d15_minus, -8.15, false}, + {aliasTypeBool_true, 1, false}, + {&aliasTypeBool_true, 1, false}, + {aliasTypeBool_false, 0, false}, + {&aliasTypeBool_false, 0, false}, + + // errors + {"10a", 0, true}, + {"a10a", 0, true}, + {"8.01a", 0, true}, + {"8.01 ", 0, true}, + {"hello", 0, true}, + {float64(math.MaxFloat64), 0, true}, + {testing.T{}, 0, true}, + {&testing.T{}, 0, true}, + {[]int{}, 0, true}, + {[]string{}, 0, true}, + {[...]string{}, 0, true}, + {map[int]string{}, 0, true}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], expect[%v], isErr[%v]", i, tt.input, tt.expect, tt.isErr) + + v, err := cvt.Float32E(tt.input) + if tt.isErr { + assert.Error(t, err, msg) + continue + } + + assert.NoError(t, err, msg) + assert.Equal(t, tt.expect, v, msg) + + // Non-E test with no default value: + v = cvt.Float32(tt.input) + assert.Equal(t, tt.expect, v, msg) + } +}