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 Nicolas,
regarding rounding Fractions:
i use fractions if i want to get an exact result (eg for comparing the result with Float calculations). if #round: returns a Float all further calcs (with Fractions) will get contaminated, since the rest will become Floats too. Hence the "asScaledDecimal: numberOfWishedDecimal" seems better to me, but i wonder why these transformations at the end are necessary at all? just for the looks? i'd suppose every person, who knows how to use Fractions, also knows how to append a #asScaledDecimal: to a result by himself, should he want that.

taking as an example financial calcs that first use 2 decimals. occasionaly the final result will be basepoints, often small ones like 0.003. with scaledDecimals the result would be (ok, look like) 0 since scaledDecimals also contaminate the calc. of course one could correct this simply with an #asScaledDecimal:3 at the end. nevertheless a first look at the zero result would surprise me for a tenth of a second.
werner


Hi Werner,
I don't know the purpose of round: at all.
Most often this kind of message was used before printing probably because lack of versatile formatted print messages.
In Squeak I replaced most usages of roundTo: by printShowing(Max)DecimalPlaces:.
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.
we can deprecate roundTo: if necessary.

Now if it has been added in Pharo and other languages, there must be some use cases I presume.
You mean roundTo: no idea.

Maybe the analysis could be carried on these use cases?

Beware, converting a Fraction asScaledDecimal will NOT round.
Only the printString is rounded, but the number keeps its whole precision.
Example (1/3 asScaledDecimal: 1)*3 = 1.0s, not 0.9s.

ScaledDecimals as they are now are just Fraction with a different printString...
Not very much added value.

Nicolas
 
On 10/26/2016 09:58 AM, Nicolas Cellier wrote:


2016-10-26 9:14 GMT+02:00 stepharo <[hidden email]>:
Hi nicolas

So what is the solution? We can integrate fast a solution.
I would really like to see them fix in Pharo 60.
I'm writing a book for newbie and this is the third time I change one chapter
so may be I should stop and throw away this chapter.


1) for Fraction:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asFloat

or just replace asFloat if you wish to remain exact:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asScaledDecimal: numberOfWishedDecimal

2) for Float, it is in 15471:

round: numberOfWishedDecimal
    | v maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self asFraction * v) rounded / v) asFloat

or if Fraction already answers a Float:

round: numberOfWishedDecimal
    | maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    ^ self asFraction round: numberOfWishedDecimal
 
It's slower than current implementation, but will round exactly to the nearest Float.
It's possible to have faster implementation up to 22 decimals if you provide a fused-multiply-accumulate primitive...



Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo
In reply to this post by Nicolas Cellier



Le 26/10/16 à 14:52, Nicolas Cellier a écrit :
Sorry for being slow minded,
yes of course financial apps is exactly the case where round: would be usefull and NEEDS to be exact.

so what is the solution?
I'm lost again.

Hi Nicolas,
yes, i know. suppose you calc some financial thing and one intermediate result will be in $. you have to round this Fraction-result  to 2 decimals. now you use this to further calc basepoints as the final result. you have to convert this to 3 decimals. looking at this final result with 2 decimals can be be irritating for a moment, if you debug the calcs. and <g> btw i never use scaledDecimals.
werner


On 10/26/2016 02:06 PM, Nicolas Cellier wrote:


2016-10-26 13:11 GMT+02:00 test <[hidden email]>:
Hi Nicolas,
regarding rounding Fractions:
i use fractions if i want to get an exact result (eg for comparing the result with Float calculations). if #round: returns a Float all further calcs (with Fractions) will get contaminated, since the rest will become Floats too. Hence the "asScaledDecimal: numberOfWishedDecimal" seems better to me, but i wonder why these transformations at the end are necessary at all? just for the looks? i'd suppose every person, who knows how to use Fractions, also knows how to append a #asScaledDecimal: to a result by himself, should he want that.

