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 guys

I loaded the slice 15471 of werner and I run

https://pharo.fogbugz.com/f/cases/15471/Can-t-round-Float-fmax-to-2-decimal-places


testIfCompletelyBroken

     "If the results are not these values (accounting for precision of
printing)
     then something is horribly wrong"

     gen seed: 2345678901.
     self assert: (((1 to: 10) collect: [:i | gen next round: 15]) =
#(0.149243269650845 0.331633021743797 0.75619644800024 0.393701540023881
0.941783181364547 0.549929193942775 0.659962596213428 0.991354559078512
0.696074432551896 0.922987899707159 )).

I get the following. Is it correct?

#(0.220388752510952 0.073763451573329 0.74233059293699 0.350275491993071
0.080193927548916 0.819340314631974 0.652668019594936 0.391405332084468
0.349416343657959 0.640487859323848)

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
Hi Stephan,
definitely no. after the test is run, the seed has changed, perhaps you
did not reset the seed. ill look into this  this night, have to work
now, ok?
werner

On 11/04/2016 03:29 PM, stepharo wrote:

> Hi guys
>
> I loaded the slice 15471 of werner and I run
>
> https://pharo.fogbugz.com/f/cases/15471/Can-t-round-Float-fmax-to-2-decimal-places 
>
>
>
> testIfCompletelyBroken
>
>     "If the results are not these values (accounting for precision of
> printing)
>     then something is horribly wrong"
>
>     gen seed: 2345678901.
>     self assert: (((1 to: 10) collect: [:i | gen next round: 15]) =
> #(0.149243269650845 0.331633021743797 0.75619644800024
> 0.393701540023881 0.941783181364547 0.549929193942775
> 0.659962596213428 0.991354559078512 0.696074432551896
> 0.922987899707159 )).
>
> I get the following. Is it correct?
>
> #(0.220388752510952 0.073763451573329 0.74233059293699
> 0.350275491993071 0.080193927548916 0.819340314631974
> 0.652668019594936 0.391405332084468 0.349416343657959 0.640487859323848)
>
>


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo


Le 4/11/16 à 17:03, werner kassens a écrit :
> Hi Stephan,
> definitely no. after the test is run, the seed has changed, perhaps
> you did not reset the seed.
I just run the test. Nothing else.

> ill look into this  this night, have to work now, ok?
tx!

> werner
>
> On 11/04/2016 03:29 PM, stepharo wrote:
>> Hi guys
>>
>> I loaded the slice 15471 of werner and I run
>>
>> https://pharo.fogbugz.com/f/cases/15471/Can-t-round-Float-fmax-to-2-decimal-places 
>>
>>
>>
>> testIfCompletelyBroken
>>
>>     "If the results are not these values (accounting for precision of
>> printing)
>>     then something is horribly wrong"
>>
>>     gen seed: 2345678901.
>>     self assert: (((1 to: 10) collect: [:i | gen next round: 15]) =
>> #(0.149243269650845 0.331633021743797 0.75619644800024
>> 0.393701540023881 0.941783181364547 0.549929193942775
>> 0.659962596213428 0.991354559078512 0.696074432551896
>> 0.922987899707159 )).
>>
>> I get the following. Is it correct?
>>
>> #(0.220388752510952 0.073763451573329 0.74233059293699
>> 0.350275491993071 0.080193927548916 0.819340314631974
>> 0.652668019594936 0.391405332084468 0.349416343657959 0.640487859323848)
>>
>>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
Hi Stephane,
i uploaded a
SLICE-Issue-15471-Cant-round-Float-fmax-to-2-decimal-places-WernerKassens.2
to the inbox with these changes:
i corrected RandomTest>>testIfCompletelyBroken, completed tests with
some border cases and the usual specifications, and added your comment
to Float>>round:.
the testIfCompletelyBroken needed to be changed:
"original in test:
  #(0.149243269650845 0.331633021743797 0.75619644800024
