diff --git a/json/lex_number.mbt b/json/lex_number.mbt index 1155b74c8..aa03bc365 100644 --- a/json/lex_number.mbt +++ b/json/lex_number.mbt @@ -399,12 +399,16 @@ fn ParseContext::lex_number_end( // returned Double lossless. `reinterpret_as_uint64` / `reinterpret_as_int64` // are value-preserving here because both operands sit in [0, 2^53), well // inside the overlap of Int64+ and UInt64. + // + // The sign is applied after the Int64 -> Double conversion so that `-0` + // parses to the IEEE-754 negative zero (`-(0L)` is still `0L`, but + // `-(0.0)` is `-0.0`), matching the `-0.0` / `-0e0` paths below. if !scan.many_digits && scan.exponent == 0L && scan.mantissa <= SAFE_INTEGER_LIMIT.reinterpret_as_uint64() { - let v = scan.mantissa.reinterpret_as_int64() - let signed = if scan.negative { -v } else { v } - return { value: signed.to_double(), repr: None } + let v = scan.mantissa.reinterpret_as_int64().to_double() + let value = if scan.negative { -v } else { v } + return { value, repr: None } } return ctx.lex_integer_end(start, end) } diff --git a/json/lex_number_test.mbt b/json/lex_number_test.mbt index a0e261103..8d14be2a7 100644 --- a/json/lex_number_test.mbt +++ b/json/lex_number_test.mbt @@ -170,3 +170,27 @@ test "parse number with huge exponent" { ), ) } + +///| +/// Every spelling of a negative zero — including values that underflow to +/// zero — must keep the IEEE-754 sign bit. The integer spelling `-0` used to +/// lose it: the integer fast path negated an `Int64` (where `-0 == 0`) +/// before converting to `Double`. +test "parse preserves the sign of negative zero" { + fn is_negative(text : String) -> Bool raise { + guard @json.parse(text) is Number(n, ..) else { fail("not a number") } + n.reinterpret_as_int64() < 0L + } + + // Zero literals. + assert_true(is_negative("-0")) + assert_true(is_negative("-0.0")) + assert_true(is_negative("-0e0")) + assert_true(is_negative("-0.00E-7")) + assert_false(is_negative("0")) + assert_false(is_negative("0.0")) + // Negative values that underflow to zero. + assert_true(is_negative("-1e-400")) + assert_true(is_negative("-4.9e-325")) + assert_true(is_negative("-1e-999999999999999999999999999999999999")) +}