taking as an example financial calcs that first use 2 decimals. occasionaly the final result will be basepoints, often small ones like 0.003. with scaledDecimals the result would be (ok, look like) 0 since scaledDecimals also contaminate the calc. of course one could correct this simply with an #asScaledDecimal:3 at the end. nevertheless a first look at the zero result would surprise me for a tenth of a second.
werner


Hi Werner,
I don't know the purpose of round: at all.
Most often this kind of message was used before printing probably because lack of versatile formatted print messages.
In Squeak I replaced most usages of roundTo: by printShowing(Max)DecimalPlaces:.
Now if it has been added in Pharo and other languages, there must be some use cases I presume.
Maybe the analysis could be carried on these use cases?

Beware, converting a Fraction asScaledDecimal will NOT round.
Only the printString is rounded, but the number keeps its whole precision.
Example (1/3 asScaledDecimal: 1)*3 = 1.0s, not 0.9s.

ScaledDecimals as they are now are just Fraction with a different printString...
Not very much added value.

Nicolas
 
On 10/26/2016 09:58 AM, Nicolas Cellier wrote:


2016-10-26 9:14 GMT+02:00 stepharo <[hidden email]>:
Hi nicolas

So what is the solution? We can integrate fast a solution.
I would really like to see them fix in Pharo 60.
I'm writing a book for newbie and this is the third time I change one chapter
so may be I should stop and throw away this chapter.


1) for Fraction:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asFloat

or just replace asFloat if you wish to remain exact:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asScaledDecimal: numberOfWishedDecimal

2) for Float, it is in 15471:

round: numberOfWishedDecimal
    | v maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self asFraction * v) rounded / v) asFloat

or if Fraction already answers a Float:

round: numberOfWishedDecimal
    | maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    ^ self asFraction round: numberOfWishedDecimal
 
It's slower than current implementation, but will round exactly to the nearest Float.
It's possible to have faster implementation up to 22 decimals if you provide a fused-multiply-accumulate primitive...





Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Nicolas Cellier
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)

For Fraction (15472), choose an exact representation for result rather than Float. This is because such method is particularly usefull for financial/monetary applications.

2016-10-26 17:28 GMT+02:00 stepharo <[hidden email]>:



Le 26/10/16 à 14:52, Nicolas Cellier a écrit :
Sorry for being slow minded,
yes of course financial apps is exactly the case where round: would be usefull and NEEDS to be exact.

so what is the solution?
I'm lost again.

Hi Nicolas,
yes, i know. suppose you calc some financial thing and one intermediate result will be in $. you have to round this Fraction-result  to 2 decimals. now you use this to further calc basepoints as the final result. you have to convert this to 3 decimals. looking at this final result with 2 decimals can be be irritating for a moment, if you debug the calcs. and <g> btw i never use scaledDecimals.
werner


On 10/26/2016 02:06 PM, Nicolas Cellier wrote:


2016-10-26 13:11 GMT+02:00 test <[hidden email]>:
Hi Nicolas,
regarding rounding Fractions:
i use fractions if i want to get an exact result (eg for comparing the result with Float calculations). if #round: returns a Float all further calcs (with Fractions) will get contaminated, since the rest will become Floats too. Hence the "asScaledDecimal: numberOfWishedDecimal" seems better to me, but i wonder why these transformations at the end are necessary at all? just for the looks? i'd suppose every person, who knows how to use Fractions, also knows how to append a #asScaledDecimal: to a result by himself, should he want that.

taking as an example financial calcs that first use 2 decimals. occasionaly the final result will be basepoints, often small ones like 0.003. with scaledDecimals the result would be (ok, look like) 0 since scaledDecimals also contaminate the calc. of course one could correct this simply with an #asScaledDecimal:3 at the end. nevertheless a first look at the zero result would surprise me for a tenth of a second.
werner


Hi Werner,
I don't know the purpose of round: at all.
Most often this kind of message was used before printing probably because lack of versatile formatted print messages.
In Squeak I replaced most usages of roundTo: by printShowing(Max)DecimalPlaces:.
Now if it has been added in Pharo and other languages, there must be some use cases I presume.
Maybe the analysis could be carried on these use cases?

