Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.
Merged
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
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ jobs:
with:
platform: ${{ matrix.platform }}
swift: ~${{ matrix.swift }}
warnings-as-errors: true
linux:
name: Linux (Swift ${{ matrix.swift }})
runs-on: ubuntu-latest
Expand All @@ -53,4 +52,4 @@ jobs:
image: swift:${{ matrix.swift }}
steps:
- uses: actions/checkout@v2
- run: swift test --enable-test-discovery --parallel -Xswiftc -warnings-as-errors
- run: swift test --enable-test-discovery --parallel
22 changes: 22 additions & 0 deletions Sources/PreciseDecimal/PreciseDecimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,49 @@ import Foundation
public struct PreciseDecimal {
public var value: Decimal

@available(*, deprecated, message: "PreciseDecimal.init(_:) is deprecated and will be removed in version 2. Use PreciseDecimal.init(string:) instead.")
public init<I: FixedWidthInteger>(_ value: I) {
self.value = Decimal(precise: value)
}

@available(*, deprecated, message: "PreciseDecimal.init(_:) loses precision past the 6th decimal place, so it is deprecated and will be removed in version 2. Use PreciseDecimal.init(string:) instead.")
public init(_ value: Double) {
self.value = Decimal(precise: value)
}

public init?(string: String) {
guard let decimal = Decimal(string: string) else {
return nil
}
self.value = decimal
}
}

extension PreciseDecimal: Hashable {}

extension PreciseDecimal: ExpressibleByIntegerLiteral {
@available(*, deprecated, message: "Initialization via integer literals is deprecated and will be removed in version 2. Use a String literal instead.")
public init(integerLiteral value: IntegerLiteralType) {
self.init(Double(value))
}
}

extension PreciseDecimal: ExpressibleByFloatLiteral {
@available(*, deprecated, message: "Initialization via floating-point literals loses precision past the 6th decimal place, so it is deprecated and will be removed in version 2. Use a String literal instead.")
public init(floatLiteral value: FloatLiteralType) {
self.init(value)
}
}

extension PreciseDecimal: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
guard let _self = Self(string: value) else {
fatalError("Invalid PreciseDecimal string literal: \(value)")
}
self = _self
}
}

extension PreciseDecimal: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
Expand All @@ -43,13 +63,15 @@ extension PreciseDecimal: CustomDebugStringConvertible {
}

