`Decimal(sign:exponent:significand:)` causes unexpected sign if `significand` is negative

2 min read Original article ↗

With the release of iOS 18 Developer Beta 5 and Public Beta 3, my team received reports from customers that all negative values in our app were showing as positives, which led to major confusion.

We narrowed down the issue to a change to the initializer for Decimal: Decimal(sign:exponent:significand). Prior to Beta 5, the sign passed into the initializer would be the sign of the Decimal. Passing .plus would result in a positive decimal, and passing .minus would result in a negative Decimal. This is regardless of the sign of the significant. In Beta 5, it seems that the sign passed into the init, and the sign of the significand are now negated. This means that passing .minus for sign and a negative Decimal for significand results in an unexpectedly positive Decimal value in Beta 5 alone. This behavior does not seem to be explicitly documented.

I created a quick playground to illustrate the issue. Here's the code:

// Expected Value: 20
// Actual Value: 20
let positiveDecimal = Decimal(sign: .plus, exponent: 1, significand: 2)

// Expected Value: 20
// Actual Value (15.4.0, 16.0 Beta 4): 20
// Actual Value (16.0 Beta 5): -20
let positiveDecimalWithNegativeSignificand = Decimal(sign: .plus, exponent: 1, significand: -2)

// Expected Value: -20
// Actual Value: -20
let negativeDecimal = Decimal(sign: .minus, exponent: 1, significand: 2)

// Expected Value: -20
// Actual Value (15.4.0, 16.0 Beta 4): -20
// Actual Value (16.0 Beta 5): 20
let negativeDecimalWithNegativeSignificand = Decimal(sign: .minus, exponent: 1, significand: -2)

I've tracked down the issue to a PR in the swift-foundation repo from 3 weeks ago, which seems to pull a commit from the swift-corelibs-foundation repo.