Beware, converting a Fraction asScaledDecimal will NOT round.
Only the printString is rounded, but the number keeps its whole precision.
Example (1/3 asScaledDecimal: 1)*3 = 1.0s, not 0.9s.

ScaledDecimals as they are now are just Fraction with a different printString...
Not very much added value.

Nicolas
 
On 10/26/2016 09:58 AM, Nicolas Cellier wrote:


2016-10-26 9:14 GMT+02:00 stepharo <[hidden email]>:
Hi nicolas

So what is the solution? We can integrate fast a solution.
I would really like to see them fix in Pharo 60.
I'm writing a book for newbie and this is the third time I change one chapter
so may be I should stop and throw away this chapter.


1) for Fraction:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asFloat

or just replace asFloat if you wish to remain exact:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asScaledDecimal: numberOfWishedDecimal

2) for Float, it is in 15471:

round: numberOfWishedDecimal
    | v maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self asFraction * v) rounded / v) asFloat

or if Fraction already answers a Float:

round: numberOfWishedDecimal
    | maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    ^ self asFraction round: numberOfWishedDecimal
 
It's slower than current implementation, but will round exactly to the nearest Float.
It's possible to have faster implementation up to 22 decimals if you provide a fused-multiply-accumulate primitive...






Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
In reply to this post by stepharo
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

Sven Van Caekenberghe-2

> On 27 Oct 2016, at 08:31, Martin McClure <[hidden email]> 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).

YES !

Nice explanation.

> 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

Denis Kudriashov
In reply to this post by Martin McClure-2

2016-10-27 8:31 GMT+02:00 Martin McClure <[hidden email]>:

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.

Exactly. Thank's for good explanation
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Nicolas Cellier


2016-10-27 9:51 GMT+02:00 Denis Kudriashov <[hidden email]>:

2016-10-27 8:31 GMT+02:00 Martin McClure <[hidden email]>:

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.

Exactly. Thank's for good explanation

Nonetheless, if user asked to round a Float, we must give him back a Float.
We ain't gonna answer a ScaledDecimal because we think that it's better for him: we don't know what is better for him.
And we MUST do our best to round correctly to the NEAREST Float that is 0.1e0, 0.2e0, ... 1.0e0

If user asked to round a Fraction or ScaledDecimal, then it's different.
We'd better keep the exactness and use ScaledDecimal rather than convert to a Float.

That's exactly these two things that the SLICE posted in inbox is doing.
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
On 10/27/2016 10:34 AM, Nicolas Cellier wrote:


2016-10-27 9:51 GMT+02:00 Denis Kudriashov <[hidden email]>:

2016-10-27 8:31 GMT+02:00 Martin McClure <[hidden email]>:

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.

Exactly. Thank's for good explanation

Nonetheless, if user asked to round a Float, we must give him back a Float.
We ain't gonna answer a ScaledDecimal because we think that it's better for him: we don't know what is better for him.
And we MUST do our best to round correctly to the NEAREST Float that is 0.1e0, 0.2e0, ... 1.0e0

If user asked to round a Fraction or ScaledDecimal, then it's different.
We'd better keep the exactness and use ScaledDecimal rather than convert to a Float.

That's exactly these two things that the SLICE posted in inbox is doing.

i completely agree (well, i would have preferred a pure Fraction result in the pure Fraction case instead of a ScaledDecimal, but so what)

werner

p.s. a pure Fraction result in the pure Fraction case would also insure that the rest of Number is more or less independent of the not very useful ScaledDecimal

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Nicolas Cellier


2016-10-27 11:04 GMT+02:00 test <[hidden email]>:
On 10/27/2016 10:34 AM, Nicolas Cellier wrote:


2016-10-27 9:51 GMT+02:00 Denis Kudriashov <[hidden email]>:

2016-10-27 8:31 GMT+02:00 Martin McClure <[hidden email]>:

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.