0.393701540023881 0.941783181364547 0.549929193942775 0.659962596213428
0.991354559078512 0.696074432551896 0.922987899707159 )"
gen := Random seed: 2345678901.
raw:=    (1 to: 10) collect: [:i | gen next] .
"#(0.14924326965084453 0.3316330217437972 0.7561964480002394
0.3937015400238808 0.9417831813645471 0.5499291939427746
0.6599625962134277 0.991354559078512 0.6960744325518955 0.922987899707159)"
gen seed: 2345678901.
test:=(1 to: 10) collect: [:i | gen next round: 15].
  "#(0.149243269650845 0.331633021743797 0.756196448000239
0.393701540023881 0.941783181364547 0.549929193942775 0.659962596213428
0.991354559078512 0.696074432551895 0.922987899707159)"
"rounding here is correct, 5 and higher is rounded up, 4 and lower down"
"differences to the original are the result of originally wrong
rounding; compare with the raw result."
werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
Hi,
i noticed that i have quite some difficulties in producing a slice that
makes at least a minimal sort of sense, hence a stupid question:
is it ok to make a slice dependent on a preceeding slice? i would guess
not, since in the meantime a lot of newer versions of a package in the
slice are in Pharo60/main hence there will be regressions that have to
be filtered out by the integrator (unfortunately it is relatively
unclear to me how the integrator operates and how much is automated).
but if it is not ok, how do i proceed if i only want to change a small
part of the preceeding slice?
werner

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by wernerk
Hi,
fwiw when i redid that #testIfCompletelyBroken i noticed a sort of bug
in that new #round: - well, i think its a feature -, but i should
probably mention it nevertheless:
1.15 round:1. "1.1"    "down"
1.25 round:1. "1.3"    "up"
1.35 round:1. "1.4"    "up"
1.45 round:1. "1.4"    "down"
1.55 round:1. "1.6"    "up"
1.65 round:1. "1.6"    "down"
btw the same happens with #printShowingDecimalPlaces: (which is
unchanged, happens eg also in 4.0).

if one wants to change this to the normal behaviour, i'd guess one could
do it perhaps by using #asMinimalDecimalFraction instead of #asFraction
in #round:

i have to admit that i have no problems at all with this idiosyncrasy
and would _not use #asMinimalDecimalFraction in #round: for these reasons:
1. i see it as a poor mans round-to-even (this bankers rounding is done
so that the sum of rounded numbers does not have an upward bias in
relation to the sum of the original unrounded numbers).
2. although i havent tested it, i assume #asMinimalDecimalFraction is
noticeably slower than #asFraction, because the former one has to use a
#whileFalse: iteration.
3. round-to-even imprints a definite structure on eg random-numbers, the
structure of the new #round: is at least less simple & does not produce
too many even decimals, insofar it's even a rich mans round-to-even (in
a way a non-random, hence repeatable, stochastic rounding). normal
rounding of course introduces an even less wanted upward bias.
But <stupid grin> perhaps somebody else wants to have a more predictable
behaviour of #round:?
werner

On 11/04/2016 10:26 PM, werner kassens wrote:
> Hi Stephane,
> i uploaded a
> SLICE-Issue-15471-Cant-round-Float-fmax-to-2-decimal-places-WernerKassens.2
> to the inbox with these changes:
> i corrected RandomTest>>testIfCompletelyBroken, completed tests with
> some border cases and the usual specifications, and added your comment
> to Float>>round:.


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
On 11/05/2016 06:17 AM, werner kassens wrote:

> Hi,
> fwiw when i redid that #testIfCompletelyBroken i noticed a sort of bug
> in that new #round: - well, i think its a feature -, but i should
> probably mention it nevertheless:
> 1.15 round:1. "1.1"    "down"
> 1.25 round:1. "1.3"    "up"
> 1.35 round:1. "1.4"    "up"
> 1.45 round:1. "1.4"    "down"
> 1.55 round:1. "1.6"    "up"
> 1.65 round:1. "1.6"    "down"
> btw the same happens with #printShowingDecimalPlaces: (which is
> unchanged, happens eg also in 4.0).
>
> if one wants to change this to the normal behaviour, i'd guess one
> could do it perhaps by using #asMinimalDecimalFraction instead of
> #asFraction in #round:


