Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ccoVeille committed Dec 14, 2024
1 parent ba4dc13 commit 95668c6
Show file tree
Hide file tree
Showing 4 changed files with 465 additions and 15 deletions.
87 changes: 73 additions & 14 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,75 @@ import (
"math"
)

func Convert[NumOut Number](orig any) (converted NumOut, err error) {
switch v := orig.(type) {
case int:
return convertFromNumber[NumOut](v)
case uint:
return convertFromNumber[NumOut](v)
case int8:
return convertFromNumber[NumOut](v)
case uint8:
return convertFromNumber[NumOut](v)
case int16:
return convertFromNumber[NumOut](v)
case uint16:
return convertFromNumber[NumOut](v)
case int32:
return convertFromNumber[NumOut](v)
case uint32:
return convertFromNumber[NumOut](v)
case int64:
return convertFromNumber[NumOut](v)
case uint64:
return convertFromNumber[NumOut](v)
case float32:
return convertFromNumber[NumOut](v)
case float64:
return convertFromNumber[NumOut](v)
}

return 0, ErrConversionIssue

Check warning on line 40 in conversion.go

View check run for this annotation

Codecov / codecov/patch

conversion.go#L40

Added line #L40 was not covered by tests
}

func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOut, err error) {
converted = NumOut(orig)

// floats could be compared directly
switch any(converted).(type) {
case float64:
// float64 cannot overflow, so we don't have to worry about it
return converted, nil
case float32:
origFloat64, isFloat64 := any(orig).(float64)
if !isFloat64 {
// only float64 can overflow float32
// everything else can be safely converted
return converted, nil
}

// check boundary
if math.Abs(origFloat64) < math.MaxFloat32 {
// the value is within float32 range, there is no overflow
return converted, nil
}

// TODO: check for numbers close to math.MaxFloat32

boundary := float32(math.MaxFloat32)
errBoundary := ErrExceedMaximumValue
if negative(orig) {
boundary = -boundary
errBoundary = ErrExceedMinimumValue
}

return 0, Error{
value: orig,
err: errBoundary,
boundary: boundary,
}
}

errBoundary := ErrExceedMaximumValue
boundary := getUpperBoundary(converted)
if negative(orig) {
Expand All @@ -27,6 +93,9 @@ func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOu
}
}

// convert back to the original type
cast := NumIn(converted)
// and compare
base := orig
switch f := any(orig).(type) {
case float64:
Expand All @@ -35,19 +104,9 @@ func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOu
base = NumIn(math.Trunc(float64(f)))
}

cast := NumIn(converted)

switch any(converted).(type) {
case float64, float32:
// we have to compare with a tolerance because of floating point inaccuracy
if math.Abs(float64(cast)-float64(orig)) <= 1e-3 {
return converted, nil
}
default:
// exact match
if cast == base {
return converted, nil
}
// exact match
if cast == base {
return converted, nil
}

return 0, Error{
Expand All @@ -57,7 +116,7 @@ func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOu
}
}

// ToInt attempts to convert any [Number] value to an int.
// ToInt attempts to convert any [Type] value to an int.
// If the conversion results in a value outside the range of an int,
// an [ErrConversionIssue] error is returned.
func ToInt[T Number](i T) (int, error) {
Expand Down
21 changes: 21 additions & 0 deletions conversion_64bit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ package safecast_test
import (
"math"
"testing"

"github.com/ccoveille/go-safecast"
)

func TestToInt32_64bit(t *testing.T) {
Expand Down Expand Up @@ -53,3 +55,22 @@ func TestToInt_64bit(t *testing.T) {
})
})
}

// TestConvert_64bit completes the [TestConvert] tests in conversion_test.go
// it contains the tests that can only works on 64-bit systems
func TestConvert_64bit(t *testing.T) {
t.Run("to uint32", func(t *testing.T) {
for name, tt := range map[string]struct {
input any
want uint32
}{
"positive out of range": {input: uint64(math.MaxUint32 + 1), want: 0},
} {
t.Run(name, func(t *testing.T) {
got, err := safecast.Convert[uint32](tt.input)
assertEqual(t, tt.want, got)
requireErrorIs(t, err, safecast.ErrConversionIssue)
})
}
})
}
Loading

0 comments on commit 95668c6

Please sign in to comment.