Exactly. Thank's for good explanation

Nonetheless, if user asked to round a Float, we must give him back a Float.
We ain't gonna answer a ScaledDecimal because we think that it's better for him: we don't know what is better for him.
And we MUST do our best to round correctly to the NEAREST Float that is 0.1e0, 0.2e0, ... 1.0e0

If user asked to round a Fraction or ScaledDecimal, then it's different.
We'd better keep the exactness and use ScaledDecimal rather than convert to a Float.

That's exactly these two things that the SLICE posted in inbox is doing.

i completely agree (well, i would have preferred a pure Fraction result in the pure Fraction case instead of a ScaledDecimal, but so what)

werner

p.s. a pure Fraction result in the pure Fraction case would also insure that the rest of Number is more or less independent of the not very useful ScaledDecimal


+1
feel free to improve the slice.

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Guillermo Polito
In reply to this post by wernerk

I am also for what Martin says :).


And also: I'd argue that you NEVER EVER want to use simple Floats for financial applications. In financial applications you really need precision, and Floats have hardware limitations that do not provide precision.

I thought ScaledDecimal (which I never used in Pharo) was meant to provide high-precision decimal numbers to the expense of using an internal representation that is not tied to the hardware limitations.

-------- Original Message --------





On 10/27/2016 10:34 AM, Nicolas Cellier
wrote:







2016-10-27 9:51 GMT+02:00 Denis
Kudriashov <[hidden email]>:





2016-10-27 8:31 GMT+02:00
Martin McClure <[hidden email]>:


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.




Exactly. Thank's for good explanation







Nonetheless, if user asked to round a
Float, we must give him back a Float.

We ain't gonna answer a ScaledDecimal because we think that
it's better for him: we don't know what is better for him.

And we MUST do our best to round correctly to the NEAREST
Float that is 0.1e0, 0.2e0, ... 1.0e0




If user asked to round a Fraction or
ScaledDecimal, then it's different.

We'd better keep the exactness and use ScaledDecimal rather
than convert to a Float.




That's exactly these two things that the SLICE posted in inbox
is doing.




i completely agree (well, i would have preferred a pure Fraction
result in the pure Fraction case instead of a ScaledDecimal, but
so what)


werner


p.s. a pure Fraction result in the pure Fraction case would also
insure that the rest of Number is more or less independent of the
not very useful ScaledDecimal









Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by Nicolas Cellier
Hi Guille,
On 10/27/2016 11:15 AM, Guille Polito wrote:

I am also for what Martin says .

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.

And also: I'd argue that you NEVER EVER want to use simple Floats for financial applications. In financial applications you really need precision, and Floats have hardware limitations that do not provide precision.

i'd be careful with such a bold statement. for example think of calculating future values of an asset, based on probabilities. probabilities are inexact anyway.

werner
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

stepharo
In reply to this post by Nicolas Cellier

Thanks ***A LOT*** nicolas.



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)

For Fraction (15472), choose an exact representation for result rather than Float. This is because such method is particularly usefull for financial/monetary applications.

2016-10-26 17:28 GMT+02:00 stepharo <[hidden email]>:



Le 26/10/16 à 14:52, Nicolas Cellier a écrit :
Sorry for being slow minded,
yes of course financial apps is exactly the case where round: would be usefull and NEEDS to be exact.

so what is the solution?
I'm lost again.

Hi Nicolas,
yes, i know. suppose you calc some financial thing and one intermediate result will be in $. you have to round this Fraction-result  to 2 decimals. now you use this to further calc basepoints as the final result. you have to convert this to 3 decimals. looking at this final result with 2 decimals can be be irritating for a moment, if you debug the calcs. and <g> btw i never use scaledDecimals.
werner


On 10/26/2016 02:06 PM, Nicolas Cellier wrote:


