Modbus: handle NaN values (#31011)

This commit is contained in:
premultiply 2026-07-19 12:44:32 +02:00 • committed by GitHub
parent 2a86cc44b1
commit cc6bef6b82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 26 additions and 9 deletions

View file

@ -5,6 +5,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"math"
"slices"
"strconv"
"strings"
@ -81,7 +82,7 @@ func decodeNaN16(f func(b []byte) float64, nan ...uint16) func(b []byte) float64
return func(b []byte) float64 {
u := binary.BigEndian.Uint16(b)
if slices.Contains(nan, u) {
return 0
return math.NaN()
}
return f(b)
}
@ -91,7 +92,7 @@ func decodeNaN32(f func(b []byte) float64, nan ...uint32) func(b []byte) float64
return func(b []byte) float64 {
u := binary.BigEndian.Uint32(b)
if slices.Contains(nan, u) {
return 0
return math.NaN()
}
return f(b)
}
@ -101,7 +102,7 @@ func decodeNaN64(f func(b []byte) float64, nan ...uint64) func(b []byte) float64
return func(b []byte) float64 {
u := binary.BigEndian.Uint64(b)
if slices.Contains(nan, u) {
return 0
return math.NaN()
}
return f(b)
}

View file

@ -249,7 +249,8 @@ func (r Register) Operation() (RegisterOperation, error) {
func asFloat64[T constraints.Signed | constraints.Unsigned | constraints.Float](f func([]byte) T) func([]byte) float64 {
return func(v []byte) float64 {
res := float64(f(v))
if math.IsNaN(res) || math.IsInf(res, 0) {
// keep NaN so callers can map it to "not available"; sanitize Inf to 0
if math.IsInf(res, 0) {
res = 0
}
return res

View file

@ -71,10 +71,13 @@ func TestDecoding(t *testing.T) {
out float64
}{
{Register{Decode: "float32"}, []byte{0x4b, 0x3c, 0x61, 0x4e}, 12345678},
{Register{Decode: "float32"}, []byte{0xff, 0xff, 0xff, 0x7f}, 0}, // NaN
{Register{Decode: "float32"}, []byte{0xff, 0xff, 0xff, 0x7f}, math.NaN()}, // IEEE NaN preserved
{Register{Decode: "float32s"}, []byte{0x61, 0x4e, 0x4b, 0x3c}, 12345678},
{Register{Decode: "float32s"}, []byte{0xff, 0x7f, 0xff, 0xff}, 0}, // NaN swapped
{Register{Decode: "float32nans"}, []byte{0xff, 0xff, 0xff, 0x7f}, 0}, // NaN
{Register{Decode: "float32s"}, []byte{0xff, 0x7f, 0xff, 0xff}, math.NaN()}, // IEEE NaN swapped preserved
{Register{Decode: "float32nans"}, []byte{0xff, 0xff, 0xff, 0x7f}, math.NaN()},
{Register{Decode: "uint16nan"}, []byte{0xff, 0xff}, math.NaN()},
{Register{Decode: "uint32nan"}, []byte{0xff, 0xff, 0xff, 0xff}, math.NaN()},
{Register{Decode: "uint64nan"}, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, math.NaN()},
{Register{Decode: "uint64"}, []byte{0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01}, 0x0004000300020001},
{Register{Decode: "uint64s"}, []byte{0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01}, 0x0001000200030004},
}
@ -82,6 +85,12 @@ func TestDecoding(t *testing.T) {
for _, tc := range tc {
fun, err := tc.r.DecodeFunc()
require.NoError(t, err, tc)
require.Equal(t, tc.out, fun(tc.in), tc)
out := fun(tc.in)
if math.IsNaN(tc.out) {
require.True(t, math.IsNaN(out), tc)
continue
}
require.Equal(t, tc.out, out, tc)
}
}