Decimals as fractions

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

Decimals as fractions

K K Subbu
Hi,

Squeak compiler compiles numbers like "3.4" into SmallFloat types. But
Squeak can also handle number pairs like Point and Fractions. Has anyone
considered decimals into fractions, instead? Like how we treat fractions
in real life? $0.02 is really 2/100th of a dollar. I wouldn't want it to
float ;-).

Squeak could compile fix points like "3.4" into fractions like "34/10"
and compile into float only if an explicit exponent is given like in
"3.4e0". This would help us maintain accuracy as long as possible in
fraction arithmetic we use in daily life. e.g.

3.41 - 3.40 0.010000000000000231
vs
((341/100) - (340/100)) asFloat 0.01

Regards .. Subbu

Reply | Threaded
Open this post in threaded view
|

Re: Decimals as fractions

Nicolas Cellier
Hi,
Yes sure, that's a possibility.
But that means changing well established Smalltalk syntax, and thus modify a huge library of source code...

We currently have ScaledDecimal with 3.41s or 3.42s2 syntax.
The problem with ScaledDecimal is that their printing is ambiguous: two different ScaledDecimal may print the same.

    {1.0s2 / 3.0s2. 0.33s2} collect: #printString.
    {1.0s2 / 3.0s2. 0.33s2} collect: #reciprocal.

They just truncate in Squeak (and maybe round in Pharo, not even with banker rounding).
This is because they are just Fraction in disguise, and that's their second problem: repeated computations could lead to monstruous fractions.

That's why I'm interested in experiments: is their usage sustainable, or does it lead to explosion?
It's very easy to experiment: introduce a new NumberParser subclass and connect it to Smalltalk Parser (via Scanner>>#xDigit).
Then, carefully recompile some part of the system, and discover the vital source code where Float literals are required.

We could also introduce FixedPoint, a number that would round to a fixed number of fractional digits. And maybe DecimalFixedPoint and BinaryFixedPoint for the two principal usefull flavours.
The problem with FixedPoint is that we must decide in advance how many digits?
A FixedPoint vs FloatingPoint is trading a bit more precision vs lot less range, so it's maybe not that intersting for a general purpose library...

Or we could have a DecimalFloat, a Floating point in base 10. https://en.wikipedia.org/wiki/Decimal_floating_point
Maybe that's the practical universal Number that we are after?
Of course, like the other kinds above, it would be emulated...

Le dim. 7 avr. 2019 à 05:10, K K Subbu <[hidden email]> a écrit :
Hi,

Squeak compiler compiles numbers like "3.4" into SmallFloat types. But
Squeak can also handle number pairs like Point and Fractions. Has anyone
considered decimals into fractions, instead? Like how we treat fractions
in real life? $0.02 is really 2/100th of a dollar. I wouldn't want it to
float ;-).

Squeak could compile fix points like "3.4" into fractions like "34/10"
and compile into float only if an explicit exponent is given like in
"3.4e0". This would help us maintain accuracy as long as possible in
fraction arithmetic we use in daily life. e.g.

3.41 - 3.40 0.010000000000000231
vs
((341/100) - (340/100)) asFloat 0.01

Regards .. Subbu


Reply | Threaded
Open this post in threaded view
|

Re: Decimals as fractions

Stéphane Rollandin
In reply to this post by K K Subbu

> 3.41 - 3.40 0.010000000000000231
> vs
> ((341/100) - (340/100)) asFloat 0.01

On my system:

[3.41 - 3.40] bench
        --> '76,200,000 per second. 13.1 nanoseconds per run.'

[((341/100) - (340/100)) asFloat] bench
        --> '1,230,000 per second. 816 nanoseconds per run.'

a := 341/100.
b := 340/100.
[a - b] bench
        --> '4,050,000 per second. 247 nanoseconds per run.'

Stef

Reply | Threaded
Open this post in threaded view
|

Re: Decimals as fractions

K K Subbu
In reply to this post by Nicolas Cellier
On 07/04/19 7:46 PM, Nicolas Cellier wrote:
> Hi,
> Yes sure, that's a possibility.
> But that means changing well established Smalltalk syntax, and thus
> modify a huge library of source code...

I am not suggesting any changes to syntax (yet!). I was exploring an
idea of storing decimal numbers like '3.41' in a lossless form and
convert to float on demand (possibly cached). Internal format would be
totally transparent to the rest of the code. We already do these for
integers:

(1 / 1) class SmallInteger
(1 / 3) class Fraction
(3 / 1) class SmallInteger

> We currently have ScaledDecimal with 3.41s or 3.42s2 syntax.
> The problem with ScaledDecimal is that their printing is ambiguous: two
> different ScaledDecimal may print the same.
>
>      {1.0s2 / 3.0s2. 0.33s2} collect: #printString.
>      {1.0s2 / 3.0s2. 0.33s2} collect: #reciprocal.