2016-10-26 13:11 GMT+02:00 test <[hidden email]>:
Hi Nicolas,
regarding rounding Fractions:
i use fractions if i want to get an exact result (eg for comparing the result with Float calculations). if #round: returns a Float all further calcs (with Fractions) will get contaminated, since the rest will become Floats too. Hence the "asScaledDecimal: numberOfWishedDecimal" seems better to me, but i wonder why these transformations at the end are necessary at all? just for the looks? i'd suppose every person, who knows how to use Fractions, also knows how to append a #asScaledDecimal: to a result by himself, should he want that.

taking as an example financial calcs that first use 2 decimals. occasionaly the final result will be basepoints, often small ones like 0.003. with scaledDecimals the result would be (ok, look like) 0 since scaledDecimals also contaminate the calc. of course one could correct this simply with an #asScaledDecimal:3 at the end. nevertheless a first look at the zero result would surprise me for a tenth of a second.
werner


Hi Werner,
I don't know the purpose of round: at all.
Most often this kind of message was used before printing probably because lack of versatile formatted print messages.
In Squeak I replaced most usages of roundTo: by printShowing(Max)DecimalPlaces:.
Now if it has been added in Pharo and other languages, there must be some use cases I presume.
Maybe the analysis could be carried on these use cases?

Beware, converting a Fraction asScaledDecimal will NOT round.
Only the printString is rounded, but the number keeps its whole precision.
Example (1/3 asScaledDecimal: 1)*3 = 1.0s, not 0.9s.

ScaledDecimals as they are now are just Fraction with a different printString...
Not very much added value.

Nicolas
 
On 10/26/2016 09:58 AM, Nicolas Cellier wrote:


2016-10-26 9:14 GMT+02:00 stepharo <[hidden email]>:
Hi nicolas

So what is the solution? We can integrate fast a solution.
I would really like to see them fix in Pharo 60.
I'm writing a book for newbie and this is the third time I change one chapter
so may be I should stop and throw away this chapter.


1) for Fraction:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asFloat

or just replace asFloat if you wish to remain exact:

round: numberOfWishedDecimal
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self * v) rounded / v) asScaledDecimal: numberOfWishedDecimal

2) for Float, it is in 15471:

round: numberOfWishedDecimal
    | v maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    v := 10 raisedTo: numberOfWishedDecimal.
    ^ ((self asFraction * v) rounded / v) asFloat

or if Fraction already answers a Float:

round: numberOfWishedDecimal
    | maxNumberOfDecimals |
    maxNumberOfDecimals := self class precision - 1 - (self exponent max: self class emin).
    maxNumberOfDecimals < numberOfWishedDecimal ifTrue: [^self].
    ^ self asFraction round: numberOfWishedDecimal
 
It's slower than current implementation, but will round exactly to the nearest Float.
It's possible to have faster implementation up to 22 decimals if you provide a fused-multiply-accumulate primitive...







Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
In reply to this post by Nicolas Cellier
On 10/27/2016 01:34 AM, Nicolas Cellier wrote:
> Nonetheless, if user asked to round a Float, we must give him back a
> Float.
> We ain't gonna answer a ScaledDecimal because we think that it's
> better for him: we don't know what is better for him.

Right, the result of #round: should always be the same kind of number as
the receiver.

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 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
Hi Martin,
of course you are right, but i find it irritating and difficult to remember, if i can calculate something with one kind of number and not with next one, especially Floats & Fractions. imo they should behave similarly, that makes them easier to use for simple minds like me, for example if i compare results with Floats & Fractions.
werner

On 10/27/2016 06:12 PM, Martin McClure wrote:
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

philippeback
In reply to this post by Guillermo Polito
Yes, I was working at a large insurance company and some C code that did calculations on lots and lots of entries triggered an audit due to sizeable discrepancies in monetary terms.

I turned out that the code compiled on a x86 PC didn't gave the same results as the same code compiled on the mainframe.

Given enough loops and runs, the skew became too large to ignore. This led to some serious finger pointing and head scratching from de devs. They found the root cause but, like, 2 months after they started looking.



Phil

On Thu, Oct 27, 2016 at 11:15 AM, Guille Polito <[hidden email]> wrote:

