roundTo: strange behavior

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
70 messages Options
1234
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo

Hi werner

to load a slice:

    - select it and press load.

to create a slice

       - + slice

       - then select the package that will compose the slice

       - save the slice in the inbox

Can you retry and let me know what did not work?


Stef


Le 29/10/16 à 17:01, test a écrit :
On 10/27/2016 11:13 AM, Nicolas Cellier wrote:

feel free to improve the slice.

Hi,
i never made a slice and thought <stupid grin>, i could eventually try to do that in this case, but of course i did not succeed:
1. Nicolas, i do not understand, why you put asScaledDecimal: numberOfWishedDecimal at the end of Fraction>>round:. what would not work, if one simply omitted that?
2. when i make a slice i find no possibility to save it to my pc, to check whether i made things correctly and there is no changes-button in the monticello-browser to look at the diff that slice would make. of course the seasoned programmer has no problems with that, but a simple user like me?
3. this line: "< expr: '(1/3 round: 2)' result: (33/100) >" is not accepted by the browser. i have no idea how to call this example to debug it, perhaps (33/100) is not accepted as a result because Fractions are no literals? and then what?
werner

p.s. regarding your slice, Nicolas, FractionTest>>testRounding needs also to be changed.


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo
In reply to this post by wernerk



Le 29/10/16 à 17:01, test a écrit :
On 10/27/2016 11:13 AM, Nicolas Cellier wrote:

feel free to improve the slice.

Hi,
i never made a slice and thought <stupid grin>, i could eventually try to do that in this case, but of course i did not succeed:
1. Nicolas, i do not understand, why you put asScaledDecimal: numberOfWishedDecimal at the end of Fraction>>round:. what would not work, if one simply omitted that?
2. when i make a slice i find no possibility to save it to my pc, to check whether i made things correctly and there is no changes-button in the monticello-browser to look at the diff that slice would make. of course the seasoned programmer has no problems with that, but a simple user like me?
3. this line: "< expr: '(1/3 round: 2)' result: (33/100) >" is not accepted by the browser. i have no idea how to call this example to debug it, perhaps (33/100) is not accepted as a result because Fractions are no literals? and then what?

This is an experience that we should remove
    "< expr: '(1/3 round: 2)' result: (33/100) >"
    -> "(1/3 round: 2)' -> (33/100)"

werner

p.s. regarding your slice, Nicolas, FractionTest>>testRounding needs also to be changed.


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo
In reply to this post by Martin McClure-2

I'm favor to deprecate round:  too.

Thanks you all for your analysis. I will reread the thread carefully to learn.


Stef


Le 27/10/16 à 18:12, Martin McClure a écrit :
On 10/27/2016 04:12 AM, test wrote:
i would prefer if #round: will not be deprecated in Floats. in some cases Float>>#round: can be useful, eg optimization problems where one searched value describes the size of a screw, that exists only in certain sizes.

#roundTo: can be used for this (and is specified by ANSI Smalltalk, for those who care). If the screws only come in 16th inch increments, for instance, then "roundTo: 0.0625" will give you an exact result, since 16ths *are* exactly representable as floats.

OTOH, #round: (not in ANSI) appears to promise something that cannot be delivered for Floats, so is misleading, and it spreads the kind of confusion that resulted in this thread.

That's why I favor deprecating #round: for Floats.

Regards,

-Martin


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by Ben Coman
Hi Ben,
this explanation helped me most, because i <big grin> realized that i
need an account with a password, which i dont have. so things have
resolved themselves.
thanks
werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by stepharo
On 10/30/2016 09:45 AM, stepharo wrote:
>
> I'm favor to deprecate round:  too.
>
> Thanks you all for your analysis. I will reread the thread carefully
> to learn.
>
Hi Stephane,

just to sum up my arguments:
1. Floats & Fractions represent almost the same thing, hence they should
have the same API! iow if Float>>round: is deprecated it should imo also
be deprecated in Fractions. and i dont see what is gained by generally
deprecating every #round: (eg i like to use it). if it is because you
want to have a small image, i'd guess, one could put ScaledDecimal in
its own package (as long as there are no dependencies of numbers on it
introduced) and have an image without that. the memory gain would be
bigger & imo the usability loss smaller.
2. most decimals are not representable as Floats. Ok, so what? pi is not
representable too, you dont deprecate Float>>pi because of that, right?

werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by wernerk
or perhaps it worked now, i think i uploaded
SLICE-Issue-15471-Cant-round-Float-fmax-to-2-decimal-places-WernerKassens.1
<http://www.smalltalkhub.com/#%21/%7EPharo/Pharo60Inbox/versions/SLICE-Issue-15471-Cant-round-Float-fmax-to-2-decimal-places-WernerKassens.1>

werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by wernerk
Hi,
just a simple numerical example using nicolas' implementation:
1.199999999999999999 round: 1.
  "1.2"
i'd guess this result is more or less what a beginner would expect with
this line. how would he do it with roundTo:? perhaps this way?
1.199999999999999999 roundTo: 0.1.
  "1.2000000000000002"
i'm not so sure that he would immediately see that he'd need to do it
this way:
(1.199999999999999999 roundTo: (1/10))asFloat.
  "1.2"
werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
oops, make that original number 1.19 in the example.
werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
tl;dr:

1.2000000000000002 *is* the correct answer for 1.19 roundTo: 0.1.
(1.19 roundTo: (1/10))asFloat gives a different answer because it's
rounding to a multiple of exactly 0.1, not the Float nearest to 0.1.


On 10/31/2016 08:35 AM, test wrote:
> oops, make that original number 1.19 in the example.

OK, so the examples are now

1.19 round: 1.

1.19 roundTo: 0.1.

(1.19 roundTo: (1/10))asFloat

I should note that the final example *should* result in a Float without
having to have #asFloat sent to it. Per ANSI and traditional Smalltalk
practice, any operation where the receiver *or* the argument is a Float
produces a Float.

Since 0.1 is not quite equal to 1/10, the correct answer may not be the
same for the latter two examples. Whether you define #round: to be one
or the other is an interesting question.

I've learned the hard way not to trust the results from Smalltalk math
libraries too far, so let's manually figure out what the correct answer
is for each of these, and see if Pharo's doing it right.

To start, every Float (ignoring infinities, NaNs, and subnormals) is (by
definition) a Fraction constrained to have a numerator in the range
[2**52..2**53) and a denominator that is a power of two.

Pharo says:
0.1 asFraction ==> (3602879701896397/36028797018963968)

Is this correct? The numerator is less than 2**52, so this Fraction has
been reduced. Let's restore it to what it would be in its Float form by
multiplying numerator and denominator by, in this case, 2:

7205759403792794
-----------------
72057594037927936

It's easy to see that this fraction is very nearly equal to 1/10, and to
see that there could be no Float closer to 1/10.

---

But I haven't shown the conversion from the string '0.1' to the actual
Float instance is correct. It's possible that there's some rounding
error there, which is reversed by a rounding in a different direction by
the #asFraction.

The Float for '0.1' is two words with values, 1069128089 and 2576980378.
Convert to hex and combine them, you get 16r3FB999999999999A

The numerator of our fraction is the low-order 52 bits of this, with a 1
added on the most-significant end, so 16r1999999999999A, which in
decimal is 7205759403792794. Which is what we got above, so we're good
so far.

========

Doing the same analysis on 1.19 gives a numerator of 16r130A3D70A3D70A,
and a denominator 1/16th the previous one, or

5359283556570890
----------------
4503599627370496

This one's harder, I can't just look at it and say it's the closest
possible to 1.19.

But we know that 1.19 = 119/100.

119/100 * 4503599627370496/4503599627370496 =

535928355657089024
------------------
450359962737049600

and is still exactly 1.19. But to make a representable Float we have to
divide numerator and denominator by 100, rounding the numerator if
necessary. And we can see that the numerator is correctly rounded, so
Pharo also converts the string '1.19' into a Float correctly.

How does Pharo do on
1.19 asFraction?

(2679641778285445/2251799813685248)

Once again, this has been reduced by dividing numerator and denominator
by two, but it's numerically correct.

======

Now that we know that we have the correct Fractions for the floats, we
can check the rounding functions.

1.19 roundTo: 0.1
should ideally be equivalent to
(1.19 asFraction roundTo: 0.1 asFraction) asFloat.

In Pharo 5,
1.19 asFraction roundTo: 0.1 asFraction ==>
(10808639105689191/9007199254740992)

For this to correct, (10808639105689191/9007199254740992) must be a
multiple of (3602879701896397/36028797018963968), since that's 0.1
asFraction.

Is it a multiple? We can align the denominators by multiplying
(10808639105689191/9007199254740992) by 4/4, and we get

43234556422756764
-----------------
36028797018963968

which would be a multiple of

3602879701896397
-----------------
36028797018963968

iff 43234556422756764 is a multiple of 3602879701896397. Looking at the
original problem (1.19 roundTo: 0.1) we'd expect the multiple to be 12.

Sure enough, 3602879701896397 * 12 ==> 43234556422756764. So we're
correct so far.