Is Squeak 5.3alpha/64b/Linux, I get

{1.0s2 / 3.0s2. 0.33s2} collect: #reciprocal {3.00s2 . 3.03s2}.

But I get your point. For decimal numbers, using both denominator and
scale is an overkill. Printing can be handled by the existing
printFractionAs... . Reciprocal of a decimal number could become
imprecise operation, so we could fall back to Float when necessary.

With decimal numbers, we could print
  0.25 reciprocal = 4
instead of the current
  0.25 reciprocal = 4.166666666666667

> They just truncate in Squeak (and maybe round in Pharo, not even with
> banker rounding).
> This is because they are just Fraction in disguise, and that's their
> second problem: repeated computations could lead to monstruous fractions.
Like Pi you mean ;-). True. But, most real life scenarios involve only
small denominators like 2, 4, 5, 100 or 1000. Large denominators could
be handled through exceptions and reduction through gcd.

> That's why I'm interested in experiments: is their usage sustainable, or
> does it lead to explosion?
> It's very easy to experiment: introduce a new NumberParser subclass and
> connect it to Smalltalk Parser (via Scanner>>#xDigit).
> Then, carefully recompile some part of the system, and discover the
> vital source code where Float literals are required.

Thanks a ton for your encouraging words and the tips. I will give them a
try.

> Or we could have a DecimalFloat, a Floating point in base 10.
> https://en.wikipedia.org/wiki/Decimal_floating_point
> Maybe that's the practical universal Number that we are after?
> Of course, like the other kinds above, it would be emulated...

A variant - DecimalNumber. It would extend the idea of place values to
the negative axis without the drawbacks of IEEE754-2008.

I suppose it would be computationally inefficient compared to Float, but
definitely more accurate in common scenarios. I would love to see "0.25
reciprocal" evaluate to 4 ;-).

Regards .. Subbu

Reply | Threaded
Open this post in threaded view
|

Re: Decimals as fractions

K K Subbu
In reply to this post by Stéphane Rollandin
On 07/04/19 8:07 PM, Stéphane Rollandin wrote:

>
>> 3.41 - 3.40 0.010000000000000231
>> vs
>> ((341/100) - (340/100)) asFloat 0.01
>
> On my system:
>
> [3.41 - 3.40] bench
>      --> '76,200,000 per second. 13.1 nanoseconds per run.'
>
> [((341/100) - (340/100)) asFloat] bench
>      --> '1,230,000 per second. 816 nanoseconds per run.'
>
> a := 341/100.
> b := 340/100.
> [a - b] bench
>      --> '4,050,000 per second. 247 nanoseconds per run.'

Thank you for the quick response. The slowdown is to be expected. Floats
are immediate while Fractions are objects. I floated a strawman proposal
to tradeoff accuracy for speed.

Regards .. Subbu

Reply | Threaded
Open this post in threaded view
|

Re: Decimals as fractions

K K Subbu
In reply to this post by K K Subbu
On 08/04/19 2:52 PM, K K Subbu wrote:

>
>> We currently have ScaledDecimal with 3.41s or 3.42s2 syntax.
>> The problem with ScaledDecimal is that their printing is ambiguous:
>> two different ScaledDecimal may print the same.
>>
>>      {1.0s2 / 3.0s2. 0.33s2} collect: #printString.
>>      {1.0s2 / 3.0s2. 0.33s2} collect: #reciprocal.
>
> Is Squeak 5.3alpha/64b/Linux, I get
>
> {1.0s2 / 3.0s2. 0.33s2} collect: #reciprocal {3.00s2 . 3.03s2}.
>
> But I get your point. For decimal numbers, using both denominator and
> scale is an overkill. Printing can be handled by the existing
> printFractionAs... . Reciprocal of a decimal number could become
> imprecise operation, so we could fall back to Float when necessary.
>
> With decimal numbers, we could print
>   0.25 reciprocal = 4
> instead of the current
>   0.25 reciprocal = 4.166666666666667

PBKAC! mea culpa. I meant to say I expected 4 instead of 4.0.

ScaledDecimal looks promising but I don't know why it doesn't reduce
like Fraction:

   self assert: (0.25s reciprocal class) classAndValueEquals: 4

Regards .. Subbu

Reply | Threaded
Open this post in threaded view
|

Re: Decimals as fractions

Stéphane Rollandin
In reply to this post by K K Subbu
> Thank you for the quick response. The slowdown is to be expected. Floats
> are immediate while Fractions are objects. I floated a strawman proposal
> to tradeoff accuracy for speed.

In my own software, and for example in my games, speed is often
paramount. I got some spectacular improvements on overall performance by
liberaly using #asFloat. I even implemented #/// as follow:

Number /// aNumber

        ^ self / aNumber asFloat

for cases (which I found many) where I want to ensure that a division
will never return a Fraction.

Stef