Skip to content

Commit 139a14b

Browse files
committed
SR-7650: Incorrect result adding and subtracting Decimals
- For integerAddition, propogate the carry if set. - For integerSubtraction, compute the borrow and set .underflow on error. - When initialising a Decimal() from an Int or UInt dont convert via a double.
1 parent 84d9605 commit 139a14b

File tree

2 files changed

+76
-32
lines changed

2 files changed

+76
-32
lines changed

Foundation/Decimal.swift

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,39 @@ extension Decimal {
441441
self.compact()
442442
}
443443
}
444+
444445
public init(_ value: UInt64) {
445-
self.init(Double(value))
446+
self = Decimal()
447+
if value == 0 {
448+
return
449+
}
450+
451+
var compactValue = value
452+
var exponent: Int32 = 0
453+
while compactValue % 10 == 0 {
454+
compactValue = compactValue / 10
455+
exponent = exponent + 1
456+
}
457+
_isCompact = 1
458+
_exponent = exponent
459+
460+
let wordCount = ((UInt64.bitWidth - compactValue.leadingZeroBitCount) + (UInt16.bitWidth - 1)) / UInt16.bitWidth
461+
_length = UInt32(wordCount)
462+
_mantissa.0 = UInt16(truncatingIfNeeded: compactValue >> 0)
463+
_mantissa.1 = UInt16(truncatingIfNeeded: compactValue >> 16)
464+
_mantissa.2 = UInt16(truncatingIfNeeded: compactValue >> 32)
465+
_mantissa.3 = UInt16(truncatingIfNeeded: compactValue >> 48)
446466
}
467+
447468
public init(_ value: Int64) {
448-
self.init(Double(value))
469+
if value < 0 {
470+
self.init(value == Int64.min ? UInt64(Int64.max) + 1 : UInt64(abs(value)))
471+
_isNegative = 1
472+
} else {
473+
self.init(UInt64(value))
474+
}
449475
}
476+
450477
public init(_ value: UInt) {
451478
self.init(UInt64(value))
452479
}
@@ -1164,16 +1191,14 @@ public func NSDecimalAdd(_ result: UnsafeMutablePointer<Decimal>, _ leftOperand:
11641191
}
11651192

