From 71c6a02f2cc3c19325fd82f2d7ad2033df748f1c Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 15:08:54 +0200 Subject: [PATCH 1/7] feat: add base and bitsize to integer conversion functions Signed-off-by: Mark Sagi-Kazar --- cast.go | 25 +++++++++++--------- number.go | 68 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/cast.go b/cast.go index 8d85539..77434f9 100644 --- a/cast.go +++ b/cast.go @@ -6,7 +6,10 @@ // Package cast provides easy and safe casting in Go. package cast -import "time" +import ( + "strconv" + "time" +) const errorMsg = "unable to cast %#v of type %T to %T" const errorMsgWith = "unable to cast %#v of type %T to %T: %w" @@ -31,25 +34,25 @@ func ToE[T Basic](i any) (T, error) { case bool: v, err = ToBoolE(i) case int: - v, err = toNumberE[int](i, parseInt[int]) + v, err = toNumberE[int](i, parseInt[int](0, strconv.IntSize)) case int8: - v, err = toNumberE[int8](i, parseInt[int8]) + v, err = toNumberE[int8](i, parseInt[int8](0, 8)) case int16: - v, err = toNumberE[int16](i, parseInt[int16]) + v, err = toNumberE[int16](i, parseInt[int16](0, 16)) case int32: - v, err = toNumberE[int32](i, parseInt[int32]) + v, err = toNumberE[int32](i, parseInt[int32](0, 32)) case int64: - v, err = toNumberE[int64](i, parseInt[int64]) + v, err = toNumberE[int64](i, parseInt[int64](0, 64)) case uint: - v, err = toUnsignedNumberE[uint](i, parseUint[uint]) + v, err = toUnsignedNumberE[uint](i, parseUint[uint](0, strconv.IntSize)) case uint8: - v, err = toUnsignedNumberE[uint8](i, parseUint[uint8]) + v, err = toUnsignedNumberE[uint8](i, parseUint[uint8](0, 8)) case uint16: - v, err = toUnsignedNumberE[uint16](i, parseUint[uint16]) + v, err = toUnsignedNumberE[uint16](i, parseUint[uint16](0, 16)) case uint32: - v, err = toUnsignedNumberE[uint32](i, parseUint[uint32]) + v, err = toUnsignedNumberE[uint32](i, parseUint[uint32](0, 32)) case uint64: - v, err = toUnsignedNumberE[uint64](i, parseUint[uint64]) + v, err = toUnsignedNumberE[uint64](i, parseUint[uint64](0, 64)) case float32: v, err = toNumberE[float32](i, parseFloat[float32]) case float64: diff --git a/number.go b/number.go index a58dc4d..7720765 100644 --- a/number.go +++ b/number.go @@ -351,43 +351,43 @@ func parseNumber[T Number](s string) (T, error) { switch any(t).(type) { case int: - v, err := parseInt[int](s) + v, err := parseInt[int](0, strconv.IntSize)(s) return T(v), err case int8: - v, err := parseInt[int8](s) + v, err := parseInt[int8](0, 8)(s) return T(v), err case int16: - v, err := parseInt[int16](s) + v, err := parseInt[int16](0, 16)(s) return T(v), err case int32: - v, err := parseInt[int32](s) + v, err := parseInt[int32](0, 32)(s) return T(v), err case int64: - v, err := parseInt[int64](s) + v, err := parseInt[int64](0, 64)(s) return T(v), err case uint: - v, err := parseUint[uint](s) + v, err := parseUint[uint](0, strconv.IntSize)(s) return T(v), err case uint8: - v, err := parseUint[uint8](s) + v, err := parseUint[uint8](0, 8)(s) return T(v), err case uint16: - v, err := parseUint[uint16](s) + v, err := parseUint[uint16](0, 16)(s) return T(v), err case uint32: - v, err := parseUint[uint32](s) + v, err := parseUint[uint32](0, 32)(s) return T(v), err case uint64: - v, err := parseUint[uint64](s) + v, err := parseUint[uint64](0, 64)(s) return T(v), err case float32: @@ -404,22 +404,26 @@ func parseNumber[T Number](s string) (T, error) { } } -func parseInt[T integer](s string) (T, error) { - v, err := strconv.ParseInt(trimDecimal(s), 0, 0) - if err != nil { - return 0, err - } +func parseInt[T integer](base int, bitSize int) func(s string) (T, error) { + return func(s string) (T, error) { + v, err := strconv.ParseInt(trimDecimal(s), base, bitSize) + if err != nil { + return 0, err + } - return T(v), nil + return T(v), nil + } } -func parseUint[T unsigned](s string) (T, error) { - v, err := strconv.ParseUint(strings.TrimLeft(trimDecimal(s), "+"), 0, 0) - if err != nil { - return 0, err - } +func parseUint[T unsigned](base int, bitSize int) func(s string) (T, error) { + return func(s string) (T, error) { + v, err := strconv.ParseUint(strings.TrimLeft(trimDecimal(s), "+"), base, bitSize) + if err != nil { + return 0, err + } - return T(v), nil + return T(v), nil + } } func parseFloat[T float](s string) (T, error) { @@ -456,52 +460,52 @@ func ToFloat32E(i any) (float32, error) { // ToInt64E casts an interface to an int64 type. func ToInt64E(i any) (int64, error) { - return toNumberE[int64](i, parseInt[int64]) + return toNumberE[int64](i, parseInt[int64](0, 64)) } // ToInt32E casts an interface to an int32 type. func ToInt32E(i any) (int32, error) { - return toNumberE[int32](i, parseInt[int32]) + return toNumberE[int32](i, parseInt[int32](0, 32)) } // ToInt16E casts an interface to an int16 type. func ToInt16E(i any) (int16, error) { - return toNumberE[int16](i, parseInt[int16]) + return toNumberE[int16](i, parseInt[int16](0, 16)) } // ToInt8E casts an interface to an int8 type. func ToInt8E(i any) (int8, error) { - return toNumberE[int8](i, parseInt[int8]) + return toNumberE[int8](i, parseInt[int8](0, 8)) } // ToIntE casts an interface to an int type. func ToIntE(i any) (int, error) { - return toNumberE[int](i, parseInt[int]) + return toNumberE[int](i, parseInt[int](0, strconv.IntSize)) } // ToUintE casts an interface to a uint type. func ToUintE(i any) (uint, error) { - return toUnsignedNumberE[uint](i, parseUint[uint]) + return toUnsignedNumberE[uint](i, parseUint[uint](0, strconv.IntSize)) } // ToUint64E casts an interface to a uint64 type. func ToUint64E(i any) (uint64, error) { - return toUnsignedNumberE[uint64](i, parseUint[uint64]) + return toUnsignedNumberE[uint64](i, parseUint[uint64](0, 64)) } // ToUint32E casts an interface to a uint32 type. func ToUint32E(i any) (uint32, error) { - return toUnsignedNumberE[uint32](i, parseUint[uint32]) + return toUnsignedNumberE[uint32](i, parseUint[uint32](0, 32)) } // ToUint16E casts an interface to a uint16 type. func ToUint16E(i any) (uint16, error) { - return toUnsignedNumberE[uint16](i, parseUint[uint16]) + return toUnsignedNumberE[uint16](i, parseUint[uint16](0, 16)) } // ToUint8E casts an interface to a uint type. func ToUint8E(i any) (uint8, error) { - return toUnsignedNumberE[uint8](i, parseUint[uint8]) + return toUnsignedNumberE[uint8](i, parseUint[uint8](0, 8)) } func trimZeroDecimal(s string) string { From a6851b5a8802bebe372bb4c8fcf7455a51e9c5b0 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 15:21:12 +0200 Subject: [PATCH 2/7] refactor: add base to number parser Signed-off-by: Mark Sagi-Kazar --- number.go | 86 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/number.go b/number.go index 7720765..8e6a26c 100644 --- a/number.go +++ b/number.go @@ -347,60 +347,66 @@ func toUnsignedNumberE[T Number](i any, parseFn func(string) (T, error)) (T, err } func parseNumber[T Number](s string) (T, error) { - var t T + return parseNumberBase[T](0)(s) +} - switch any(t).(type) { - case int: - v, err := parseInt[int](0, strconv.IntSize)(s) +func parseNumberBase[T Number](base int) func(s string) (T, error) { + return func(s string) (T, error) { + var t T - return T(v), err - case int8: - v, err := parseInt[int8](0, 8)(s) + switch any(t).(type) { + case int: + v, err := parseInt[int](base, strconv.IntSize)(s) - return T(v), err - case int16: - v, err := parseInt[int16](0, 16)(s) + return T(v), err + case int8: + v, err := parseInt[int8](base, 8)(s) - return T(v), err - case int32: - v, err := parseInt[int32](0, 32)(s) + return T(v), err + case int16: + v, err := parseInt[int16](base, 16)(s) - return T(v), err - case int64: - v, err := parseInt[int64](0, 64)(s) + return T(v), err + case int32: + v, err := parseInt[int32](base, 32)(s) - return T(v), err - case uint: - v, err := parseUint[uint](0, strconv.IntSize)(s) + return T(v), err + case int64: + v, err := parseInt[int64](base, 64)(s) - return T(v), err - case uint8: - v, err := parseUint[uint8](0, 8)(s) + return T(v), err + case uint: + v, err := parseUint[uint](base, strconv.IntSize)(s) - return T(v), err - case uint16: - v, err := parseUint[uint16](0, 16)(s) + return T(v), err + case uint8: + v, err := parseUint[uint8](base, 8)(s) - return T(v), err - case uint32: - v, err := parseUint[uint32](0, 32)(s) + return T(v), err + case uint16: + v, err := parseUint[uint16](base, 16)(s) - return T(v), err - case uint64: - v, err := parseUint[uint64](0, 64)(s) + return T(v), err + case uint32: + v, err := parseUint[uint32](base, 32)(s) - return T(v), err - case float32: - v, err := strconv.ParseFloat(s, 32) + return T(v), err + case uint64: + v, err := parseUint[uint64](base, 64)(s) - return T(v), err - case float64: - v, err := strconv.ParseFloat(s, 64) + return T(v), err + case float32: + v, err := strconv.ParseFloat(s, 32) - return T(v), err + return T(v), err + case float64: + v, err := strconv.ParseFloat(s, 64) - default: - return 0, fmt.Errorf("unknown number type: %T", t) + return T(v), err + + default: + return 0, fmt.Errorf("unknown number type: %T", t) + } } } From 8d65147c02c0292a955fb5c5696b21c966a347b1 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 15:23:16 +0200 Subject: [PATCH 3/7] refactor: make room for generic to number function Signed-off-by: Mark Sagi-Kazar --- cast.go | 14 +++++++------- number.go | 38 +++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cast.go b/cast.go index 77434f9..60fb0c9 100644 --- a/cast.go +++ b/cast.go @@ -34,15 +34,15 @@ func ToE[T Basic](i any) (T, error) { case bool: v, err = ToBoolE(i) case int: - v, err = toNumberE[int](i, parseInt[int](0, strconv.IntSize)) + v, err = toSignedNumberE[int](i, parseInt[int](0, strconv.IntSize)) case int8: - v, err = toNumberE[int8](i, parseInt[int8](0, 8)) + v, err = toSignedNumberE[int8](i, parseInt[int8](0, 8)) case int16: - v, err = toNumberE[int16](i, parseInt[int16](0, 16)) + v, err = toSignedNumberE[int16](i, parseInt[int16](0, 16)) case int32: - v, err = toNumberE[int32](i, parseInt[int32](0, 32)) + v, err = toSignedNumberE[int32](i, parseInt[int32](0, 32)) case int64: - v, err = toNumberE[int64](i, parseInt[int64](0, 64)) + v, err = toSignedNumberE[int64](i, parseInt[int64](0, 64)) case uint: v, err = toUnsignedNumberE[uint](i, parseUint[uint](0, strconv.IntSize)) case uint8: @@ -54,9 +54,9 @@ func ToE[T Basic](i any) (T, error) { case uint64: v, err = toUnsignedNumberE[uint64](i, parseUint[uint64](0, 64)) case float32: - v, err = toNumberE[float32](i, parseFloat[float32]) + v, err = toSignedNumberE[float32](i, parseFloat[float32]) case float64: - v, err = toNumberE[float64](i, parseFloat[float64]) + v, err = toSignedNumberE[float64](i, parseFloat[float64]) case time.Time: v, err = ToTimeE(i) case time.Duration: diff --git a/number.go b/number.go index 8e6a26c..d6fee5c 100644 --- a/number.go +++ b/number.go @@ -50,15 +50,15 @@ func ToNumberE[T Number](i any) (T, error) { switch any(t).(type) { case int: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) case int8: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) case int16: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) case int32: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) case int64: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) case uint: return toUnsignedNumberE[T](i, parseNumber[T]) case uint8: @@ -70,9 +70,9 @@ func ToNumberE[T Number](i any) (T, error) { case uint64: return toUnsignedNumberE[T](i, parseNumber[T]) case float32: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) case float64: - return toNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumber[T]) default: return 0, fmt.Errorf("unknown number type: %T", t) } @@ -85,10 +85,10 @@ func ToNumber[T Number](i any) T { return v } -// toNumber's semantics differ from other "to" functions. +// toSignedNumber's semantics differ from other "to" functions. // It returns false as the second parameter if the conversion fails. // This is to signal other callers that they should proceed with their own conversions. -func toNumber[T Number](i any) (T, bool) { +func toSignedNumber[T Number](i any) (T, bool) { i, _ = indirect(i) switch s := i.(type) { @@ -135,8 +135,8 @@ func toNumber[T Number](i any) (T, bool) { return 0, false } -func toNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { - n, ok := toNumber[T](i) +func toSignedNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { + n, ok := toSignedNumber[T](i) if ok { return n, nil } @@ -185,7 +185,7 @@ func toNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { return T(s.Float64()), nil default: if i, ok := resolveAlias(i); ok { - return toNumberE(i, parseFn) + return toSignedNumberE(i, parseFn) } return 0, fmt.Errorf(errorMsg, i, i, n) @@ -456,37 +456,37 @@ func parseFloat[T float](s string) (T, error) { // ToFloat64E casts an interface to a float64 type. func ToFloat64E(i any) (float64, error) { - return toNumberE[float64](i, parseFloat[float64]) + return toSignedNumberE[float64](i, parseFloat[float64]) } // ToFloat32E casts an interface to a float32 type. func ToFloat32E(i any) (float32, error) { - return toNumberE[float32](i, parseFloat[float32]) + return toSignedNumberE[float32](i, parseFloat[float32]) } // ToInt64E casts an interface to an int64 type. func ToInt64E(i any) (int64, error) { - return toNumberE[int64](i, parseInt[int64](0, 64)) + return toSignedNumberE[int64](i, parseInt[int64](0, 64)) } // ToInt32E casts an interface to an int32 type. func ToInt32E(i any) (int32, error) { - return toNumberE[int32](i, parseInt[int32](0, 32)) + return toSignedNumberE[int32](i, parseInt[int32](0, 32)) } // ToInt16E casts an interface to an int16 type. func ToInt16E(i any) (int16, error) { - return toNumberE[int16](i, parseInt[int16](0, 16)) + return toSignedNumberE[int16](i, parseInt[int16](0, 16)) } // ToInt8E casts an interface to an int8 type. func ToInt8E(i any) (int8, error) { - return toNumberE[int8](i, parseInt[int8](0, 8)) + return toSignedNumberE[int8](i, parseInt[int8](0, 8)) } // ToIntE casts an interface to an int type. func ToIntE(i any) (int, error) { - return toNumberE[int](i, parseInt[int](0, strconv.IntSize)) + return toSignedNumberE[int](i, parseInt[int](0, strconv.IntSize)) } // ToUintE casts an interface to a uint type. From b407804f2d73c0ec4e60a3585748c75aec673d39 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 15:28:32 +0200 Subject: [PATCH 4/7] feat: add number base parsing Signed-off-by: Mark Sagi-Kazar --- number.go | 61 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/number.go b/number.go index d6fee5c..6e6fdfe 100644 --- a/number.go +++ b/number.go @@ -46,45 +46,68 @@ type float interface { // ToNumberE casts any value to a [Number] type. func ToNumberE[T Number](i any) (T, error) { + return toNumberE[T](i, 0) +} + +// ToNumber casts any value to a [Number] type. +func ToNumber[T Number](i any) T { + v, _ := ToNumberE[T](i) + + return v +} + +// ToNumberBaseE casts any value to a [Number] type. +// +// For integer types the second parameter is used as a base instead of auto detection. +func ToNumberBaseE[T Number](i any, base int) (T, error) { + return toNumberE[T](i, base) +} + +// ToNumberBase casts any value to a [Number] type. +// +// For integer types the second parameter is used as a base instead of auto detection. +func ToNumberBase[T Number](i any, base int) T { + v, _ := ToNumberBaseE[T](i, base) + + return v +} + +// toNumberE casts any value to a [Number] type. +// +// For integer types the second parameter is used as a base. +func toNumberE[T Number](i any, base int) (T, error) { var t T switch any(t).(type) { case int: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) case int8: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) case int16: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) case int32: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) case int64: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) case uint: - return toUnsignedNumberE[T](i, parseNumber[T]) + return toUnsignedNumberE[T](i, parseNumberBase[T](base)) case uint8: - return toUnsignedNumberE[T](i, parseNumber[T]) + return toUnsignedNumberE[T](i, parseNumberBase[T](base)) case uint16: - return toUnsignedNumberE[T](i, parseNumber[T]) + return toUnsignedNumberE[T](i, parseNumberBase[T](base)) case uint32: - return toUnsignedNumberE[T](i, parseNumber[T]) + return toUnsignedNumberE[T](i, parseNumberBase[T](base)) case uint64: - return toUnsignedNumberE[T](i, parseNumber[T]) + return toUnsignedNumberE[T](i, parseNumberBase[T](base)) case float32: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) case float64: - return toSignedNumberE[T](i, parseNumber[T]) + return toSignedNumberE[T](i, parseNumberBase[T](base)) default: return 0, fmt.Errorf("unknown number type: %T", t) } } -// ToNumber casts any value to a [Number] type. -func ToNumber[T Number](i any) T { - v, _ := ToNumberE[T](i) - - return v -} - // toSignedNumber's semantics differ from other "to" functions. // It returns false as the second parameter if the conversion fails. // This is to signal other callers that they should proceed with their own conversions. From b5f229c138120b39b9d8952a376c80603bb4d3be Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 15:28:51 +0200 Subject: [PATCH 5/7] refactor: remove unused parseNumber function Signed-off-by: Mark Sagi-Kazar --- number.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/number.go b/number.go index 6e6fdfe..58e970b 100644 --- a/number.go +++ b/number.go @@ -369,10 +369,6 @@ func toUnsignedNumberE[T Number](i any, parseFn func(string) (T, error)) (T, err } } -func parseNumber[T Number](s string) (T, error) { - return parseNumberBase[T](0)(s) -} - func parseNumberBase[T Number](base int) func(s string) (T, error) { return func(s string) (T, error) { var t T From 7e09963b56acd2a443cae03347bb0830a89f0d97 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 16:40:14 +0200 Subject: [PATCH 6/7] refactor: fix order of functions Signed-off-by: Mark Sagi-Kazar --- number.go | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/number.go b/number.go index 58e970b..c1183f4 100644 --- a/number.go +++ b/number.go @@ -473,24 +473,14 @@ func parseFloat[T float](s string) (T, error) { return v.(T), err } -// ToFloat64E casts an interface to a float64 type. -func ToFloat64E(i any) (float64, error) { - return toSignedNumberE[float64](i, parseFloat[float64]) -} - -// ToFloat32E casts an interface to a float32 type. -func ToFloat32E(i any) (float32, error) { - return toSignedNumberE[float32](i, parseFloat[float32]) -} - -// ToInt64E casts an interface to an int64 type. -func ToInt64E(i any) (int64, error) { - return toSignedNumberE[int64](i, parseInt[int64](0, 64)) +// ToIntE casts an interface to an int type. +func ToIntE(i any) (int, error) { + return toSignedNumberE[int](i, parseInt[int](0, strconv.IntSize)) } -// ToInt32E casts an interface to an int32 type. -func ToInt32E(i any) (int32, error) { - return toSignedNumberE[int32](i, parseInt[int32](0, 32)) +// ToInt8E casts an interface to an int8 type. +func ToInt8E(i any) (int8, error) { + return toSignedNumberE[int8](i, parseInt[int8](0, 8)) } // ToInt16E casts an interface to an int16 type. @@ -498,14 +488,14 @@ func ToInt16E(i any) (int16, error) { return toSignedNumberE[int16](i, parseInt[int16](0, 16)) } -// ToInt8E casts an interface to an int8 type. -func ToInt8E(i any) (int8, error) { - return toSignedNumberE[int8](i, parseInt[int8](0, 8)) +// ToInt32E casts an interface to an int32 type. +func ToInt32E(i any) (int32, error) { + return toSignedNumberE[int32](i, parseInt[int32](0, 32)) } -// ToIntE casts an interface to an int type. -func ToIntE(i any) (int, error) { - return toSignedNumberE[int](i, parseInt[int](0, strconv.IntSize)) +// ToInt64E casts an interface to an int64 type. +func ToInt64E(i any) (int64, error) { + return toSignedNumberE[int64](i, parseInt[int64](0, 64)) } // ToUintE casts an interface to a uint type. @@ -513,9 +503,14 @@ func ToUintE(i any) (uint, error) { return toUnsignedNumberE[uint](i, parseUint[uint](0, strconv.IntSize)) } -// ToUint64E casts an interface to a uint64 type. -func ToUint64E(i any) (uint64, error) { - return toUnsignedNumberE[uint64](i, parseUint[uint64](0, 64)) +// ToUint8E casts an interface to a uint type. +func ToUint8E(i any) (uint8, error) { + return toUnsignedNumberE[uint8](i, parseUint[uint8](0, 8)) +} + +// ToUint16E casts an interface to a uint16 type. +func ToUint16E(i any) (uint16, error) { + return toUnsignedNumberE[uint16](i, parseUint[uint16](0, 16)) } // ToUint32E casts an interface to a uint32 type. @@ -523,14 +518,19 @@ func ToUint32E(i any) (uint32, error) { return toUnsignedNumberE[uint32](i, parseUint[uint32](0, 32)) } -// ToUint16E casts an interface to a uint16 type. -func ToUint16E(i any) (uint16, error) { - return toUnsignedNumberE[uint16](i, parseUint[uint16](0, 16)) +// ToUint64E casts an interface to a uint64 type. +func ToUint64E(i any) (uint64, error) { + return toUnsignedNumberE[uint64](i, parseUint[uint64](0, 64)) } -// ToUint8E casts an interface to a uint type. -func ToUint8E(i any) (uint8, error) { - return toUnsignedNumberE[uint8](i, parseUint[uint8](0, 8)) +// ToFloat32E casts an interface to a float32 type. +func ToFloat32E(i any) (float32, error) { + return toSignedNumberE[float32](i, parseFloat[float32]) +} + +// ToFloat64E casts an interface to a float64 type. +func ToFloat64E(i any) (float64, error) { + return toSignedNumberE[float64](i, parseFloat[float64]) } func trimZeroDecimal(s string) string { From d6a61cd44bb4a038c6f1b4cd65eb254977cb3f8e Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 13 Oct 2025 16:40:33 +0200 Subject: [PATCH 7/7] test: add tests for NumberBase functions Signed-off-by: Mark Sagi-Kazar --- number_base_test.go | 141 ++++++++++++++++++++++++++++++++++++++++++++ number_test.go | 34 +++++++++++ 2 files changed, 175 insertions(+) create mode 100644 number_base_test.go diff --git a/number_base_test.go b/number_base_test.go new file mode 100644 index 0000000..7e2917c --- /dev/null +++ b/number_base_test.go @@ -0,0 +1,141 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" +) + +type baseTestCase struct { + input any + base int + expected any + expectError bool +} + +func generateNumberBaseTestCases(samples []any) []baseTestCase { + zero := samples[0] + // one := samples[1] + eight := samples[2] + // eightNegative := samples[3] + // eightPoint31 := samples[4] + // eightPoint31Negative := samples[5] + // aliasEight := samples[6] + min := samples[7] + max := samples[8] + underflowString := samples[9] + overflowString := samples[10] + + _ = min + _ = max + _ = underflowString + _ = overflowString + + // kind := reflect.TypeOf(zero).Kind() + // isSint := kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 + // isUint := kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 + // isInt := isSint || isUint + + // Some precision is lost when converting from float64 to float32. + // eightPoint31_32 := eightPoint31 + // eightPoint31Negative_32 := eightPoint31Negative + // if kind == reflect.Float64 { + // eightPoint31_32 = float64(float32(eightPoint31.(float64))) + // eightPoint31Negative_32 = float64(float32(eightPoint31Negative.(float64))) + // } + + testCases := []baseTestCase{ + {"08", 10, eight, false}, + {"0008", 10, eight, false}, + {"010", 8, eight, false}, + {"08", 16, eight, false}, + + {"0x08", 10, zero, true}, + } + + return testCases +} + +func TestNumberBase(t *testing.T) { + t.Parallel() + + for typeName, ctx := range numberContexts { + // TODO: remove after minimum Go version is >=1.22 + typeName := typeName + ctx := ctx + + if typeName == "float32" || typeName == "float64" { + continue + } + + t.Run(typeName, func(t *testing.T) { + t.Parallel() + + testCases := generateNumberBaseTestCases(ctx.samples) + + for _, testCase := range testCases { + // TODO: remove after minimum Go version is >=1.22 + testCase := testCase + + t.Run("", func(t *testing.T) { + t.Parallel() + + t.Run("Value", func(t *testing.T) { + t.Run("To", func(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + v := ctx.base(testCase.input, testCase.base) + c.Assert(v, qt.Equals, testCase.expected) + }) + + t.Run("ToE", func(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + v, err := ctx.baseErr(testCase.input, testCase.base) + if testCase.expectError { + c.Assert(err, qt.IsNotNil) + } else { + c.Assert(err, qt.IsNil) + c.Assert(v, qt.Equals, testCase.expected) + } + }) + + t.Run("Pointer", func(t *testing.T) { + t.Run("To", func(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + v := ctx.base(&testCase.input, testCase.base) + c.Assert(v, qt.Equals, testCase.expected) + }) + + t.Run("ToE", func(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + v, err := ctx.baseErr(&testCase.input, testCase.base) + if testCase.expectError { + c.Assert(err, qt.IsNotNil) + } else { + c.Assert(err, qt.IsNil) + c.Assert(v, qt.Equals, testCase.expected) + } + }) + }) + }) + }) + } + }) + } +} diff --git a/number_test.go b/number_test.go index 9d84d18..5974d41 100644 --- a/number_test.go +++ b/number_test.go @@ -25,6 +25,8 @@ type numberContext struct { generic func(any) any specificErr func(any) (any, error) genericErr func(any) (any, error) + base func(any, int) any + baseErr func(any, int) (any, error) // Order of samples: // zero, one, 8, -8, 8.3, -8.3, min, max, underflow string, overflow string @@ -39,6 +41,14 @@ func toAnyErr[T cast.Number](fn func(any) (T, error)) func(i any) (any, error) { return func(i any) (any, error) { return fn(i) } } +func baseToAny[T cast.Number](fn func(any, int) T) func(any, int) any { + return func(i any, b int) any { return fn(i, b) } +} + +func baseToAnyErr[T cast.Number](fn func(any, int) (T, error)) func(any, int) (any, error) { + return func(i any, b int) (any, error) { return fn(i, b) } +} + var numberContexts = map[string]numberContext{ "int": { to: toAny(cast.To[int]), @@ -47,6 +57,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[int]), specificErr: toAnyErr(cast.ToIntE), genericErr: toAnyErr(cast.ToNumberE[int]), + base: baseToAny(cast.ToNumberBase[int]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[int]), samples: []any{int(0), int(1), int(8), int(-8), int(8), int(-8), MyInt(8), math.MinInt, math.MaxInt, new(big.Int).Sub(big.NewInt(math.MinInt), big.NewInt(1)).String(), new(big.Int).Add(big.NewInt(math.MaxInt), big.NewInt(1)).String()}, }, "int8": { @@ -56,6 +68,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[int8]), specificErr: toAnyErr(cast.ToInt8E), genericErr: toAnyErr(cast.ToNumberE[int8]), + base: baseToAny(cast.ToNumberBase[int8]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[int8]), samples: []any{int8(0), int8(1), int8(8), int8(-8), int8(8), int8(-8), MyInt8(8), int8(math.MinInt8), int8(math.MaxInt8), "-129", "128"}, }, "int16": { @@ -65,6 +79,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[int16]), specificErr: toAnyErr(cast.ToInt16E), genericErr: toAnyErr(cast.ToNumberE[int16]), + base: baseToAny(cast.ToNumberBase[int16]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[int16]), samples: []any{int16(0), int16(1), int16(8), int16(-8), int16(8), int16(-8), MyInt16(8), int16(math.MinInt16), int16(math.MaxInt16), "-32769", "32768"}, }, "int32": { @@ -74,6 +90,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[int32]), specificErr: toAnyErr(cast.ToInt32E), genericErr: toAnyErr(cast.ToNumberE[int32]), + base: baseToAny(cast.ToNumberBase[int32]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[int32]), samples: []any{int32(0), int32(1), int32(8), int32(-8), int32(8), int32(-8), MyInt32(8), int32(math.MinInt32), int32(math.MaxInt32), "-2147483649", "2147483648"}, }, "int64": { @@ -83,6 +101,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[int64]), specificErr: toAnyErr(cast.ToInt64E), genericErr: toAnyErr(cast.ToNumberE[int64]), + base: baseToAny(cast.ToNumberBase[int64]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[int64]), samples: []any{int64(0), int64(1), int64(8), int64(-8), int64(8), int64(-8), MyInt64(8), int64(math.MinInt64), int64(math.MaxInt64), "-9223372036854775809", "9223372036854775808"}, }, "uint": { @@ -92,6 +112,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[uint]), specificErr: toAnyErr(cast.ToUintE), genericErr: toAnyErr(cast.ToNumberE[uint]), + base: baseToAny(cast.ToNumberBase[uint]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[uint]), samples: []any{uint(0), uint(1), uint(8), uint(0), uint(8), uint(0), MyUint(8), uint(0), uint(math.MaxUint), nil, nil}, }, "uint8": { @@ -101,6 +123,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[uint8]), specificErr: toAnyErr(cast.ToUint8E), genericErr: toAnyErr(cast.ToNumberE[uint8]), + base: baseToAny(cast.ToNumberBase[uint8]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[uint8]), samples: []any{uint8(0), uint8(1), uint8(8), uint8(0), uint8(8), uint8(0), MyUint8(8), uint8(0), uint8(math.MaxUint8), "-1", "256"}, }, "uint16": { @@ -110,6 +134,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[uint16]), specificErr: toAnyErr(cast.ToUint16E), genericErr: toAnyErr(cast.ToNumberE[uint16]), + base: baseToAny(cast.ToNumberBase[uint16]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[uint16]), samples: []any{uint16(0), uint16(1), uint16(8), uint16(0), uint16(8), uint16(0), MyUint16(8), uint16(0), uint16(math.MaxUint16), "-1", "65536"}, }, "uint32": { @@ -119,6 +145,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[uint32]), specificErr: toAnyErr(cast.ToUint32E), genericErr: toAnyErr(cast.ToNumberE[uint32]), + base: baseToAny(cast.ToNumberBase[uint32]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[uint32]), samples: []any{uint32(0), uint32(1), uint32(8), uint32(0), uint32(8), uint32(0), MyUint32(8), uint32(0), uint32(math.MaxUint32), "-1", "4294967296"}, }, "uint64": { @@ -128,6 +156,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[uint64]), specificErr: toAnyErr(cast.ToUint64E), genericErr: toAnyErr(cast.ToNumberE[uint64]), + base: baseToAny(cast.ToNumberBase[uint64]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[uint64]), samples: []any{uint64(0), uint64(1), uint64(8), uint64(0), uint64(8), uint64(0), MyUint64(8), uint64(0), uint64(math.MaxUint64), "-1", "18446744073709551616"}, }, "float32": { @@ -137,6 +167,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[float32]), specificErr: toAnyErr(cast.ToFloat32E), genericErr: toAnyErr(cast.ToNumberE[float32]), + base: baseToAny(cast.ToNumberBase[float32]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[float32]), samples: []any{float32(0), float32(1), float32(8), float32(-8), float32(8.31), float32(-8.31), MyFloat32(8), float32(-math.MaxFloat32), float32(math.MaxFloat32), nil, nil}, }, "float64": { @@ -146,6 +178,8 @@ var numberContexts = map[string]numberContext{ generic: toAny(cast.ToNumber[float64]), specificErr: toAnyErr(cast.ToFloat64E), genericErr: toAnyErr(cast.ToNumberE[float64]), + base: baseToAny(cast.ToNumberBase[float64]), + baseErr: baseToAnyErr(cast.ToNumberBaseE[float64]), samples: []any{float64(0), float64(1), float64(8), float64(-8), float64(8.31), float64(-8.31), MyFloat64(8), float64(-math.MaxFloat64), float64(math.MaxFloat64), nil, nil}, }, }