I am also for what Martin says :).


And also: I'd argue that you NEVER EVER want to use simple Floats for financial applications. In financial applications you really need precision, and Floats have hardware limitations that do not provide precision.

I thought ScaledDecimal (which I never used in Pharo) was meant to provide high-precision decimal numbers to the expense of using an internal representation that is not tied to the hardware limitations.

-------- Original Message --------





On 10/27/2016 10:34 AM, Nicolas Cellier
wrote:







2016-10-27 9:51 GMT+02:00 Denis
Kudriashov <[hidden email]>:





2016-10-27 8:31 GMT+02:00
Martin McClure <[hidden email]>:


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.




Exactly. Thank's for good explanation







Nonetheless, if user asked to round a
Float, we must give him back a Float.

We ain't gonna answer a ScaledDecimal because we think that
it's better for him: we don't know what is better for him.

And we MUST do our best to round correctly to the NEAREST
Float that is 0.1e0, 0.2e0, ... 1.0e0




If user asked to round a Fraction or
ScaledDecimal, then it's different.

We'd better keep the exactness and use ScaledDecimal rather
than convert to a Float.




That's exactly these two things that the SLICE posted in inbox
is doing.




i completely agree (well, i would have preferred a pure Fraction
result in the pure Fraction case instead of a ScaledDecimal, but
so what)


werner


p.s. a pure Fraction result in the pure Fraction case would also
insure that the rest of Number is more or less independent of the
not very useful ScaledDecimal










Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Martin McClure-2
On 10/27/2016 01:53 PM, [hidden email] wrote:
> Yes, I was working at a large insurance company and some C code that did
> calculations on lots and lots of entries triggered an audit due to
> sizeable discrepancies in monetary terms.

[...]

>
> Also there is the banker's
> rounding. https://www.eecis.udel.edu/~breech/contest.inet.fall.07/problems/bankers-rounding.html
> <https://www.eecis.udel.edu/%7Ebreech/contest.inet.fall.07/problems/bankers-rounding.html>
>
>

Yes! When we were re-doing our ScaledDecimal implementation a few years
ago, I moved GemStone to Banker's Rounding (round-to-even). It's fairly
simple to implement, and is important to avoid biases in complex
computerized calculations.

I suspect that the "standard" rounding rule (if the digit is 5, round
up) is a leftover from manual arithmetic -- if you're doing, say, long
division, and you get a five, and the remainder is non-zero, you know
that the quotient is actually greater than that, so you round up. And if
you *don't* know what those digits beyond the 5 are, you round up
anyway, because the only time you don't is if they're *all* zeroes.

But with computer math it's easy to figure out whether all the digits
will be zeroes or not, so we can use the better rounding rule.

Regards,

-Martin

Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

wernerk
In reply to this post by Nicolas Cellier
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

Henrik Nergaard-2

Hi,

 

To view changes from a slice using Monticello Browser:

Select the slice-package and then the Pharo60/main repository before pressing changes. (See attached picture)

 

Best regards,

Henrik

 

 

From: Pharo-dev [mailto:[hidden email]] On Behalf Of test
Sent: Saturday, October 29, 2016 5:01 PM
To: Pharo Development List <[hidden email]>
Subject: Re: [Pharo-dev] roundTo: strange behavior

 

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.


changesInSlice.PNG (382K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: roundTo: strange behavior

Ben Coman
In reply to this post by wernerk
On Sat, Oct 29, 2016 at 11:01 PM, test <[hidden email]> wrote:
> 2. when i make a slice i find no possibility to save it to my pc,

Save it to package-cache (Henrik's snapshot),
then later <Copy> it from package-cache to Pharo6Inbox.

Note that <Copy> doesn't automatically request your password
for Pharo6Inbox like when you <Save> direct to it. You will need to
edit its repository definition to add manually add your account & password.

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

I find the preview provided by the <Merge> button
more often gives me what I want to review my Slices in a fresh Image.

cheers -ben

1234