Now we have to convert our answer to the nearest representable Float.

Our denominator is already a power of two, but our numerator is out of
range -- it's too large.

It's even, so we can cut it in half, getting

21617278211378382

Still too large, still even. Cut it in half again:

10808639105689191

Just a bit too large. Cut it in half again:

5404319552844595.5

Now it's in range [2**52..2**53) but it's not an integer, so we must
round. Our number is exactly halfway between two representable numbers,
so by IEEE754 we round to the even one, which is

5404319552844596

The Float we get back from

1.19 roundTo: 0.1

is 16r3FF3333333333334

and the numerator part of that is 16r13333333333334, or

5404319552844596

Which agrees with what we computed before. So this *is* the correct
Float result. And it prints as 1.2000000000000002.

This print string implies that there is a Float nearer to 1.2 than this.

1.2 is represented as 16r3FF3333333333333, so one representable Float
less than the result of the #roundTo:. But, as we've shown,
1.2000000000000002 is *correct*. (Which was not what I expected when
started this -- I suspected there might be rounding error in the
computation of #roundTo:. But for this example, there isn't).

I haven't done the full analysis of it, but I expect that
(1.19 roundTo: (1/10))asFloat gives the Float with representation
16r3FF3333333333333 because it's rounding to a multiple of exactly 0.1,
not the Float nearest to 0.1.

Regards,

-Martin

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

John Brant-2
> On Oct 31, 2016, at 3:58 PM, Martin McClure <[hidden email]> wrote:
>
> (1.19 roundTo: (1/10))asFloat
>
> I should note that the final example *should* result in a Float without
> having to have #asFloat sent to it. Per ANSI and traditional Smalltalk
> practice, any operation where the receiver *or* the argument is a Float
> produces a Float.

While ANSI says that the numbers should be converted using the default conversion table, all of the Smalltalk’s that I’ve tried don’t follow that. I’ve tried Dolphin, VW, Pharo, & VAST, and all return fractions. I prefer this behavior over the ANSI behavior.


John Brant
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
On 10/31/2016 02:49 PM, John Brant wrote:

>> On Oct 31, 2016, at 3:58 PM, Martin McClure <[hidden email]> wrote:
>>
>> (1.19 roundTo: (1/10))asFloat
>>
>> I should note that the final example *should* result in a Float without
>> having to have #asFloat sent to it. Per ANSI and traditional Smalltalk
>> practice, any operation where the receiver *or* the argument is a Float
>> produces a Float.
>
> While ANSI says that the numbers should be converted using the default conversion table, all of the Smalltalk’s that I’ve tried don’t follow that. I’ve tried Dolphin, VW, Pharo, & VAST, and all return fractions. I prefer this behavior over the ANSI behavior.
>

Thanks for the information. GemStone answers a Float.
Since Fractions are more general than Floats, there is indeed a good
basis for arguing that this is one of the things that ANSI got wrong.

Regards,

-Martin


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Nicolas Cellier
In reply to this post by John Brant-2


2016-10-31 22:49 GMT+01:00 John Brant <[hidden email]>:
> On Oct 31, 2016, at 3:58 PM, Martin McClure <[hidden email]> wrote:
>
> (1.19 roundTo: (1/10))asFloat
>
> I should note that the final example *should* result in a Float without
> having to have #asFloat sent to it. Per ANSI and traditional Smalltalk
> practice, any operation where the receiver *or* the argument is a Float
> produces a Float.

While ANSI says that the numbers should be converted using the default conversion table, all of the Smalltalk’s that I’ve tried don’t follow that. I’ve tried Dolphin, VW, Pharo, & VAST, and all return fractions. I prefer this behavior over the ANSI behavior.


John Brant

Whatever result species, don't trust (aFloat roundTo: aFraction) too much, they have some chances to be inexact, except for trivial powers of two.

Example:

(1.105 roundTo: 1/100) -> (111/100)
1.105 < (1105/1000) -> true

Though the number was smaller than exact tie (in decimal), it was rounded up.
Indeed, the exact computations have a different opinion:

(1.105 asFraction roundTo: 1/100) -> (11/10)

That's why we must implement round: with exact computations...
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Ben Coman
In reply to this post by wernerk
On Mon, Oct 31, 2016 at 11:10 PM, test <[hidden email]> wrote:

> Hi,
> just a simple numerical example using nicolas' implementation:
> 1.199999999999999999 round: 1.
>  "1.2"
> i'd guess this result is more or less what a beginner would expect with this
> line. how would he do it with roundTo:? perhaps this way?
> 1.199999999999999999 roundTo: 0.1.
>  "1.2000000000000002"
> i'm not so sure that he would immediately see that he'd need to do it this
> way:
> (1.199999999999999999 roundTo: (1/10))asFloat.
>  "1.2"
> werner
>

On Tue, Nov 1, 2016 at 4:58 AM, Martin McClure <[hidden email]> wrote:
> tl;dr:
>
> 1.2000000000000002 *is* the correct answer for 1.19 roundTo: 0.1.
> (1.19 roundTo: (1/10))asFloat gives a different answer because it's
> rounding to a multiple of exactly 0.1, not the Float nearest to 0.1.

So then why don't we *really* help the naive programmer make "x
roundTo: 0.1" an error?
Conceptually...

  Number>>roundTo: quantum
      quantum < 1 ifTrue: [ quantum isFraction ifFalse: [
SubtleArithmeticError signal ].
      ^(self / quantum) rounded * quantum

  SubtleArithmeticError >> defaultAction
       self inform: 'Decimals have an inexact Float representation, so
rounding may not give you what you expect. Us Fractions instead.'.

cheers -ben

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
On 10/31/2016 05:07 PM, Ben Coman wrote:

> So then why don't we *really* help the naive programmer make "x
> roundTo: 0.1" an error?
> Conceptually...
>
>   Number>>roundTo: quantum
>       quantum < 1 ifTrue: [ quantum isFraction ifFalse: [
> SubtleArithmeticError signal ].
>       ^(self / quantum) rounded * quantum
>
>   SubtleArithmeticError >> defaultAction
>        self inform: 'Decimals have an inexact Float representation, so
> rounding may not give you what you expect. Us Fractions instead.'.
>
> cheers -ben
>

While this is tempting, this philosophy would, I'm afraid, lead to
signaling this error on *all* uses of Floats, because Floats are *full*
of this kind of behavior that is surprising to the uninitiated.

Regards,

-Martin

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by Martin McClure-2
Hi Martin,
thanks for this analysis, i really appreciate it. you did proof that, as
you said "1.2000000000000002 *is* the correct answer for 1.19 roundTo:
0.1.". please excuse me - i seriously think it was very friendly of you
to do this detailed analysis -, but my argument never was, that if i use
Float>roundTo: this produces problematic floating-point-errors. if it
produces floating-point-errors they wouldnt be unexpected. i thought
this would be clear from my previous posts. what i tried to say is, that
i am a simple user and pharo shouldnt expect from me, that i know, that
if i want a result looking a bit more like 1.2, i should use "(x
asFraction roundTo:(1/10))asFloat" instead of "x roundTo:0.1". the
#round: method circumvents the irritating part of Float calcs imo in an
inexpensive way, and rounding numbers is imo not a very exotic task, but
to presume that i figure out (x asFraction roundTo:(1/10))asFloat by
myself is simply expecting too much from me. otoh now i that i know how
to do it, it's not a problem for me, if the majority wants to deprecate
#roundTo:.
Thank you again for the careful analysis
werner
On 10/31/2016 09:58 PM, Martin McClure wrote:

> tl;dr:
>
> 1.2000000000000002 *is* the correct answer for 1.19 roundTo: 0.1.
> (1.19 roundTo: (1/10))asFloat gives a different answer because it's
> rounding to a multiple of exactly 0.1, not the Float nearest to 0.1.
>
>
> On 10/31/2016 08:35 AM, test wrote:
>> oops, make that original number 1.19 in the example.
> OK, so the examples are now
>
> 1.19 round: 1.
>
> 1.19 roundTo: 0.1.
>
> (1.19 roundTo: (1/10))asFloat
>
> I should note that the final example *should* result in a Float without
> having to have #asFloat sent to it. Per ANSI and traditional Smalltalk
> practice, any operation where the receiver *or* the argument is a Float
> produces a Float.
>
> Since 0.1 is not quite equal to 1/10, the correct answer may not be the
> same for the latter two examples. Whether you define #round: to be one
> or the other is an interesting question.
>
> I've learned the hard way not to trust the results from Smalltalk math
> libraries too far, so let's manually figure out what the correct answer
> is for each of these, and see if Pharo's doing it right.
>
> To start, every Float (ignoring infinities, NaNs, and subnormals) is (by
> definition) a Fraction constrained to have a numerator in the range
> [2**52..2**53) and a denominator that is a power of two.
>
> Pharo says:
> 0.1 asFraction ==> (3602879701896397/36028797018963968)
>
> Is this correct? The numerator is less than 2**52, so this Fraction has
> been reduced. Let's restore it to what it would be in its Float form by
> multiplying numerator and denominator by, in this case, 2:
>
> 7205759403792794
> -----------------
> 72057594037927936
>
> It's easy to see that this fraction is very nearly equal to 1/10, and to
> see that there could be no Float closer to 1/10.
>
> ---
>
> But I haven't shown the conversion from the string '0.1' to the actual
> Float instance is correct. It's possible that there's some rounding
> error there, which is reversed by a rounding in a different direction by
> the #asFraction.
>
> The Float for '0.1' is two words with values, 1069128089 and 2576980378.
> Convert to hex and combine them, you get 16r3FB999999999999A
>
> The numerator of our fraction is the low-order 52 bits of this, with a 1
> added on the most-significant end, so 16r1999999999999A, which in
> decimal is 7205759403792794. Which is what we got above, so we're good
> so far.
>
> ========
>
> Doing the same analysis on 1.19 gives a numerator of 16r130A3D70A3D70A,
> and a denominator 1/16th the previous one, or
>
> 5359283556570890
> ----------------
> 4503599627370496
>
> This one's harder, I can't just look at it and say it's the closest
> possible to 1.19.
>
> But we know that 1.19 = 119/100.
>
> 119/100 * 4503599627370496/4503599627370496 =
>
> 535928355657089024
> ------------------
> 450359962737049600
>
> and is still exactly 1.19. But to make a representable Float we have to
> divide numerator and denominator by 100, rounding the numerator if
> necessary. And we can see that the numerator is correctly rounded, so
> Pharo also converts the string '1.19' into a Float correctly.
>
> How does Pharo do on
> 1.19 asFraction?
>
> (2679641778285445/2251799813685248)
>
> Once again, this has been reduced by dividing numerator and denominator
> by two, but it's numerically correct.
>
> ======
>
> Now that we know that we have the correct Fractions for the floats, we
> can check the rounding functions.
>
> 1.19 roundTo: 0.1
> should ideally be equivalent to
> (1.19 asFraction roundTo: 0.1 asFraction) asFloat.
>
> In Pharo 5,
> 1.19 asFraction roundTo: 0.1 asFraction ==>
> (10808639105689191/9007199254740992)
>
> For this to correct, (10808639105689191/9007199254740992) must be a
> multiple of (3602879701896397/36028797018963968), since that's 0.1
> asFraction.
>
> Is it a multiple? We can align the denominators by multiplying
> (10808639105689191/9007199254740992) by 4/4, and we get
>
> 43234556422756764
> -----------------
> 36028797018963968
>
> which would be a multiple of
>
> 3602879701896397
> -----------------
> 36028797018963968
>
> iff 43234556422756764 is a multiple of 3602879701896397. Looking at the
> original problem (1.19 roundTo: 0.1) we'd expect the multiple to be 12.
>
> Sure enough, 3602879701896397 * 12 ==> 43234556422756764. So we're
> correct so far.
>
> Now we have to convert our answer to the nearest representable Float.
>
> Our denominator is already a power of two, but our numerator is out of
> range -- it's too large.
>
> It's even, so we can cut it in half, getting
>
> 21617278211378382
>
> Still too large, still even. Cut it in half again:
>
> 10808639105689191
>
> Just a bit too large. Cut it in half again:
>
> 5404319552844595.5
>
> Now it's in range [2**52..2**53) but it's not an integer, so we must
> round. Our number is exactly halfway between two representable numbers,
> so by IEEE754 we round to the even one, which is
>
> 5404319552844596
>
> The Float we get back from
>
> 1.19 roundTo: 0.1
>
> is 16r3FF3333333333334
>
> and the numerator part of that is 16r13333333333334, or
>
> 5404319552844596
>
> Which agrees with what we computed before. So this *is* the correct
> Float result. And it prints as 1.2000000000000002.
>
> This print string implies that there is a Float nearer to 1.2 than this.
>
> 1.2 is represented as 16r3FF3333333333333, so one representable Float
> less than the result of the #roundTo:. But, as we've shown,
> 1.2000000000000002 is *correct*. (Which was not what I expected when
> started this -- I suspected there might be rounding error in the
> computation of #roundTo:. But for this example, there isn't).
>
> I haven't done the full analysis of it, but I expect that
> (1.19 roundTo: (1/10))asFloat gives the Float with representation
> 16r3FF3333333333333 because it's rounding to a multiple of exactly 0.1,
> not the Float nearest to 0.1.
>
> Regards,
>
> -Martin
>
>


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
On 11/01/2016 03:28 PM, test wrote:

> Hi Martin,
> thanks for this analysis, i really appreciate it. you did proof that,
> as you said "1.2000000000000002 *is* the correct answer for 1.19
> roundTo: 0.1.". please excuse me - i seriously think it was very
> friendly of you to do this detailed analysis -, but my argument never
> was, that if i use Float>roundTo: this produces problematic
> floating-point-errors. if it produces floating-point-errors they
> wouldnt be unexpected. i thought this would be clear from my previous
> posts. what i tried to say is, that i am a simple user and pharo
> shouldnt expect from me, that i know, that if i want a result looking
> a bit more like 1.2, i should use "(x asFraction
> roundTo:(1/10))asFloat" instead of "x roundTo:0.1". the #round: method
> circumvents the irritating part of Float calcs imo in an inexpensive
> way, and rounding numbers is imo not a very exotic task, but to
> presume that i figure out (x asFraction roundTo:(1/10))asFloat by
> myself is simply expecting too much from me. otoh now i that i know
> how to do it, it's not a problem for me, if the majority wants to
> deprecate #roundTo:.
oops ... deprecate #round: ... of course

> Thank you again for the careful analysis
> werner
> On 10/31/2016 09:58 PM, Martin McClure wrote:
>> tl;dr:
>>
>> 1.2000000000000002 *is* the correct answer for 1.19 roundTo: 0.1.
>> (1.19 roundTo: (1/10))asFloat gives a different answer because it's
>> rounding to a multiple of exactly 0.1, not the Float nearest to 0.1.
>>
>>
>> On 10/31/2016 08:35 AM, test wrote:
>>> oops, make that original number 1.19 in the example.
>> OK, so the examples are now
>>
>> 1.19 round: 1.
>>
>> 1.19 roundTo: 0.1.
>>
>> (1.19 roundTo: (1/10))asFloat
>>
>> I should note that the final example *should* result in a Float without
>> having to have #asFloat sent to it. Per ANSI and traditional Smalltalk
>> practice, any operation where the receiver *or* the argument is a Float
>> produces a Float.
>>
>> Since 0.1 is not quite equal to 1/10, the correct answer may not be the
>> same for the latter two examples. Whether you define #round: to be one
>> or the other is an interesting question.
>>
>> I've learned the hard way not to trust the results from Smalltalk math
>> libraries too far, so let's manually figure out what the correct answer
>> is for each of these, and see if Pharo's doing it right.
>>
>> To start, every Float (ignoring infinities, NaNs, and subnormals) is (by
>> definition) a Fraction constrained to have a numerator in the range
>> [2**52..2**53) and a denominator that is a power of two.
>>
>> Pharo says:
>> 0.1 asFraction ==> (3602879701896397/36028797018963968)
>>
>> Is this correct? The numerator is less than 2**52, so this Fraction has
>> been reduced. Let's restore it to what it would be in its Float form by
>> multiplying numerator and denominator by, in this case, 2:
>>
>> 7205759403792794
>> -----------------
>> 72057594037927936
>>
>> It's easy to see that this fraction is very nearly equal to 1/10, and to
>> see that there could be no Float closer to 1/10.
>>
>> ---
>>
>> But I haven't shown the conversion from the string '0.1' to the actual
>> Float instance is correct. It's possible that there's some rounding
>> error there, which is reversed by a rounding in a different direction by
>> the #asFraction.
>>
>> The Float for '0.1' is two words with values, 1069128089 and 2576980378.
>> Convert to hex and combine them, you get 16r3FB999999999999A
>>
>> The numerator of our fraction is the low-order 52 bits of this, with a 1
>> added on the most-significant end, so 16r1999999999999A, which in
>> decimal is 7205759403792794. Which is what we got above, so we're good
>> so far.
>>
>> ========
>>
>> Doing the same analysis on 1.19 gives a numerator of 16r130A3D70A3D70A,
>> and a denominator 1/16th the previous one, or
>>
>> 5359283556570890
>> ----------------
>> 4503599627370496
>>
>> This one's harder, I can't just look at it and say it's the closest
>> possible to 1.19.
>>
>> But we know that 1.19 = 119/100.
>>
>> 119/100 * 4503599627370496/4503599627370496 =
>>
>> 535928355657089024
>> ------------------
>> 450359962737049600
>>
>> and is still exactly 1.19. But to make a representable Float we have to
>> divide numerator and denominator by 100, rounding the numerator if
>> necessary. And we can see that the numerator is correctly rounded, so
>> Pharo also converts the string '1.19' into a Float correctly.
>>
>> How does Pharo do on
>> 1.19 asFraction?
>>
>> (2679641778285445/2251799813685248)
>>
>> Once again, this has been reduced by dividing numerator and denominator
>> by two, but it's numerically correct.
>>
>> ======
>>
>> Now that we know that we have the correct Fractions for the floats, we
>> can check the rounding functions.
>>
>> 1.19 roundTo: 0.1
>> should ideally be equivalent to
>> (1.19 asFraction roundTo: 0.1 asFraction) asFloat.
>>
>> In Pharo 5,
>> 1.19 asFraction roundTo: 0.1 asFraction ==>
>> (10808639105689191/9007199254740992)
>>
>> For this to correct, (10808639105689191/9007199254740992) must be a
>> multiple of (3602879701896397/36028797018963968), since that's 0.1
>> asFraction.
>>
>> Is it a multiple? We can align the denominators by multiplying
>> (10808639105689191/9007199254740992) by 4/4, and we get
>>
>> 43234556422756764
>> -----------------
>> 36028797018963968
>>
>> which would be a multiple of
>>
>> 3602879701896397
>> -----------------
>> 36028797018963968
>>
>> iff 43234556422756764 is a multiple of 3602879701896397. Looking at the
>> original problem (1.19 roundTo: 0.1) we'd expect the multiple to be 12.
>>
>> Sure enough, 3602879701896397 * 12 ==> 43234556422756764. So we're
>> correct so far.
>>
>> Now we have to convert our answer to the nearest representable Float.
>>
>> Our denominator is already a power of two, but our numerator is out of
>> range -- it's too large.
>>
>> It's even, so we can cut it in half, getting
>>
>> 21617278211378382
>>
>> Still too large, still even. Cut it in half again:
>>
>> 10808639105689191
>>
>> Just a bit too large. Cut it in half again:
>>
>> 5404319552844595.5
>>
>> Now it's in range [2**52..2**53) but it's not an integer, so we must
>> round. Our number is exactly halfway between two representable numbers,
>> so by IEEE754 we round to the even one, which is
>>
>> 5404319552844596
>>
>> The Float we get back from
>>
>> 1.19 roundTo: 0.1
>>
>> is 16r3FF3333333333334
>>
>> and the numerator part of that is 16r13333333333334, or
>>
>> 5404319552844596
>>
>> Which agrees with what we computed before. So this *is* the correct
>> Float result. And it prints as 1.2000000000000002.
>>
>> This print string implies that there is a Float nearer to 1.2 than this.
>>
>> 1.2 is represented as 16r3FF3333333333333, so one representable Float
>> less than the result of the #roundTo:. But, as we've shown,
>> 1.2000000000000002 is *correct*. (Which was not what I expected when
>> started this -- I suspected there might be rounding error in the
>> computation of #roundTo:. But for this example, there isn't).
>>
>> I haven't done the full analysis of it, but I expect that
>> (1.19 roundTo: (1/10))asFloat gives the Float with representation
>> 16r3FF3333333333333 because it's rounding to a multiple of exactly 0.1,
>> not the Float nearest to 0.1.
>>
>> Regards,
>>
>> -Martin
>>
>>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
In reply to this post by wernerk
On 11/01/2016 07:28 AM, test wrote:

> Hi Martin,
> thanks for this analysis, i really appreciate it. you did proof that, as
> you said "1.2000000000000002 *is* the correct answer for 1.19 roundTo:
> 0.1.". please excuse me - i seriously think it was very friendly of you
> to do this detailed analysis -, but my argument never was, that if i use
> Float>roundTo: this produces problematic floating-point-errors. if it
> produces floating-point-errors they wouldnt be unexpected. i thought
> this would be clear from my previous posts. what i tried to say is, that
> i am a simple user and pharo shouldnt expect from me, that i know, that
> if i want a result looking a bit more like 1.2, i should use "(x
> asFraction roundTo:(1/10))asFloat" instead of "x roundTo:0.1". the
> #round: method circumvents the irritating part of Float calcs imo in an
> inexpensive way, and rounding numbers is imo not a very exotic task, but
> to presume that i figure out (x asFraction roundTo:(1/10))asFloat by
> myself is simply expecting too much from me. otoh now i that i know how
> to do it, it's not a problem for me, if the majority wants to deprecate
> #roundTo:.
> Thank you again for the careful analysis
> werner

Hi Werner,

Thanks for your comments. I posted the analysis because I did the
analysis (and thought some others might want to see it), and I did the
analysis because I wanted to find out whether that answer was right.
Some Smalltalks are pretty bad in similar areas of Float handling.


But aside from all the fine points, if you want a floating-point number
to "look" nice and human-readable,
(x asFraction roundTo:(1/10))asFloat
will work, but I still recommend not rounding the number itself, but
rounding the printing of the number. This is not a Pharo thing, it's an
any-language-with-floats thing. In C you have printf, etc. In Pharo, you
can use for instance:

  1.19 printShowingDecimalPlaces: 1  ==> '1.2'

This makes it easier for someone reading the code to see the intent.

Regards,

-Martin

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
yes Martin, i get that point and i already reacted to it, i occasionally
want to calculate something with a rounded float, not print it: apart
from my harley i normally use _metric screws which come in decimal
steps. <friendly grin> let's end that discussion, we are going around in
circles.
werner

On 11/02/2016 02:48 AM, Martin McClure wrote:

> Hi Werner,
> Thanks for your comments. I posted the analysis because I did the
> analysis (and thought some others might want to see it), and I did the
> analysis because I wanted to find out whether that answer was right.
> Some Smalltalks are pretty bad in similar areas of Float handling.
>
>
> But aside from all the fine points, if you want a floating-point number
> to "look" nice and human-readable,
> (x asFraction roundTo:(1/10))asFloat
> will work, but I still recommend not rounding the number itself, but
> rounding the printing of the number. This is not a Pharo thing, it's an
> any-language-with-floats thing. In C you have printf, etc. In Pharo, you
> can use for instance:
>
>    1.19 printShowingDecimalPlaces: 1  ==> '1.2'
>
> This makes it easier for someone reading the code to see the intent.
>
> Regards,
>
> -Martin
>
>


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Nicolas Cellier
Last thing, I'm not sure it's a good idea to deprecate round:
round: has been added to overcome the rounding problems of roundTo:
If we deprecate round:, then users will fallback to roundTo: and won't get correctly rounded Floats...

Thus my original question: why is there a round in python, ruby etc...
There must be other applications than just printing (we said financial is one).

2016-11-02 14:40 GMT+01:00 werner kassens <[hidden email]>:
yes Martin, i get that point and i already reacted to it, i occasionally want to calculate something with a rounded float, not print it: apart from my harley i normally use _metric screws which come in decimal steps. <friendly grin> let's end that discussion, we are going around in circles.
werner


On 11/02/2016 02:48 AM, Martin McClure wrote:
Hi Werner,
Thanks for your comments. I posted the analysis because I did the
analysis (and thought some others might want to see it), and I did the
analysis because I wanted to find out whether that answer was right.
Some Smalltalks are pretty bad in similar areas of Float handling.


But aside from all the fine points, if you want a floating-point number
to "look" nice and human-readable,
(x asFraction roundTo:(1/10))asFloat
will work, but I still recommend not rounding the number itself, but
rounding the printing of the number. This is not a Pharo thing, it's an
any-language-with-floats thing. In C you have printf, etc. In Pharo, you
can use for instance:

   1.19 printShowingDecimalPlaces: 1  ==> '1.2'

This makes it easier for someone reading the code to see the intent.

Regards,

-Martin





Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo

Ok at least we should change the comment to point to printShowingDecimalPlaces:

1.19 printShowingDecimalPlaces: 1

STf

Le 2/11/16 à 17:31, Nicolas Cellier a écrit :
Last thing, I'm not sure it's a good idea to deprecate round:
round: has been added to overcome the rounding problems of roundTo:
If we deprecate round:, then users will fallback to roundTo: and won't get correctly rounded Floats...

Thus my original question: why is there a round in python, ruby etc...
There must be other applications than just printing (we said financial is one).

2016-11-02 14:40 GMT+01:00 werner kassens <[hidden email]>:
yes Martin, i get that point and i already reacted to it, i occasionally want to calculate something with a rounded float, not print it: apart from my harley i normally use _metric screws which come in decimal steps. <friendly grin> let's end that discussion, we are going around in circles.
werner


On 11/02/2016 02:48 AM, Martin McClure wrote:
Hi Werner,
Thanks for your comments. I posted the analysis because I did the
analysis (and thought some others might want to see it), and I did the
analysis because I wanted to find out whether that answer was right.
Some Smalltalks are pretty bad in similar areas of Float handling.


But aside from all the fine points, if you want a floating-point number
to "look" nice and human-readable,
(x asFraction roundTo:(1/10))asFloat
will work, but I still recommend not rounding the number itself, but
rounding the printing of the number. This is not a Pharo thing, it's an
any-language-with-floats thing. In C you have printf, etc. In Pharo, you
can use for instance:

   1.19 printShowingDecimalPlaces: 1  ==> '1.2'

This makes it easier for someone reading the code to see the intent.

Regards,

-Martin






1234