This is, in part, an example of it being difficult to predict, at a
glance, what #round: should do, without an understanding of the
strengths and limitations of binary floating point numbers.

Remember that Floats are not usually exact representations of nice
decimal fractions. So most of these numbers are not *exactly* halfway
between their possible roundings.

1.15 < (115/100) "true"
1.25 = (125/100) "true"
1.35 > (135/100) "true"
1.45 < (145/100) "true"
1.55 > (155/100) "true"
1.65 < (165/100) "true"

In every case except 1.25, the answer is the correct rounding, that, is
the one that is closer. For 1.25, the number *is* exactly halfway
between two roundings. For round-to-even (which I recommend) the answer
should be 1.2, so it gets that one "wrong".

Regards,

-Martin


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
Hi Martin,
yes, what i said was wrong (!). i got irritated when i used
#printShowingDecimalPlaces: on a directly entered decimal number <g> and
got a seemingly upwards rounded number. of course that result was also
correct. iow there is no problem in the new #round: or
#printShowingDecimalPlaces:. sorry for my confusion.
werner

On 11/06/2016 01:57 AM, Martin McClure wrote:

> On 11/05/2016 06:17 AM, werner kassens wrote:
>> Hi,
>> fwiw when i redid that #testIfCompletelyBroken i noticed a sort of
>> bug in that new #round: - well, i think its a feature -, but i should
>> probably mention it nevertheless:
>> 1.15 round:1. "1.1"    "down"
>> 1.25 round:1. "1.3"    "up"
>> 1.35 round:1. "1.4"    "up"
>> 1.45 round:1. "1.4"    "down"
>> 1.55 round:1. "1.6"    "up"
>> 1.65 round:1. "1.6"    "down"
>> btw the same happens with #printShowingDecimalPlaces: (which is
>> unchanged, happens eg also in 4.0).
>>
>> if one wants to change this to the normal behaviour, i'd guess one
>> could do it perhaps by using #asMinimalDecimalFraction instead of
>> #asFraction in #round:
>
>
> This is, in part, an example of it being difficult to predict, at a
> glance, what #round: should do, without an understanding of the
> strengths and limitations of binary floating point numbers.
>
> Remember that Floats are not usually exact representations of nice
> decimal fractions. So most of these numbers are not *exactly* halfway
> between their possible roundings.
>
> 1.15 < (115/100) "true"
> 1.25 = (125/100) "true"
> 1.35 > (135/100) "true"
> 1.45 < (145/100) "true"
> 1.55 > (155/100) "true"
> 1.65 < (165/100) "true"
>
> In every case except 1.25, the answer is the correct rounding, that,
> is the one that is closer. For 1.25, the number *is* exactly halfway
> between two roundings. For round-to-even (which I recommend) the
> answer should be 1.2, so it gets that one "wrong".
>
> Regards,
>
> -Martin
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Andres Valloud-4
In reply to this post by Martin McClure-2
Hey Martin... I agree on the confusion.

 > I still recommend not rounding the number itself, but
rounding the printing of the number

Yes.

If "rounding" had well specified behavior that was at least somewhat
reasonable (e.g. return the closest floating point value and round to
even if tied), I'd accept floating point results that don't print
"pretty" in base 10.  That's the way things are, there's no point in
pretending IEEE-754 is not base 2.

If "rounding" is being done in place of "printing", or obtaining a
"string representation", I think that won't work as expected because 0.1
not representable etc.

For "printing", the algorithms were Dragon4 and Grisu3 last time I checked.

For "floating point that works nice in base 10", there's IEEE-854.  For
those unfamiliar with the spec, it's been implemented in non-exotic
hardware already.

 > you did proof that, as you said "1.2000000000000002 *is* the correct
answer for 1.19 roundTo: 0.1.".