11661193
fileprivate func integerAdd(_ result: inout WideDecimal, _ left: inout Decimal, _ right: inout Decimal) -> NSDecimalNumber.CalculationError {
1167-
var i:UInt32 = 0
1168-
var carry:UInt16 = 0
1169-
var accumulator:UInt32 = 0
1170-
1171-
let c:UInt32 = min(left._length, right._length)
1194+
var i: UInt32 = 0
1195+
var carry: UInt16 = 0
1196+
let c: UInt32 = min(left._length, right._length)
11721197

11731198
while i < c {
11741199
let li = UInt32(left[i])
11751200
let ri = UInt32(right[i])
1176-
accumulator = li + ri + UInt32(carry)
1201+
let accumulator = li + ri + UInt32(carry)
11771202
carry = UInt16(truncatingIfNeeded:accumulator >> 16)
11781203
result[i] = UInt16(truncatingIfNeeded:accumulator)
11791204
i += 1
@@ -1182,7 +1207,7 @@ fileprivate func integerAdd(_ result: inout WideDecimal, _ left: inout Decimal,
11821207
while i < left._length {
11831208
if carry != 0 {
11841209
let li = UInt32(left[i])
1185-
accumulator = li + UInt32(carry)
1210+
let accumulator = li + UInt32(carry)
11861211
carry = UInt16(truncatingIfNeeded:accumulator >> 16)
11871212
result[i] = UInt16(truncatingIfNeeded:accumulator)
11881213
i += 1
@@ -1197,7 +1222,7 @@ fileprivate func integerAdd(_ result: inout WideDecimal, _ left: inout Decimal,
11971222
while i < right._length {
11981223
if carry != 0 {
11991224
let ri = UInt32(right[i])
1200-
accumulator = ri + UInt32(carry)
1225+
let accumulator = ri + UInt32(carry)
12011226
carry = UInt16(truncatingIfNeeded:accumulator >> 16)
12021227
result[i] = UInt16(truncatingIfNeeded:accumulator)
12031228
i += 1
@@ -1209,17 +1234,17 @@ fileprivate func integerAdd(_ result: inout WideDecimal, _ left: inout Decimal,
12091234
break
12101235
}
12111236
}
1237+
result._length = i
12121238

12131239
if carry != 0 {
1214-
if result._length < i {
1215-
result._length = i
1216-
return .overflow
1217-
} else {
1218-
result[i] = carry
1219-
i += 1
1220-
}
1240+
result[i] = carry
1241+
i += 1
1242+
result._length = i
12211243
}
1222-
result._length = i;
1244+
if i > Decimal.maxSize {
1245+
return .overflow
1246+
}
1247+
12231248
return .noError;
12241249
}
12251250

@@ -1231,26 +1256,24 @@ fileprivate func integerAdd(_ result: inout WideDecimal, _ left: inout Decimal,
12311256
// give b-a...
12321257
//
12331258
fileprivate func integerSubtract(_ result: inout Decimal, _ left: inout Decimal, _ right: inout Decimal) -> NSDecimalNumber.CalculationError {
1234-
var i:UInt32 = 0
1235-
var carry:UInt16 = 1
1236-
var accumulator:UInt32 = 0
1237-
1238-
let c:UInt32 = min(left._length, right._length)
1259+
var i: UInt32 = 0
1260+
var borrow: UInt16 = 0
1261+
let c: UInt32 = min(left._length, right._length)
12391262

12401263
while i < c {
12411264
let li = UInt32(left[i])
12421265
let ri = UInt32(right[i])
1243-
accumulator = 0xffff + li - ri + UInt32(carry)
1244-
carry = UInt16(truncatingIfNeeded:accumulator >> 16)
1266+
let accumulator: UInt32 = (0x10000 + li) - UInt32(borrow) - ri
12451267
result[i] = UInt16(truncatingIfNeeded:accumulator)
1268+
borrow = 1 - UInt16(truncatingIfNeeded:accumulator >> 16)
12461269
i += 1
12471270
}
12481271

12491272
while i < left._length {
1250-
if carry != 0 {
1273+
if borrow != 0 {
12511274
let li = UInt32(left[i])
1252-
accumulator = 0xffff + li // + no carry
1253-
carry = UInt16(truncatingIfNeeded:accumulator >> 16)
1275+
let accumulator = 0xffff + li // + no carry
1276+
borrow = 1 - UInt16(truncatingIfNeeded:accumulator >> 16)
12541277
result[i] = UInt16(truncatingIfNeeded:accumulator)
12551278
i += 1
12561279
} else {
@@ -1263,17 +1286,16 @@ fileprivate func integerSubtract(_ result: inout Decimal, _ left: inout Decimal,
12631286
}
12641287
while i < right._length {
12651288
let ri = UInt32(right[i])
1266-
accumulator = 0xffff - ri + UInt32(carry)
1267-
carry = UInt16(truncatingIfNeeded:accumulator >> 16)
1289+
let accumulator = 0xffff - ri + UInt32(borrow)
1290+
borrow = 1 - UInt16(truncatingIfNeeded:accumulator >> 16)
12681291
result[i] = UInt16(truncatingIfNeeded:accumulator)
12691292
i += 1
12701293
}
12711294

1272-
if carry != 0 {
1295+
if borrow != 0 {
12731296
return .overflow
12741297
}
12751298
result._length = i;
1276-
12771299
result.trimTrailingZeros()
12781300

12791301
return .noError;

TestFoundation/TestDecimal.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ class TestDecimal: XCTestCase {
132132
XCTAssertFalse(zero.isInfinite)
133133
XCTAssertFalse(zero.isNaN)
134134
XCTAssertFalse(zero.isSignaling)
135+
136+
let d1 = Decimal(1234567890123456789 as UInt64)
137+
XCTAssertEqual(d1._exponent, 0)
138+
XCTAssertEqual(d1._length, 4)
135139
}
136140
func test_Constants() {
137141
XCTAssertEqual(8, NSDecimalMaxSize)
@@ -281,7 +285,21 @@ class TestDecimal: XCTestCase {
281285
}
282286
}
283287
}
288+
284289
XCTAssertEqual(Decimal(186243 * 15673 as Int64), Decimal(186243) * Decimal(15673))
290+
291+
XCTAssertEqual(Decimal(5538) + Decimal(2880.4), Decimal(5538 + 2880.4))
292+
XCTAssertEqual(NSDecimalNumber(floatLiteral: 5538).adding(NSDecimalNumber(floatLiteral: 2880.4)), NSDecimalNumber(floatLiteral: 5538 + 2880.4))
293+
294+
XCTAssertEqual(Decimal(5538) - Decimal(2880.4), Decimal(5538 - 2880.4))
295+
XCTAssertEqual(Decimal(2880.4) - Decimal(5538), Decimal(2880.4 - 5538))
296+
XCTAssertEqual(Decimal(0x10000) - Decimal(0x1000), Decimal(0xf000))
297+
XCTAssertEqual(Decimal(0x1_0000_0000) - Decimal(0x1000), Decimal(0xFFFFF000))
298+
XCTAssertEqual(Decimal(0x1_0000_0000_0000) - Decimal(0x1000), Decimal(0xFFFFFFFFF000))
299+
XCTAssertEqual(Decimal(1234_5678_9012_3456_7899 as UInt64) - Decimal(1234_5678_9012_3456_7890 as UInt64), Decimal(9))
300+
XCTAssertEqual(Decimal(0xffdd_bb00_8866_4422 as UInt64) - Decimal(0x7777_7777), Decimal(0xFFDD_BB00_10EE_CCAB as UInt64))
301+
XCTAssertEqual(NSDecimalNumber(floatLiteral: 5538).subtracting(NSDecimalNumber(floatLiteral: 2880.4)), NSDecimalNumber(floatLiteral: 5538 - 2880.4))
302+
XCTAssertEqual(NSDecimalNumber(floatLiteral: 2880.4).subtracting(NSDecimalNumber(floatLiteral: 5538)), NSDecimalNumber(floatLiteral: 2880.4 - 5538))
285303
}
286304

287305
func test_Misc() {
@@ -486,6 +504,10 @@ class TestDecimal: XCTestCase {
486504
XCTAssertEqual(1, f._isCompact)
487505
let after = f.description
488506
XCTAssertEqual(before, after)
507+
508+
let nsd1 = NSDecimalNumber(decimal: Decimal(2657.6))
509+
let nsd2 = NSDecimalNumber(floatLiteral: 2657.6)
510+
XCTAssertEqual(nsd1, nsd2)
489511
}
490512

491513
func test_PositivePowers() {

0 commit comments

Comments
 (0)