Skip to content
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

Calculation order affect result #561

Closed
z-Wind opened this issue Dec 21, 2022 · 3 comments
Closed

Calculation order affect result #561

z-Wind opened this issue Dec 21, 2022 · 3 comments

Comments

@z-Wind
Copy link

z-Wind commented Dec 21, 2022

let a = Decimal::new(7, 0) / Decimal::new(5, 0);
let b = Decimal::new(10, 0) / Decimal::new(9, 0);
assert_eq!(
    a * b,
    (Decimal::new(7, 0) / Decimal::new(5, 0)) * (Decimal::new(10, 0) / Decimal::new(9, 0))
); // => Pass
assert_eq!(
    a * b,
    Decimal::new(7, 0) / Decimal::new(5, 0) * Decimal::new(10, 0) / Decimal::new(9, 0)
); // => Fail
left: `1.5555555555555555555555555555`,
right: `1.5555555555555555555555555556`'

I guess that the precision of 28 causes that result.
Are there any methods to avoid this?

@paupino
Copy link
Owner

paupino commented Dec 22, 2022

Unfortunately, this is expected behavior in the current implementation of Rust Decimal due to each operation being evaluated, feeding the result into the next one. Effectively, without brackets order of operations applies at the std::op level. As expected, Division and Multiplication are equal in terms of order of precedence (i.e. it's why you have BEDMAS vs PEMDAS etc - multiplication and division are interchangeable in these systems).

The current workaround is to use brackets. In this particular example the first assertion is equivalent to the a & b calculations - since a & b are statements these can be thought of as have brackets surrounding them. The second assertion however is technically not equivalent due to order of execution.

All that said, brackets won't solve everything. For example:

println!("{}", dec!(1) / dec!(3) * dec!(3));
println!("{}", (dec!(1) / dec!(3)) * dec!(3));

Due to operator evaluation we will get 0.9999999999999999999999999999 for both of these. The current fix for this would be to rearrange the operators which is often easier said than done:

println!("{}", dec!(1) * dec!(3) / dec!(3)); // Outputs 1

The longer answer which has been discussed though yet to be implemented, is to delay evaluation until absolutely required instead of performing an evaluation each operation (e.g. #511 (comment)). This would be useful in many different scenarios - in particular when performing mathematical functions. My initial thinking is that this would use an intermediary format to be used to maintain maximum precision (i.e. perhaps a ratio)- all in all, it's an experiment I want to take a look into when we start work on v2 of the library.

Anyway, does this help at all? If I've parsed your question incorrectly, please let me know!

@schungx
Copy link
Contributor

schungx commented Dec 23, 2022

@z-Wind That's the definition of 28-digit precision. It means that the 29th digit does not need to be correct. Technically speaking, it can even come up with a different number for the same calculation; it won't be deterministic, but it will be correct to the set precision.

Therefore, truncate the number to the max precision before you do any comparisons.

A more technical explanation: There is not enough bits in order to represent 0-9 fully for the 29th position (if I remember correctly, the digits 8 and 9 cannot be represented). So it depends on luck whether any intermediate calculation results in an ending 8 or 9.

@z-Wind
Copy link
Author

z-Wind commented Dec 23, 2022

Thanks a lot.
This is really a great library.
I will looking forward to the implementation of v2 of the library.

@z-Wind z-Wind closed this as completed Dec 23, 2022
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

No branches or pull requests

3 participants