extension Decimal {
@available(*, deprecated, message: "PreciseDecimal.init(precise:) loses precision past the 6th decimal place, so it is deprecated and will be removed in version 2. Use PreciseDecimal.init(string:) instead.")
public init<I: FixedWidthInteger>(precise value: I) {
guard let decimal = Self(string: String(value)) else {
preconditionFailure("Failed to convert FixedWidthInteger '\(value)' to Decimal")
}
self = decimal
}

@available(*, deprecated, message: "PreciseDecimal.init(precise:) loses precision past the 6th decimal place, so it is deprecated and will be removed in version 2. Use PreciseDecimal.init(string:) instead.")
public init(precise value: Double) {
guard let decimal = Self(string: String(value)) else {
preconditionFailure("Failed to convert Double '\(value)' to Decimal")
Expand Down
101 changes: 97 additions & 4 deletions Tests/PreciseDecimalTests/InitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import XCTest
import PreciseDecimal

final class InitTests: XCTestCase {
func testIntegers() {
func testIntegers() throws {
assertInteger(0, "0")
assertInteger(1, "1")
assertInteger(2, "2")
Expand Down Expand Up @@ -32,9 +32,39 @@ final class InitTests: XCTestCase {
assertLiteral(42, "42")
assertLiteral(100, "100")
assertLiteral(69420, "69420")

try assertString("0", "0")
try assertString("1", "1")
try assertString("2", "2")
try assertString("3", "3")
try assertString("4", "4")
try assertString("5", "5")
try assertString("6", "6")
try assertString("7", "7")
try assertString("8", "8")
try assertString("9", "9")
try assertString("10", "10")
try assertString("42", "42")
try assertString("100", "100")
try assertString("69420", "69420")

assertLiteral("0", "0")
assertLiteral("1", "1")
assertLiteral("2", "2")
assertLiteral("3", "3")
assertLiteral("4", "4")
assertLiteral("5", "5")
assertLiteral("6", "6")
assertLiteral("7", "7")
assertLiteral("8", "8")
assertLiteral("9", "9")
assertLiteral("10", "10")
assertLiteral("42", "42")
assertLiteral("100", "100")
assertLiteral("69420", "69420")
}

func testOneDecimal() {
func testOneDecimal() throws {
assertDouble(0.0, "0")
assertDouble(0.2, "0.2")
assertDouble(0.5, "0.5")
Expand All @@ -54,9 +84,29 @@ final class InitTests: XCTestCase {
assertLiteral(1.5, "1.5")
assertLiteral(420.6, "420.6")
assertLiteral(42069.4, "42069.4")

try assertString("0.0", "0")
try assertString("0.2", "0.2")
try assertString("0.5", "0.5")
try assertString("0.7", "0.7")
try assertString("1.0", "1")
try assertString("1.2", "1.2")
try assertString("1.5", "1.5")
try assertString("420.6", "420.6")
try assertString("42069.4", "42069.4")

assertLiteral("0.0", "0")
assertLiteral("0.2", "0.2")
assertLiteral("0.5", "0.5")
assertLiteral("0.7", "0.7")
assertLiteral("1.0", "1")
assertLiteral("1.2", "1.2")
assertLiteral("1.5", "1.5")
assertLiteral("420.6", "420.6")
assertLiteral("42069.4", "42069.4")
}

func testTwoDecimals() {
func testTwoDecimals() throws {
assertDouble(0.00, "0")
assertDouble(0.25, "0.25")
assertDouble(0.50, "0.5")
Expand All @@ -72,9 +122,25 @@ final class InitTests: XCTestCase {
assertLiteral(1.00, "1")
assertLiteral(1.25, "1.25")
assertLiteral(1.50, "1.5")

try assertString("0.00", "0")
try assertString("0.25", "0.25")
try assertString("0.50", "0.5")
try assertString("0.75", "0.75")
try assertString("1.00", "1")
try assertString("1.25", "1.25")
try assertString("1.50", "1.5")

assertLiteral("0.00", "0")
assertLiteral("0.25", "0.25")
assertLiteral("0.50", "0.5")
assertLiteral("0.75", "0.75")
assertLiteral("1.00", "1")
assertLiteral("1.25", "1.25")
assertLiteral("1.50", "1.5")
}

func testThreeDecimals() {
func testThreeDecimals() throws {
assertDouble(1.111, "1.111")
assertDouble(2.222, "2.222")
assertDouble(3.133, "3.133")
Expand All @@ -88,6 +154,24 @@ final class InitTests: XCTestCase {
assertLiteral(3.333, "3.333")
assertLiteral(69.420, "69.42")
assertLiteral(69.421, "69.421")

try assertString("1.111", "1.111")
try assertString("2.222", "2.222")
try assertString("3.133", "3.133")
try assertString("3.333", "3.333")
try assertString("69.420", "69.42")
try assertString("69.421", "69.421")

assertLiteral("1.111", "1.111")
assertLiteral("2.222", "2.222")
assertLiteral("3.133", "3.133")
assertLiteral("3.333", "3.333")
assertLiteral("69.420", "69.42")
assertLiteral("69.421", "69.421")
}

func testInvalidInput() {
XCTAssertNil(PreciseDecimal(string: "abc"))
}
}

Expand Down Expand Up @@ -118,6 +202,15 @@ private extension InitTests {
)
}

func assertString(_ sut: String, _ expected: String, line: UInt = #line) throws {
let decimal = try XCTUnwrap(PreciseDecimal(string: sut)?.value)
assertDecimal(
decimal,
expected,
line: line
)
}

func assertLiteral(_ preciseDecimal: PreciseDecimal, _ string: String, line: UInt = #line) {
assertDecimal(
preciseDecimal.value,
Expand Down