Hi Richard,
Whether 6.7 - 2.2 gives 4.5 or something else is a subtle issue, starting with the fact that 6.7 and 2.2 cannot be represented precisely in floating-point format. In this particular case, the errors happen to match up. What you *can* rely on in Smalltalk is that (67/10) - (22/10) = (9/2).
Let's just consider h * s / 113 + (r - p). If h, s, r, and p are all exact numbers (Integers or Fractions) the result will be an exact number. So what kinds of number are h, s, r, and p, and *why* are they like that? If you are reading them from a file, consider that Fraction readFrom: '6.7' readStream >>> (67/10)
I'm not reading them from a file, although some numbers come from an API as a JS number (float). Using Fractions wherever possible seems to be the right solution to this. And given that I already know the precision (it's always 1 decimal as much) I can turn everything into that and store them as DECIMAL in the database. I'm reluctant to perform such a change because everything has been working fine and this is just an edge case, but I think it is the right thing to do.
Is there a specification you could show us?
There is no specification other than the formulas: https://www.usga.org/content/usga/home-page/handicapping/roh/2020-rules-of-h... Section III -> Rules 6.1 and 6.2 There is only a remark after 6.1b that you must retain full precision in intermediate calculations. "the full calculated value is retained and rounding occurs only after the Playing Handicap calculation."
Some test data?
Not that I could find, I've been adding cases to my tests to assert it behaves properly. Thanks! Esteban A. Maringolo On Tue, Jun 15, 2021 at 4:13 AM Richard O'Keefe <raoknz@gmail.com> wrote:
On Tue, 15 Jun 2021 at 14:24, Esteban Maringolo <emaringolo@gmail.com> wrote:
On Mon, Jun 14, 2021 at 10:37 PM Richard O'Keefe <raoknz@gmail.com> wrote:
For what it's worth, I expected ScaledDecimal to act like fixed point arithmetic, and implemented it that way in my own Smalltalk library, where two ScaledDecimals *do* print the same if and only if they are numerically exactly the same. What Squeak and Pharo do is exceedingly odd: a ScaledDecimal is an exact rational number (Integer or Fraction) combined with a precision that is used for printing, not for calculation.
I pointed out before the weird behavior of ScaledDecimals in Pharo, two ScaledDecimal that print the same are not equal. Giving some weird comparison issues as result.
There really isn't any principle of least surprise when it comes to floating- point arithmetic. It's full of surprises and edge cases. Excel in particular is notorious for messing up due to trying to pretend all is well. In this particular case, the exact result is 4.5
Well... for the end user, it produces what they'd expect. So it might mess up, but in a spreadsheet 6.7 - 2.2 should give 4.5.
There are at least three rules for rounding such numbers: rounding out (5), rounding in (4), and rounding in [banker'salgorithm] (4 here, but 5.5 -> 6). So you are pushing up against an edge case for exact hand calculation!
I implemented a Float>>#roundedHandicap method that does something like the banking algorithm, but rounds positive numbers towards the next integer and negative numbers towards zero. E.g. 0.49 -> 0 0.5 -> 1 -0.5 -> 0 -0.51 -> -1
Nothing uses more than one decimal, so a ScaledDecimal would work but the specification says that it should use all possible precision in intermediate calculations, so I cannot use it.
I think you need to re-express your entire calculation to use exact arithmetic.
I really don't know how to do this, any pointers?
Nothing is more straightforward than addition and subtraction to me, 6.7 - 2.2 is the simplest it can get.
The common formula here is: h * s / 113 + (r - p), but in this particular case s was 113 so it removed the "troubling" part.
That or get agreement on "de minimis non curat lex".
I had to search for that expression. Now I know, I agree.
Regards,
Esteban A. Maringolo
On Tue, 15 Jun 2021 at 08:45, Esteban Maringolo <emaringolo@gmail.com> wrote:
I'm coming back to this because I've been bitten by these floating points things again.
If in Pharo [1] you do: a := 6.7 + (32.8 - 35)
It will produce: 4.499999999999997
Which, when rounded, will produce 4.
In other places [2] I do the same simple addition and subtraction it produces 4.5, that when rounded will produce 5.
I know now that Pharo doesn't lie to me while other systems do, and all that Richard pointed to before.
The issue here is that I'm following some calculation formula that was defined in some of the "other" systems, and so when I follow such a formula I get these edgy cases where my system produces a different output.
In this case the formula is for golf handicap calculations, and it caused my system to give 4 instead of 5 to a player, resulting in giving the first place to a player other than the one deserved. It was no big deal (it's not The Masters), but these cases appear from time to time.
Is there any way to "configure" the floating point calculation to behave as the "other systems"?
What is the best way to anticipate these situations, am I the only one being bitten by these issues?
Thanks in advance for any hints about these problems.
Best regards,
[1] Dolphin Smalltalk, JS, Python, Ruby, Dart produces the same output as Pharo. [2] VisualWorks, VAST, Excel, VB and all calculators I tried
Esteban A. Maringolo
On Tue, Sep 8, 2020 at 12:45 AM Esteban Maringolo <emaringolo@gmail.com> wrote:
On Tue, Sep 8, 2020 at 12:16 AM Richard O'Keefe <raoknz@gmail.com> wrote:
"7.1 roundTo: 0.1 should return 7.1" You're still not getting it.
I was until Konrad explained it.
Binary floating point CANNOT represent either of those numbers. You seem to be assuming that Pharo is making some mistake. It isn't. All it is doing is refusing to lie to you. <snip> The systems that print 7.1 are LYING to you, and Pharo is not.
I'm not assuming a mistake from Pharo, I had a wrong expectation what to get if I round to that precision. I don't know whether other systems lie or simply fulfill user expectations, if you send the #roundTo: to a float, I did expect to get a number with the same precision. That is my expectation as a user. As in the other thread I expected two scaled decimals that are printed equal to also be compared as equal (which they don't).
Whether there is a good reason for those behaviors is beyond my current comprehension, but it certainly doesn't follow the "principle of least surprise".
In any case, the method proposed by Tomohiro solved my issues.
Regards,
Esteban A. Maringolo