Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/RawStructuredFieldValues/PseudoDecimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public struct PseudoDecimal: Hashable, Sendable {
}
}

private static func validate(mantissa: Int64, exponent: Int8) throws {
package static func validate(mantissa: Int64, exponent: Int8) throws {
// We compare against our upper and lower bounds. The maximum allowable magnitude of the mantissa is contingent
// on the exponent.
switch exponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,7 @@ extension _StructuredFieldEncoder: SingleValueEncodingContainer {
}

func encode(_ data: Decimal) throws {
let significand = (data.significand.magnitude as NSNumber).intValue // Yes, really.
guard let exponent = Int8(exactly: data.exponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}

let pd = PseudoDecimal(mantissa: significand * (data.isSignMinus ? -1 : 1), exponent: Int(exponent))
let pd = try PseudoDecimal(data)
try self.currentStackEntry.storage.insertBareItem(.decimal(pd))
}

Expand Down Expand Up @@ -469,12 +464,7 @@ extension _StructuredFieldEncoder {
}

func append(_ value: Decimal) throws {
let significand = (value.significand.magnitude as NSNumber).intValue // Yes, really.
guard let exponent = Int8(exactly: value.exponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}

let pd = PseudoDecimal(mantissa: significand * (value.isSignMinus ? -1 : 1), exponent: Int(exponent))
let pd = try PseudoDecimal(value)
try self.currentStackEntry.storage.appendBareItem(.decimal(pd))
}

Expand Down Expand Up @@ -652,12 +642,7 @@ extension _StructuredFieldEncoder {
}

func encode(_ value: Decimal, forKey key: String) throws {
let significand = (value.significand.magnitude as NSNumber).intValue // Yes, really.
guard let exponent = Int8(exactly: value.exponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}

let pd = PseudoDecimal(mantissa: significand * (value.isSignMinus ? -1 : 1), exponent: Int(exponent))
let pd = try PseudoDecimal(value)
try self.currentStackEntry.storage.insertBareItem(.decimal(pd), atKey: key)
}

Expand Down Expand Up @@ -994,3 +979,28 @@ extension Item {
self.init(bareItem: partialItem.bareItem!, parameters: partialItem.parameters)
}
}

extension PseudoDecimal {
fileprivate init(_ decimal: Decimal) throws {
var significand = (decimal.significand.magnitude as NSNumber).intValue // Yes, really.
var decimalExponent = decimal.exponent

// Foundation stores trailing zeros as a positive exponent (100 is 1e2), so fold them into the significand.
while decimalExponent > 0 {
let (scaled, overflow) = significand.multipliedReportingOverflow(by: 10)
guard !overflow else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}
significand = scaled
decimalExponent -= 1
}

guard let exponent = Int8(exactly: decimalExponent) else {
throw StructuredHeaderError.invalidIntegerOrDecimal
}

let mantissa = significand * (decimal.isSignMinus ? -1 : 1)
try Self.validate(mantissa: Int64(mantissa), exponent: exponent)
self.init(mantissa: mantissa, exponent: Int(exponent))
}
}
10 changes: 10 additions & 0 deletions Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ final class StructuredFieldEncoderTests: XCTestCase {
)
}

func testEncodeDecimalWithPositiveExponent() throws {
// Foundation stores trailing zeros as a positive exponent, so 100 is 1e2.
let encoder = StructuredFieldValueEncoder()
XCTAssertEqual(Array("100.0".utf8), try encoder.encode(ItemField(Decimal(100))))
XCTAssertEqual(Array("900000000000.0".utf8), try encoder.encode(ItemField(Decimal(string: "9e11")!)))
XCTAssertEqual(Array("-100.0, 100.0".utf8), try encoder.encode(List([Decimal(-100), Decimal(100)])))
XCTAssertThrowsError(try encoder.encode(ItemField(Decimal(string: "1e12")!)))
XCTAssertThrowsError(try encoder.encode(List([Decimal(string: "1e12")!])))
}

func testEncodeKeyedItemHeader() throws {
struct KeyedItem<ItemType: Codable & Equatable>: Equatable, StructuredFieldValue {
static var structuredFieldType: StructuredFieldType {
Expand Down