Skip to content

Commit

Permalink
Float formatting fixes (#384)
Browse files Browse the repository at this point in the history
* Float formatting fixes

* Fix test
  • Loading branch information
irees authored Oct 25, 2024
1 parent b7d8cde commit b66c9ac
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 35 deletions.
68 changes: 38 additions & 30 deletions tt/option_convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,31 +72,26 @@ func FromCsv(val any, strv string) error {
return p
}

func formatFloat(v float64) string {
value := ""
if math.IsNaN(v) {
value = ""
} else if v > -100_000 && v < 100_000 {
// use pretty %g formatting but avoid exponents
value = fmt.Sprintf("%g", v)
} else {
value = fmt.Sprintf("%0.5f", v)
}
return value
}

// ToCsv converts any value to a CSV string representation
func ToCsv(val any) (string, error) {
value := ""
switch v := val.(type) {
case nil:
value = ""
case canCsvString:
value = v.ToCsv()
case canValue:
a, err := v.Value()
if err != nil {
return "", err
}
return ToCsv(a)
case string:
value = v
case int:
value = strconv.Itoa(v)
case int64:
value = strconv.Itoa(int(v))
value = strconv.FormatInt(v, 10)
case int:
value = strconv.FormatInt(int64(v), 10)
case bool:
if v {
value = "true"
Expand All @@ -115,16 +110,6 @@ func ToCsv(val any) (string, error) {
}
case []byte:
value = string(v)
case canCsvString:
value = v.ToCsv()
case canValue:
a, err := v.Value()
if err != nil {
return "", err
}
return ToCsv(a)
case canString:
value = v.String()
case int8, int16, int32, uint, uint8, uint16, uint32, uint64:
value = fmt.Sprintf("%d", v)
default:
Expand Down Expand Up @@ -155,12 +140,12 @@ func convertAssign(dest any, src any) (bool, error) {
*d = s
case []byte:
*d = string(s)
case int:
*d = strconv.Itoa(s)
case int64:
*d = strconv.Itoa(int(s))
*d = strconv.FormatInt(s, 10)
case int:
*d = strconv.FormatInt(int64(s), 10)
case float64:
*d = fmt.Sprintf("%0.5f", s)
*d = formatFloat(s)
case time.Time:
*d = s.Format(time.RFC3339)
case canString:
Expand Down Expand Up @@ -286,3 +271,26 @@ func parseTime(d string) (time.Time, error) {
}
return s, err
}

func formatFloat(v float64) string {
if math.IsNaN(v) || math.IsInf(v, 0) || math.IsInf(v, -1) {
return ""
}
return trimZeroAfterDecimal(strconv.FormatFloat(v, 'f', 5, 64))
}

func trimZeroAfterDecimal(value string) string {
i := 0
j := len(value) - 1
for ; i < len(value); i++ {
if value[i] == '.' {
break
}
}
for ; j > i+1; j-- {
if value[j] != '0' {
break
}
}
return value[0 : j+1]
}
70 changes: 65 additions & 5 deletions tt/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,72 @@ package tt

import (
"database/sql/driver"
"math"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestToCsv(t *testing.T) {
tcs := []struct {
name string
val any
expectString string
expectError bool
}{
// ints
{name: "int:1", val: 1, expectString: "1"},
{name: "int:-1", val: -1, expectString: "-1"},
{name: "int:0", val: 0, expectString: "0"},
// Ints
{name: "Int:1", val: NewInt(1), expectString: "1"},
{name: "Int:-1", val: NewInt(-1), expectString: "-1"},
{name: "Int:0", val: NewInt(0), expectString: "0"},
{name: "Int:empty", val: Int{}, expectString: ""},
// floats
{name: "float:1.0", val: 1.0, expectString: "1.0"},
{name: "float:NaN", val: math.NaN(), expectString: ""},
{name: "float:+Inf", val: math.Inf(0), expectString: ""},
{name: "float:-Inf", val: math.Inf(-1), expectString: ""},
{name: "float:1.2", val: 1.2, expectString: "1.2"},
{name: "float:1.23", val: 1.23, expectString: "1.23"},
// Floats
{name: "Float:1.0", val: NewFloat(1.0), expectString: "1.0"},
{name: "Float:empty", val: Float{}, expectString: ""},
{name: "Float:+Inf", val: NewFloat(math.Inf(0)), expectString: ""},
{name: "Float:-Inf", val: NewFloat(math.Inf(-1)), expectString: ""},
{name: "Float:NaN", val: NewFloat(math.NaN()), expectString: ""},
{name: "Float:1.2", val: NewFloat(1.2), expectString: "1.2"},
{name: "Float:-1.2", val: NewFloat(-1.2), expectString: "-1.2"},
{name: "Float:1.23", val: NewFloat(1.23), expectString: "1.23"},
{name: "Float:1.234", val: NewFloat(1.234), expectString: "1.234"},
{name: "Float:1.2345", val: NewFloat(1.23456), expectString: "1.23456"},
{name: "Float:1.123456", val: NewFloat(1.123456), expectString: "1.12346"},
{name: "Float:-1.123456", val: NewFloat(-1.123456), expectString: "-1.12346"},
{name: "Float:1000.0", val: NewFloat(1000.0), expectString: "1000.0"},
{name: "Float:1000.12345", val: NewFloat(1000.12345), expectString: "1000.12345"},
{name: "Float:1000.1234567890", val: NewFloat(1000.1234567890), expectString: "1000.12346"},
{name: "Float:123_456_789_000", val: NewFloat(123_456_789_000), expectString: "123456789000.0"},
{name: "Float:123_456_789_000.123`", val: NewFloat(123_456_789_000.123), expectString: "123456789000.123"},
{name: "Float:123_456_789_000.123456`", val: NewFloat(123_456_789_000.123456), expectString: "123456789000.12346"},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
s, ferr := ToCsv(tc.val)
if tc.expectError && ferr != nil {
// ok
return
} else if tc.expectError && ferr == nil {
t.Error("expected error")
} else if !tc.expectError && ferr != nil {
t.Error(ferr)
}
assert.Equal(t, tc.expectString, s)
})
}
}

func TestOptionString(t *testing.T) {
testStr := "hello"
quote := func(v string) string { return "\"" + v + "\"" }
Expand Down Expand Up @@ -43,7 +103,7 @@ func TestOptionString(t *testing.T) {
true: nil,
"true": "true",
"nil": "nil",
1.23: "1.23000",
1.23: "1.23",
1.234567: "1.23457",
nil: nil,
},
Expand All @@ -53,7 +113,7 @@ func TestOptionString(t *testing.T) {
1: "1",
nil: "",
true: "",
1.23: "1.23000",
1.23: "1.23",
1.234567: "1.23457",
},
uj: map[string]any{
Expand Down Expand Up @@ -113,9 +173,9 @@ func TestOptionString(t *testing.T) {
"1.234": 1.234,
},
str: map[any]any{
1234: "1234.00000",
1.234: "1.23400",
1.567: "1.56700",
1234: "1234.0",
1.234: "1.234",
1.567: "1.567",
"fail": "",
},
uj: map[string]any{
Expand Down

0 comments on commit b66c9ac

Please sign in to comment.