As a suggestion for these types of problems, try iterating over a
floating point bit range converting each to a true fraction and see
which one is closest?

 > testIfCompletelyBroken [doesn't round to even]

I'd agree rounding to even would be better.


Guillermo,

 > And also: I'd argue that you NEVER EVER want to use simple Floats for
financial applications

Define "financial" applications?  In the risk management world, tying
down that last $1 difference will likely cost more electricity (and way
more time) than the $1 is worth.


John,

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

I agree that ANSI doesn't always make the best sense.  If it were up to
me, I'd follow the relevant standard to the letter.  That's more
important than providing a custom interpretation of an interoperability
mechanism just for Smalltalk (or a particular dialect) by default.


Nicolas,

 > printShowingDecimalPlaces:

That's quite a long selector.  Has anyone considered an approach along
the lines of this?

1.2345 %f #'3.2' => '  1.23'
1.2345 %f #'03.2' => '001.23'


A reminder: calling printf() via FFI is undefined behavior.

Andres.

On 10/26/16 23:31 , Martin McClure wrote:

> On 10/26/2016 08:27 AM, stepharo wrote:
>> So what is the definition of printShowingMaxDecimalDigit:?
>> I have the impression that there is a confusion between the rounding a
>> number and the printing of his value in a given format.
>
> Yes, I see quite a bit of confusion in this thread about that.
>
>> we can deprecate roundTo: if necessary.
>
> I think roundTo: is OK. #round: is not, and should be deprecated (at
> least for Floats). For Floats, the idea of rounding to a specific number
> of decimal digits is a fantasy. Here's why: Floats cannot exactly
> represent most common decimal fractions. For example:
>
> 0.1 -- not representable
>
> 0.2 -- not representable
>
> 0.3 -- not representable
>
> 0.4 -- not representable
>
> 0.5 -- hey, representable!
>
> 0.6 -- not representable
>
> 0.7 -- not representable
>
> 0.8 -- not representable
>
> 0.9 -- not representable
>
> 1.0 -- representable.
>
> *Printing* a Float to a specific rounded decimal format is a sensible
> idea, and should be encouraged. But trying for a "rounded Float"
> *number* just can't be done exactly (except by converting to a Fraction).
>
> Fractions can be rounded to exact decimal fractions, so something like
> "myFraction roundTo: 1/100" makes sense. But the current implementation
> of round: on Fraction that converts to a Float just gets you the misery
> detailed above.
>
>
> On 10/26/2016 01:00 PM, Nicolas Cellier wrote:
>> I've put a single slice in the inbox for the 3 issues because they all
>> are related.
>> See slice comment:
>>
>> For Float, implement guard to prevent overflow (15471), and use exact
>> representation for intermediate results (asFraction) so as to avoid
>> double rounding problems (15473)
>
> The double rounding problem is not the fundamental problem, the
> fundamental problem is that what is desired does not exist, because
> Floats cannot exactly represent most decimal fractions. So this can't
> really fix it.
>
>>
>> For Fraction (15472), choose an exact representation for result rather
>> than Float. This is because such method is particularly usefull for
>> financial/monetary applications.
>>
> Yes. Fraction or ScaledDecimal. Not Floats, unless the developer
> understands the needs of the financial world and the limitations of
> Floats very well.
>
>
> Regards,
>
> -Martin
>

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
On 11/25/2016 08:33 PM, Andres Valloud wrote:

> If "rounding" had well specified behavior that was at least somewhat
> reasonable (e.g. return the closest floating point value and round to
> even if tied), I'd accept floating point results that don't print
> "pretty" in base 10.  That's the way things are, there's no point in
> pretending IEEE-754 is not base 2.
>
> If "rounding" is being done in place of "printing", or obtaining a
> "string representation", I think that won't work as expected because
> 0.1 not representable etc.
>
> For "printing", the algorithms were Dragon4 and Grisu3 last time I
> checked.

Thanks for the reference! Grisu3 looks interesting.

>
> For "floating point that works nice in base 10", there's IEEE-854.  
> For those unfamiliar with the spec, it's been implemented in
> non-exotic hardware already.
>
Base 2 and base 10 are now both covered in IEEE-754 as of the 2008
revision (which also ties up some loose ends that were not specified
adequately in IEEE 754-1985)

Regards,

-Martin


1234