Skip to content

SR-7650: Incorrect result adding and subtracting Decimals #1550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 24, 2018

Conversation

spevans
Copy link
Contributor

@spevans spevans commented May 11, 2018

  • 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.

- 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.
@spevans
Copy link
Contributor Author

spevans commented May 11, 2018

@swift-ci please test

@spevans spevans requested a review from parkera May 11, 2018 16:59
@parkera parkera requested review from phausler and stephentyrone May 14, 2018 17:51
@spevans
Copy link
Contributor Author

spevans commented May 21, 2018

@phausler @stephentyrone Have you had a chance to review this fix?

@stephentyrone
Copy link
Contributor

@spevans Sorry, I have not. I should be able to review it sometime this week, please ping me if I haven't done so by Friday.

@dmonagle
Copy link

Hi all, I fully respect that we all have jobs to do, but I'm wondering if anybody has realised how serious this is? There is production code out there using Decimal and NSDecimalNumber for currency calculations and a core Swift framework is giving the wrong result for basic arithmetic. This could have serious impact on the reputation of Swift and the frameworks that are based on it. I would have thought a bug like this justified immediate release of a patch version of the foundation library and hopefully a patch release of the Swift tools so that production systems can start using it immediately.

Again, no disrespect but perhaps somebody could educate me as to the path and timeframe that will be followed to get this out to production?

@stephentyrone
Copy link
Contributor

stephentyrone commented May 23, 2018

@dmonagle That makes it more important to give it a proper review, which I personally will not have time to do until later this week. I'm sorry, but I will not simply rubber stamp a change because it's important.

I understand that you want your change to go in as quickly as possible; I'm frequently frustrated waiting on reviews myself, but I promise that I will get to it as soon as my other projects allow me to do so, and I expect that @phausler will do the same.

@parkera is the person to address any schedule questions.

@dmonagle
Copy link

@stephentyrone please understand this is not my pull request. I am also not asking you to "rubber stamp" something without review. However I do think this problem rates as being more than merely "important", which is why I felt the need to comment.

In the meantime I think it's only right to make sure that this gets some circulation as anybody using Decimal in production for arithmetic should immediately start looking for a workaround.

@parkera
Copy link
Contributor

parkera commented May 23, 2018

@dmonagle Thanks for raising the importance of this change. We'll get it reviewed as soon as possible and then merged into the right branches.

Copy link
Contributor

@millenomi millenomi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with it but would be happier if the comment was addressed.

let c:UInt32 = min(left._length, right._length)
var i: UInt32 = 0
var carry: UInt16 = 0
let c: UInt32 = min(left._length, right._length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not blocking the patch on this, but it'd be nice to have more descriptive names here.


while i < c {
let li = UInt32(left[i])
let ri = UInt32(right[i])
accumulator = li + ri + UInt32(carry)
let accumulator = li + ri + UInt32(carry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, why is something that's called an accumulator destroyed every cycle? I understand you're using it here to mean the 32-bit value that's about to go in the i-th word, but then it isn't an accumulator any longer. (Why was it being accumulated in the original? Why is it no longer used to accumulate? This code sorely needs commenting.)

To be clear: the word-by-word approach makes sense to me, and the old one may just be incorrect, but if we are to make it substantially better we should make sure a casual reader can handle this code down the line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I have since writing this verified on paper that it looks correct to me.)

XCTAssertEqual(Decimal(0x1_0000_0000_0000) - Decimal(0x1000), Decimal(0xFFFFFFFFF000))
XCTAssertEqual(Decimal(1234_5678_9012_3456_7899 as UInt64) - Decimal(1234_5678_9012_3456_7890 as UInt64), Decimal(9))
XCTAssertEqual(Decimal(0xffdd_bb00_8866_4422 as UInt64) - Decimal(0x7777_7777), Decimal(0xFFDD_BB00_10EE_CCAB as UInt64))
XCTAssertEqual(NSDecimalNumber(floatLiteral: 5538).subtracting(NSDecimalNumber(floatLiteral: 2880.4)), NSDecimalNumber(floatLiteral: 5538 - 2880.4))
Copy link
Contributor

@millenomi millenomi May 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d rather these be initialized from strings to avoid floating-point representation shenanigans.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where ‘these’ are all Decimal/NSDecimalNumber instances created from floating-point values.

- integerAdd(), integerSubtract(): Use more meaningful variable names.
@spevans
Copy link
Contributor Author

spevans commented May 23, 2018

@swift-ci please test

@phausler
Copy link
Contributor

with the renamed variables this is much clearer and looks quite reasonable to me.

The tests added pass on Darwin so this definitely brings things more into line with the two platforms.

Sorry for the delay on the review.

@millenomi
Copy link
Contributor

@swift-ci please test

Copy link
Contributor

@stephentyrone stephentyrone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtraction is a bit overcomplicated, but OK with me.

let li = UInt32(left[idx])
let ri = UInt32(right[idx])
// 0x10000 is to borrow in advance to avoid underflow.
let difference: UInt32 = (0x10000 + li) - UInt32(borrow) - ri
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really over-complicated way to do this instead of using the wrapping operators, but it's correct, so it's OK for now.

@spevans
Copy link
Contributor Author

spevans commented May 24, 2018

@swift-ci please test and merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants