From fb868e2c6b47aac3e96272b6f85c3f78c595efcb Mon Sep 17 00:00:00 2001 From: deadcafe Date: Wed, 14 Oct 2026 19:44:06 +0530 Subject: [PATCH] Correctly round Double(PseudoDecimal) Motivation: Double(PseudoDecimal) multiplied the mantissa by pow(10, exponent). For negative exponents that power of ten is not exactly representable, so the result is rounded twice and can be off by one ULP. Decoding "0.3" as a Double produced 0.30000000000000004. Modifications: Divide the mantissa by the exactly representable power of ten instead, so the result is rounded once. Add a decoder test covering the case. Result: Double(PseudoDecimal) returns the nearest Double to the decimal value. --- Sources/RawStructuredFieldValues/PseudoDecimal.swift | 3 ++- .../StructuredFieldDecoderTests.swift | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/RawStructuredFieldValues/PseudoDecimal.swift b/Sources/RawStructuredFieldValues/PseudoDecimal.swift index e5cf92c..d2c2ec4 100644 --- a/Sources/RawStructuredFieldValues/PseudoDecimal.swift +++ b/Sources/RawStructuredFieldValues/PseudoDecimal.swift @@ -226,6 +226,7 @@ extension String { // Swift Numerics would let us do this for all the floating point types. extension Double { public init(_ pseudoDecimal: PseudoDecimal) { - self = Double(pseudoDecimal.mantissa) * pow(10, Double(pseudoDecimal.exponent)) + // Divide by an exact power of ten: multiplying by an inexact negative power of ten rounds twice. + self = Double(pseudoDecimal.mantissa) / pow(10, -Double(pseudoDecimal.exponent)) } } diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift index d56350a..5082d9c 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift @@ -182,6 +182,14 @@ final class StructuredFieldDecoderTests: XCTestCase { XCTAssertEqual(ItemField(Double(5.0)), try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8))) } + func testDecodingDecimalAsDoubleIsCorrectlyRounded() throws { + XCTAssertEqual(ItemField(Double(0.3)), try StructuredFieldValueDecoder().decode(from: Array("0.3".utf8))) + XCTAssertEqual( + ItemField(Double(-19.993)), + try StructuredFieldValueDecoder().decode(from: Array("-19.993".utf8)) + ) + } + func testAskingForTheWrongType() throws { let headerField = "gzip" let intField = "5"