Skip to content

Commit

Permalink
Copy signbit for -nan
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
arp242 committed Sep 30, 2023
1 parent 41f8cc8 commit d17285a
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 4 deletions.
68 changes: 68 additions & 0 deletions decode_test.go
Expand Up @@ -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
Expand Down
16 changes: 14 additions & 2 deletions encode.go
Expand Up @@ -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)))
}
Expand Down
2 changes: 1 addition & 1 deletion encode_test.go
Expand Up @@ -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)
}

Expand Down
8 changes: 7 additions & 1 deletion parse.go
Expand Up @@ -2,6 +2,7 @@ package toml

import (
"fmt"
"math"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down

0 comments on commit d17285a

Please sign in to comment.