From d17285a027833847142f76ac28ed3a55a5bdd2e5 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Sat, 30 Sep 2023 23:14:11 +0100 Subject: [PATCH] Copy signbit for -nan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Is this useful? Doubtful. But doesn't hurt either, and it is useful for toml-test. Also write the signbit back, and don't write explicit +inf – just inf is enough. --- decode_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ encode.go | 16 ++++++++++-- encode_test.go | 2 +- parse.go | 8 +++++- 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/decode_test.go b/decode_test.go index 439b803c..9a097717 100644 --- a/decode_test.go +++ b/decode_test.go @@ -423,6 +423,74 @@ func TestDecodeFloatOverflow(t *testing.T) { } } +func TestDecodeSignbit(t *testing.T) { + var m struct { + N1, N2 float64 + I1, I2 float64 + Z1, Z2 float64 + ZF1, ZF2 float64 + } + _, err := Decode(` +n1 = nan +n2 = -nan +i1 = inf +i2 = -inf +z1 = 0 +z2 = -0 +zf1 = 0.0 +zf2 = -0.0 +`, &m) + if err != nil { + t.Fatal(err) + } + + if h := fmt.Sprintf("%v %v", m.N1, math.Signbit(m.N1)); h != "NaN false" { + t.Error("N1:", h) + } + if h := fmt.Sprintf("%v %v", m.I1, math.Signbit(m.I1)); h != "+Inf false" { + t.Error("I1:", h) + } + if h := fmt.Sprintf("%v %v", m.Z1, math.Signbit(m.Z1)); h != "0 false" { + t.Error("Z1:", h) + } + if h := fmt.Sprintf("%v %v", m.ZF1, math.Signbit(m.ZF1)); h != "0 false" { + t.Error("ZF1:", h) + } + + if h := fmt.Sprintf("%v %v", m.N2, math.Signbit(m.N2)); h != "NaN true" { + t.Error("N2:", h) + } + if h := fmt.Sprintf("%v %v", m.I2, math.Signbit(m.I2)); h != "-Inf true" { + t.Error("I2:", h) + } + if h := fmt.Sprintf("%v %v", m.Z2, math.Signbit(m.Z2)); h != "0 false" { // Correct: -0 is same as 0 + t.Error("Z2:", h) + } + if h := fmt.Sprintf("%v %v", m.ZF2, math.Signbit(m.ZF2)); h != "-0 true" { + t.Error("ZF2:", h) + } + + buf := new(bytes.Buffer) + err = NewEncoder(buf).Encode(m) + if err != nil { + t.Fatal(err) + } + + want := strings.ReplaceAll(` + N1 = nan + N2 = -nan + I1 = inf + I2 = -inf + Z1 = 0.0 + Z2 = 0.0 + ZF1 = 0.0 + ZF2 = -0.0 + `, "\t", "")[1:] + if buf.String() != want { + t.Errorf("\nwant:\n%s\nhave:\n%s", want, buf.String()) + } +} + func TestDecodeSizedInts(t *testing.T) { type table struct { U8 uint8 diff --git a/encode.go b/encode.go index bb726060..5939acf9 100644 --- a/encode.go +++ b/encode.go @@ -280,18 +280,30 @@ func (enc *Encoder) eElement(rv reflect.Value) { case reflect.Float32: f := rv.Float() if math.IsNaN(f) { + if math.Signbit(f) { + enc.wf("-") + } enc.wf("nan") } else if math.IsInf(f, 0) { - enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)]) + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("inf") } else { enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32))) } case reflect.Float64: f := rv.Float() if math.IsNaN(f) { + if math.Signbit(f) { + enc.wf("-") + } enc.wf("nan") } else if math.IsInf(f, 0) { - enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)]) + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("inf") } else { enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64))) } diff --git a/encode_test.go b/encode_test.go index 8ac72a18..2ec57579 100644 --- a/encode_test.go +++ b/encode_test.go @@ -470,7 +470,7 @@ func TestEncodeNaN(t *testing.T) { Nan float32 `toml:"nan"` Inf float32 `toml:"inf"` }{float32(math.NaN()), float32(math.Inf(-1))} - encodeExpected(t, "", s1, "nan = nan\ninf = +inf\n", nil) + encodeExpected(t, "", s1, "nan = nan\ninf = inf\n", nil) encodeExpected(t, "", s2, "nan = nan\ninf = -inf\n", nil) } diff --git a/parse.go b/parse.go index 47c2544d..e31dc839 100644 --- a/parse.go +++ b/parse.go @@ -2,6 +2,7 @@ package toml import ( "fmt" + "math" "os" "strconv" "strings" @@ -323,7 +324,9 @@ func (p *parser) valueFloat(it item) (any, tomlType) { p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val) } val := strings.Replace(it.val, "_", "", -1) - if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does. + signbit := false + if val == "+nan" || val == "-nan" { + signbit = val == "-nan" val = "nan" } num, err := strconv.ParseFloat(val, 64) @@ -334,6 +337,9 @@ func (p *parser) valueFloat(it item) (any, tomlType) { p.panicItemf(it, "Invalid float value: %q", it.val) } } + if signbit { + num = math.Copysign(num, -1) + } return num, p.typeOfPrimitive(it) }