From ef138b5eeea3424a820cbf76aa4c77cfbc8937c0 Mon Sep 17 00:00:00 2001 From: zeio Date: Mon, 9 Jan 2023 16:02:39 +0300 Subject: [PATCH 1/5] Added support for decimal-styled doubles --- Package.resolved | 16 ---------------- Sources/Yams/Emitter.swift | 11 +++++++++++ Sources/Yams/Representer.swift | 19 +++++++++++++------ Tests/YamsTests/RepresenterTests.swift | 10 ++++++++++ 4 files changed, 34 insertions(+), 22 deletions(-) delete mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 3b6d58b8..00000000 --- a/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "SwiftDocCPlugin", - "repositoryURL": "https://github.com/apple/swift-docc-plugin", - "state": { - "branch": null, - "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", - "version": "1.0.0" - } - } - ] - }, - "version": 1 -} diff --git a/Sources/Yams/Emitter.swift b/Sources/Yams/Emitter.swift index f2d0ffe6..8f075122 100644 --- a/Sources/Yams/Emitter.swift +++ b/Sources/Yams/Emitter.swift @@ -223,6 +223,11 @@ public final class Emitter { case crln } + public enum NumberFormatStyle { + case scientific + case decimal + } + /// Retrieve this Emitter's binary output. public internal(set) var data = Data() @@ -254,6 +259,12 @@ public final class Emitter { /// Set the style for mappings (dictionaries) public var mappingStyle: Node.Mapping.Style = .any + + /// Set the style for formatting doubles + public static var doubleFormatStyle: NumberFormatStyle = .scientific + + public static var doubleMaximumSignificantDigits = 15 + public static var floatMaximumSignificantDigits = 7 } /// Configuration options to use when emitting YAML. diff --git a/Sources/Yams/Representer.swift b/Sources/Yams/Representer.swift index 759335bb..62efa9d7 100644 --- a/Sources/Yams/Representer.swift +++ b/Sources/Yams/Representer.swift @@ -170,7 +170,12 @@ private let iso8601WithoutZFormatter: DateFormatter = { extension Double: ScalarRepresentable { /// This value's `Node.scalar` representation. public func represented() -> Node.Scalar { - return .init(doubleFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-"), Tag(.float)) + switch Emitter.Options.doubleFormatStyle { + case .scientific: + return .init(doubleScientificFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-"), Tag(.float)) + case .decimal: + return .init(doubleDecimalFormatter.string(for: self)!, Tag(.float)) + } } } @@ -181,10 +186,10 @@ extension Float: ScalarRepresentable { } } -private func numberFormatter(with significantDigits: Int) -> NumberFormatter { +private func numberFormatter(with significantDigits: Int, style: NumberFormatter.Style = .scientific) -> NumberFormatter { let formatter = NumberFormatter() formatter.locale = Locale(identifier: "en_US") - formatter.numberStyle = .scientific + formatter.numberStyle = style formatter.usesSignificantDigits = true formatter.maximumSignificantDigits = significantDigits formatter.positiveInfinitySymbol = ".inf" @@ -194,8 +199,10 @@ private func numberFormatter(with significantDigits: Int) -> NumberFormatter { return formatter } -private let doubleFormatter = numberFormatter(with: 15) -private let floatFormatter = numberFormatter(with: 7) +private let doubleDecimalFormatter = numberFormatter(with: Emitter.Options.doubleMaximumSignificantDigits, style: .decimal) +private let doubleScientificFormatter = numberFormatter(with: Emitter.Options.doubleMaximumSignificantDigits, style: .scientific) + +private let floatFormatter = numberFormatter(with: Emitter.Options.floatMaximumSignificantDigits) // TODO: Support `Float80` // extension Float80: ScalarRepresentable {} @@ -320,7 +327,7 @@ private extension FloatingPoint where Self: CVarArg { // "%*.g" does not use scientific notation if the exponent is less than –4. // So fallback to using `NumberFormatter` if string does not uses scientific notation. guard string.lazy.suffix(5).contains("e") else { - return doubleFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-") + return doubleScientificFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-") } return string } diff --git a/Tests/YamsTests/RepresenterTests.swift b/Tests/YamsTests/RepresenterTests.swift index 4829b479..9faa616d 100644 --- a/Tests/YamsTests/RepresenterTests.swift +++ b/Tests/YamsTests/RepresenterTests.swift @@ -48,6 +48,15 @@ class RepresenterTests: XCTestCase { XCTAssertEqual(try Node(Double.leastNormalMagnitude), "2.2250738585072e-308") } + func testDecimalDoubleStyle() throws { + let cachedFormatStyle = Emitter.Options.doubleFormatStyle + Emitter.Options.doubleFormatStyle = .decimal + defer { + Emitter.Options.doubleFormatStyle = cachedFormatStyle + } + XCTAssertEqual(try Node(Double(6.85)), "6.85") + } + func testFloat() throws { XCTAssertEqual(try Node(Float.infinity), ".inf") XCTAssertEqual(try Node(-Float.infinity), "-.inf") @@ -143,6 +152,7 @@ extension RepresenterTests { ("testData", testData), ("testDate", testDate), ("testDouble", testDouble), + ("testDecimalDoubleStyle", testDecimalDoubleStyle), ("testFloat", testFloat), ("testInteger", testInteger), ("testString", testString), From ff1a98ec994b2eab2c592c6f02469f567950d743 Mon Sep 17 00:00:00 2001 From: zeio Date: Mon, 9 Jan 2023 16:05:43 +0300 Subject: [PATCH 2/5] Moved deferred statement for restoring cached format style upper --- Tests/YamsTests/RepresenterTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/YamsTests/RepresenterTests.swift b/Tests/YamsTests/RepresenterTests.swift index 9faa616d..7344926e 100644 --- a/Tests/YamsTests/RepresenterTests.swift +++ b/Tests/YamsTests/RepresenterTests.swift @@ -50,10 +50,11 @@ class RepresenterTests: XCTestCase { func testDecimalDoubleStyle() throws { let cachedFormatStyle = Emitter.Options.doubleFormatStyle - Emitter.Options.doubleFormatStyle = .decimal defer { Emitter.Options.doubleFormatStyle = cachedFormatStyle } + + Emitter.Options.doubleFormatStyle = .decimal XCTAssertEqual(try Node(Double(6.85)), "6.85") } From 7209cf56b354534fc491a5839a21b8d24896fd61 Mon Sep 17 00:00:00 2001 From: zeio Date: Mon, 9 Jan 2023 16:32:05 +0300 Subject: [PATCH 3/5] Moved new tests to a separate file --- Sources/Yams/Emitter.swift | 6 ++-- Sources/Yams/Representer.swift | 11 +++--- Tests/LinuxMain.swift | 3 +- Tests/YamsTests/DoubleEncodingTests.swift | 41 +++++++++++++++++++++++ Tests/YamsTests/RepresenterTests.swift | 31 +++++++++++------ 5 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 Tests/YamsTests/DoubleEncodingTests.swift diff --git a/Sources/Yams/Emitter.swift b/Sources/Yams/Emitter.swift index 8f075122..3bc69b25 100644 --- a/Sources/Yams/Emitter.swift +++ b/Sources/Yams/Emitter.swift @@ -263,8 +263,10 @@ public final class Emitter { /// Set the style for formatting doubles public static var doubleFormatStyle: NumberFormatStyle = .scientific - public static var doubleMaximumSignificantDigits = 15 - public static var floatMaximumSignificantDigits = 7 + public static let doubleMaximumSignificantDigits = 15 + public static let doubleMinimumFractionDigits = 1 + + public static let floatMaximumSignificantDigits = 7 } /// Configuration options to use when emitting YAML. diff --git a/Sources/Yams/Representer.swift b/Sources/Yams/Representer.swift index 62efa9d7..a0519a41 100644 --- a/Sources/Yams/Representer.swift +++ b/Sources/Yams/Representer.swift @@ -186,20 +186,23 @@ extension Float: ScalarRepresentable { } } -private func numberFormatter(with significantDigits: Int, style: NumberFormatter.Style = .scientific) -> NumberFormatter { +private func numberFormatter(with significantDigits: Int, style: NumberFormatter.Style = .scientific, minimumFractionDigits: Int = 0) -> NumberFormatter { let formatter = NumberFormatter() formatter.locale = Locale(identifier: "en_US") formatter.numberStyle = style - formatter.usesSignificantDigits = true - formatter.maximumSignificantDigits = significantDigits + if style == .scientific { + formatter.usesSignificantDigits = true + formatter.maximumSignificantDigits = significantDigits + } formatter.positiveInfinitySymbol = ".inf" formatter.negativeInfinitySymbol = "-.inf" formatter.notANumberSymbol = ".nan" formatter.exponentSymbol = "e+" + formatter.minimumFractionDigits = minimumFractionDigits return formatter } -private let doubleDecimalFormatter = numberFormatter(with: Emitter.Options.doubleMaximumSignificantDigits, style: .decimal) +private let doubleDecimalFormatter = numberFormatter(with: Emitter.Options.doubleMaximumSignificantDigits, style: .decimal, minimumFractionDigits: Emitter.Options.doubleMinimumFractionDigits) private let doubleScientificFormatter = numberFormatter(with: Emitter.Options.doubleMaximumSignificantDigits, style: .scientific) private let floatFormatter = numberFormatter(with: Emitter.Options.floatMaximumSignificantDigits) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index a8d4fbf6..38d30287 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -13,5 +13,6 @@ XCTMain([ testCase(ResolverTests.allTests), testCase(SpecTests.allTests), testCase(StringTests.allTests), - testCase(YamlErrorTests.allTests) + testCase(YamlErrorTests.allTests), + testCase(DoubleEncodingTests.allTests) ]) diff --git a/Tests/YamsTests/DoubleEncodingTests.swift b/Tests/YamsTests/DoubleEncodingTests.swift new file mode 100644 index 00000000..7366b32b --- /dev/null +++ b/Tests/YamsTests/DoubleEncodingTests.swift @@ -0,0 +1,41 @@ +import Foundation +import XCTest +import Yams + +class DoubleEncodingTests: XCTestCase { + // private let cachedFormatStyle: NumberFormatter.Style + + override class func setUp() { + // cachedFormatStyle = Emitter.Options.doubleFormatStyle + Emitter.Options.doubleFormatStyle = .decimal + } + + func testDecimalDoubleStyle() throws { + // let cachedFormatStyle = Emitter.Options.doubleFormatStyle + // defer { + // Emitter.Options.doubleFormatStyle = cachedFormatStyle + // } + + // Emitter.Options.doubleFormatStyle = .decimal + XCTAssertEqual(try Node(Double(6.85)), "6.85") + } + + func testMinimumFractionDigits() throws { + // let cachedFormatStyle = Emitter.Options.doubleFormatStyle + // defer { + // Emitter.Options.doubleFormatStyle = cachedFormatStyle + // } + + // Emitter.Options.doubleFormatStyle = .decimal + XCTAssertEqual(try Node(Double(6.0)), "6.0") + } +} + +extension DoubleEncodingTests { + static var allTests: [(String, (DoubleEncodingTests) -> () throws -> Void)] { + return [ + ("testDecimalDoubleStyle", testDecimalDoubleStyle), + ("testMinimumFractionDigits", testMinimumFractionDigits), + ] + } +} diff --git a/Tests/YamsTests/RepresenterTests.swift b/Tests/YamsTests/RepresenterTests.swift index 7344926e..2def3cb3 100644 --- a/Tests/YamsTests/RepresenterTests.swift +++ b/Tests/YamsTests/RepresenterTests.swift @@ -48,15 +48,25 @@ class RepresenterTests: XCTestCase { XCTAssertEqual(try Node(Double.leastNormalMagnitude), "2.2250738585072e-308") } - func testDecimalDoubleStyle() throws { - let cachedFormatStyle = Emitter.Options.doubleFormatStyle - defer { - Emitter.Options.doubleFormatStyle = cachedFormatStyle - } - - Emitter.Options.doubleFormatStyle = .decimal - XCTAssertEqual(try Node(Double(6.85)), "6.85") - } + // func testDecimalDoubleStyle() throws { + // let cachedFormatStyle = Emitter.Options.doubleFormatStyle + // defer { + // Emitter.Options.doubleFormatStyle = cachedFormatStyle + // } + + // Emitter.Options.doubleFormatStyle = .decimal + // XCTAssertEqual(try Node(Double(6.85)), "6.85") + // } + + // func testMinimumFractionDigits() throws { + // let cachedFormatStyle = Emitter.Options.doubleFormatStyle + // defer { + // Emitter.Options.doubleFormatStyle = cachedFormatStyle + // } + + // Emitter.Options.doubleFormatStyle = .decimal + // XCTAssertEqual(try Node(Double(6.0)), "6.0") + // } func testFloat() throws { XCTAssertEqual(try Node(Float.infinity), ".inf") @@ -153,7 +163,8 @@ extension RepresenterTests { ("testData", testData), ("testDate", testDate), ("testDouble", testDouble), - ("testDecimalDoubleStyle", testDecimalDoubleStyle), + // ("testDecimalDoubleStyle", testDecimalDoubleStyle), + // ("testMinimumFractionDigits", testMinimumFractionDigits), ("testFloat", testFloat), ("testInteger", testInteger), ("testString", testString), From c73e2499d6b7d0b23cf89a977defa645e3b29638 Mon Sep 17 00:00:00 2001 From: zeio Date: Mon, 9 Jan 2023 16:34:19 +0300 Subject: [PATCH 4/5] Deleted redundant comments --- Tests/YamsTests/DoubleEncodingTests.swift | 15 --------------- Tests/YamsTests/RepresenterTests.swift | 22 ---------------------- 2 files changed, 37 deletions(-) diff --git a/Tests/YamsTests/DoubleEncodingTests.swift b/Tests/YamsTests/DoubleEncodingTests.swift index 7366b32b..e9f6a986 100644 --- a/Tests/YamsTests/DoubleEncodingTests.swift +++ b/Tests/YamsTests/DoubleEncodingTests.swift @@ -3,30 +3,15 @@ import XCTest import Yams class DoubleEncodingTests: XCTestCase { - // private let cachedFormatStyle: NumberFormatter.Style - override class func setUp() { - // cachedFormatStyle = Emitter.Options.doubleFormatStyle Emitter.Options.doubleFormatStyle = .decimal } func testDecimalDoubleStyle() throws { - // let cachedFormatStyle = Emitter.Options.doubleFormatStyle - // defer { - // Emitter.Options.doubleFormatStyle = cachedFormatStyle - // } - - // Emitter.Options.doubleFormatStyle = .decimal XCTAssertEqual(try Node(Double(6.85)), "6.85") } func testMinimumFractionDigits() throws { - // let cachedFormatStyle = Emitter.Options.doubleFormatStyle - // defer { - // Emitter.Options.doubleFormatStyle = cachedFormatStyle - // } - - // Emitter.Options.doubleFormatStyle = .decimal XCTAssertEqual(try Node(Double(6.0)), "6.0") } } diff --git a/Tests/YamsTests/RepresenterTests.swift b/Tests/YamsTests/RepresenterTests.swift index 2def3cb3..4829b479 100644 --- a/Tests/YamsTests/RepresenterTests.swift +++ b/Tests/YamsTests/RepresenterTests.swift @@ -48,26 +48,6 @@ class RepresenterTests: XCTestCase { XCTAssertEqual(try Node(Double.leastNormalMagnitude), "2.2250738585072e-308") } - // func testDecimalDoubleStyle() throws { - // let cachedFormatStyle = Emitter.Options.doubleFormatStyle - // defer { - // Emitter.Options.doubleFormatStyle = cachedFormatStyle - // } - - // Emitter.Options.doubleFormatStyle = .decimal - // XCTAssertEqual(try Node(Double(6.85)), "6.85") - // } - - // func testMinimumFractionDigits() throws { - // let cachedFormatStyle = Emitter.Options.doubleFormatStyle - // defer { - // Emitter.Options.doubleFormatStyle = cachedFormatStyle - // } - - // Emitter.Options.doubleFormatStyle = .decimal - // XCTAssertEqual(try Node(Double(6.0)), "6.0") - // } - func testFloat() throws { XCTAssertEqual(try Node(Float.infinity), ".inf") XCTAssertEqual(try Node(-Float.infinity), "-.inf") @@ -163,8 +143,6 @@ extension RepresenterTests { ("testData", testData), ("testDate", testDate), ("testDouble", testDouble), - // ("testDecimalDoubleStyle", testDecimalDoubleStyle), - // ("testMinimumFractionDigits", testMinimumFractionDigits), ("testFloat", testFloat), ("testInteger", testInteger), ("testString", testString), From 12a673d6e47dbd627de403fb032ad8b00aab47b5 Mon Sep 17 00:00:00 2001 From: zeio Date: Mon, 9 Jan 2023 17:17:00 +0300 Subject: [PATCH 5/5] Added box handling --- Sources/Yams/Representer.swift | 4 ++++ Tests/YamsTests/DoubleEncodingTests.swift | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/Sources/Yams/Representer.swift b/Sources/Yams/Representer.swift index a0519a41..861ffac4 100644 --- a/Sources/Yams/Representer.swift +++ b/Sources/Yams/Representer.swift @@ -324,6 +324,10 @@ extension Float: YAMLEncodable { private extension FloatingPoint where Self: CVarArg { var formattedStringForCodable: String { + if Emitter.Options.doubleFormatStyle == .decimal { + return doubleDecimalFormatter.string(for: self)! + } + // Since `NumberFormatter` creates a string with insufficient precision for Decode, // it uses with `String(format:...)` let string = String(format: "%.*g", DBL_DECIMAL_DIG, self) diff --git a/Tests/YamsTests/DoubleEncodingTests.swift b/Tests/YamsTests/DoubleEncodingTests.swift index e9f6a986..c6d778b0 100644 --- a/Tests/YamsTests/DoubleEncodingTests.swift +++ b/Tests/YamsTests/DoubleEncodingTests.swift @@ -14,6 +14,14 @@ class DoubleEncodingTests: XCTestCase { func testMinimumFractionDigits() throws { XCTAssertEqual(try Node(Double(6.0)), "6.0") } + + func testDecimalDoubleStyleBox() throws { + print(try Double(6.85).box(), "6.85") + } + + func testMinimumFractionDigitsBox() throws { + print(try Double(6.0).box(), "6.0") + } } extension DoubleEncodingTests { @@ -21,6 +29,8 @@ extension DoubleEncodingTests { return [ ("testDecimalDoubleStyle", testDecimalDoubleStyle), ("testMinimumFractionDigits", testMinimumFractionDigits), + ("testDecimalDoubleStyleBox", testDecimalDoubleStyleBox), + ("testMinimumFractionDigitsBox", testMinimumFractionDigitsBox) ] } }