From db93abef91f57e6c5ada7fcd9098e97969b98308 Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Tue, 25 Mar 2025 23:41:40 +0100 Subject: [PATCH] fix: better document and isolate performance hack * fixes #110 Signed-off-by: Frederic BIDON --- conv/convert.go | 10 +++------- conv/format.go | 6 +----- conv/sizeof.go | 17 +++++++++++++++++ conv/type_constraints.go | 5 +++++ 4 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 conv/sizeof.go diff --git a/conv/convert.go b/conv/convert.go index b7238ac..696fcde 100644 --- a/conv/convert.go +++ b/conv/convert.go @@ -18,7 +18,6 @@ import ( "math" "strconv" "strings" - "unsafe" ) // same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER @@ -54,11 +53,8 @@ func IsFloat64AJSONInteger(f float64) bool { // ConvertFloat turns a string into a float numerical value. func ConvertFloat[T Float](str string) (T, error) { - // NOTE: [unsafe.SizeOf] simply returns the size in bytes of the value. - // For primitive types T, the generic stencil is precompiled and this value - // is resolved at compile time, resulting in an immediate call to [strconv.ParseFloat]. var v T - f, err := strconv.ParseFloat(str, int(unsafe.Sizeof(v))*8) + f, err := strconv.ParseFloat(str, bitsize(v)) if err != nil { return 0, err } @@ -69,7 +65,7 @@ func ConvertFloat[T Float](str string) (T, error) { // ConvertInteger turns a string into a signed integer. func ConvertInteger[T Signed](str string) (T, error) { var v T - f, err := strconv.ParseInt(str, 10, int(unsafe.Sizeof(v))*8) + f, err := strconv.ParseInt(str, 10, bitsize(v)) if err != nil { return 0, err } @@ -80,7 +76,7 @@ func ConvertInteger[T Signed](str string) (T, error) { // ConvertUinteger turns a string into an unsigned integer. func ConvertUinteger[T Unsigned](str string) (T, error) { var v T - f, err := strconv.ParseUint(str, 10, int(unsafe.Sizeof(v))*8) + f, err := strconv.ParseUint(str, 10, bitsize(v)) if err != nil { return 0, err } diff --git a/conv/format.go b/conv/format.go index 10d7d6b..db7562a 100644 --- a/conv/format.go +++ b/conv/format.go @@ -16,7 +16,6 @@ package conv import ( "strconv" - "unsafe" ) // FormatInteger turns an integer type into a string. @@ -31,10 +30,7 @@ func FormatUinteger[T Unsigned](value T) string { // FormatFloat turns a floating point numerical value into a string. func FormatFloat[T Float](value T) string { - // NOTE: [unsafe.SizeOf] simply returns the size in bytes of the value. - // For primitive types T, the generic stencil is precompiled and this value - // is resolved at compile time, resulting in an immediate call to [strconv.FormatFloat]. - return strconv.FormatFloat(float64(value), 'f', -1, int(unsafe.Sizeof(value))*8) + return strconv.FormatFloat(float64(value), 'f', -1, bitsize(value)) } // FormatBool turns a boolean into a string. diff --git a/conv/sizeof.go b/conv/sizeof.go new file mode 100644 index 0000000..646f8be --- /dev/null +++ b/conv/sizeof.go @@ -0,0 +1,17 @@ +package conv + +import "unsafe" + +// bitsize returns the size in bits of a type. +// +// NOTE: [unsafe.SizeOf] simply returns the size in bytes of the value. +// For primitive types T, the generic stencil is precompiled and this value +// is resolved at compile time, resulting in an immediate call to [strconv.ParseFloat]. +// +// We may leave up to the go compiler to simplify this function into a +// constant value, which happens in practice at least for primitive types +// (e.g. numerical types). +func bitsize[T Numerical](value T) int { + const bitsPerByte = 8 + return int(unsafe.Sizeof(value)) * bitsPerByte +} diff --git a/conv/type_constraints.go b/conv/type_constraints.go index 5fb92d6..3c61498 100644 --- a/conv/type_constraints.go +++ b/conv/type_constraints.go @@ -32,4 +32,9 @@ type ( Float interface { ~float32 | ~float64 } + + // Numerical types + Numerical interface { + Signed | Unsigned | Float + } )