diff --git a/CHANGELOG.md b/CHANGELOG.md index 4656659..cf00cbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.14.3 (February 29, 2024) + +* `msgpack`: Fixed edge-case bug that could cause loss of floating point precision when round-tripping due to incorrectly using a MessagePack integer to represent a large non-integral number. [#176](https://github.com/zclconf/go-cty/pull/176) +* `cty`: Fixed some false-negative numeric equality test results by comparing numbers as integers when possible. [#176](https://github.com/zclconf/go-cty/pull/176) + # 1.14.2 (January 23, 2024) * `convert`: Converting from an unknown map value to an object type now correctly handles the situation where the map element type disagrees with an _optional_ attribute of the target type, since when a map value is unknown we don't yet know which keys it has and thus cannot predict what subset of the elements will get converted as attributes in the resulting object. ([#175](https://github.com/zclconf/go-cty/pull/175)) diff --git a/cty/msgpack/marshal.go b/cty/msgpack/marshal.go index 27da762..274d559 100644 --- a/cty/msgpack/marshal.go +++ b/cty/msgpack/marshal.go @@ -32,7 +32,10 @@ func Marshal(val cty.Value, ty cty.Type) ([]byte, error) { var buf bytes.Buffer enc := msgpack.NewEncoder(&buf) enc.UseCompactInts(true) - enc.UseCompactFloats(true) + + // UseCompactFloats can fail on some platforms due to undefined behavior of + // float conversions + enc.UseCompactFloats(false) err := marshal(val, ty, path, enc) if err != nil { diff --git a/cty/msgpack/roundtrip_test.go b/cty/msgpack/roundtrip_test.go index 497919b..5b73ea9 100644 --- a/cty/msgpack/roundtrip_test.go +++ b/cty/msgpack/roundtrip_test.go @@ -84,6 +84,18 @@ func TestRoundTrip(t *testing.T) { bigNumberVal, cty.Number, }, + { + cty.MustParseNumberVal("9223372036854775807"), + cty.Number, + }, + { + cty.MustParseNumberVal("9223372036854775808"), + cty.Number, + }, + { + cty.MustParseNumberVal("9223372036854775809"), + cty.Number, + }, { awkwardFractionVal, cty.Number, diff --git a/cty/primitive_type.go b/cty/primitive_type.go index 3ce2540..2beea65 100644 --- a/cty/primitive_type.go +++ b/cty/primitive_type.go @@ -1,6 +1,8 @@ package cty -import "math/big" +import ( + "math/big" +) // primitiveType is the hidden implementation of the various primitive types // that are exposed as variables in this package. @@ -77,6 +79,18 @@ func rawNumberEqual(a, b *big.Float) bool { case a.Sign() != b.Sign(): return false default: + // First check if these are integers, and compare them directly. Floats + // need a more nuanced approach. + aInt, aAcc := a.Int(nil) + bInt, bAcc := b.Int(nil) + if aAcc != bAcc { + // only one is an exact integer value, so they can't be equal + return false + } + if aAcc == big.Exact { + return aInt.Cmp(bInt) == 0 + } + // This format and precision matches that used by cty/json.Marshal, // and thus achieves our definition of "two numbers are equal if // we'd use the same JSON serialization for both of them". diff --git a/cty/value_ops_test.go b/cty/value_ops_test.go index d3ad902..d4fb6a5 100644 --- a/cty/value_ops_test.go +++ b/cty/value_ops_test.go @@ -91,6 +91,11 @@ func TestValueEquals(t *testing.T) { NumberFloatVal(1.22222), BoolVal(true), }, + { + MustParseNumberVal("9223372036854775808"), + NumberFloatVal(float64(9223372036854775808)), + BoolVal(true), + }, // Strings {