> On 9 Apr 2020, at 16:32, Gilles Sadowski <gillese...@gmail.com> wrote:
> 
> Le jeu. 9 avr. 2020 à 16:48, Alex Herbert <alex.d.herb...@gmail.com 
> <mailto:alex.d.herb...@gmail.com>> a écrit :
>> 
>> 
>> On 09/04/2020 14:38, Gilles Sadowski wrote:
>>> Le jeu. 9 avr. 2020 à 14:09, Alex Herbert <alex.d.herb...@gmail.com> a 
>>> écrit :
>>>> 
>>>> On 09/04/2020 12:04, Alex Herbert wrote:
>>>>>>> So if we are to support 0/-1 then I will add this to the standard test
>>>>>>> cases to make sure it is correctly implemented.
>>>> So I did this. No major functionality failures.
>>>> 
>>>> However there is a discrepancy between multiply and other functions.
>>>> 
>>>> Multiply will return the form 0 / 1 for zero when either argument is zero.
>>>> 
>>>> 
>>>> Divide will return the current instance if the instance is zero.
>>>> 
>>>> Add/subtract will return the current instance if the instance to add is
>>>> zero.
>>>> 
>>>> Pow will return the current instance if the instance is zero.
>>>> 
>>>> 
>>>> So you get this:
>>>> 
>>>> 0 / -1   multiply   1 / 3    == 0 / 1
>>>> 
>>>> 
>>>> 0 / -1   divide   1 / 3    == 0 / -1
>>>> 
>>>> 
>>>> 0 / -1   add   0 / 1    == 0 / -1
>>>> 
>>>> 0 / -1   pow   1    == 0 / -1
>>>> 
>>>> 0 / -1   pow   2    == 0 / -1
>>>> 
>>>> 
>>>> In the later case you may actually expect pow(0 / -1, 2) to return 0 /
>>>> 1. The result would require a change of sign on the denominator when the
>>>> power is even.
>>>> 
>>>> 
>>>> So do we:
>>>> 
>>>> 1. return the canonical form 0 / 1 when the result is zero?
>>>> 
>>>> 2. return the appropriate instance that is zero when the result is zero?
>>>> 
>>>> 3. maintain the sign as if implemented using the arithmetic on the
>>>> denominator?
>>>> 
>>>> 
>>>> I do not see a situation where preserving the signedness of the zero
>>>> denominator is important. This rules out 3.
>>> But didn't you imply above that
>>>   0 / -1   pow   2
>>> should return
>>>   0 / 1
>>> ?
>> 
>> If you computed pow(2) it as (0 * 0) / (-1 * -1) then the result is 0 /
>> 1. Likewise pow(3) is 0 / -1. But both are zero.
>> 
>> If you see the result as zero then the sign of the denominator does not
>> matter.
>> 
>> If you want to preserve the sign of the denominator then more work will
>> have to be done in certain methods. I do not think this is important.
> 
> So there is no issue that several representations come up
> during computations (?).

I do not think so. At lest I have not found an issue where the computation is 
broken.

> 
>> Note: This is in contrast to Complex where the sign of zero components
>> of the complex number have to be preserved to ensure compatibility with
>> ISO C99 standards.
>> 
>> So do we leave it to produce 0 / -1 or 0 / 1 depending on the
>> computation (as is done currently), fix it to preserve the sign
>> appropriately or fix it to ensure that zero is always a canonical 0 / 1?
> 
> Is there any advantage to a single zero representation?
> If not, I'd vote for the simplest code.
> 
>>> 
>>>> It makes it cleaner in the code to always return Fraction.ZERO when
>>>> operations result in zero. This has memory advantage by allowing the
>>>> garbage collection of fractions that are zero when they go out of scope
>>>> following computations.
>>> +1
>> 
>> Returning 0 / 1 for zero when possible is simple to do in the existing
>> methods that detect zero as one of the arguments.
> 
> if (this.isZero()) { return ZERO; }
> if (this.isZero()) { return this; }
> 
> Is one better than the other?
> If not, I'd vote for the former (referring to allowing garbage collection).

I will make this change as it makes the code more readable IMO. It is very 
clear the result is zero.

> 
>>>> This is a weak argument as it is unlikely to
>>>> save much memory in common usage. It is also possible to create zeros in
>>>> computation and we do not want to go the route of switching new 0/1
>>>> instances to ZERO.
>>> Why not?
>> It is extra work in the computations since computations do not detect
>> zeros in the result. They detect zeros in the arguments and can fast
>> rack the result.
> 
> I thought that we were indeed referring to the above case (this
> vs ZERO).
> 
>> For those that actually compute a result then the result would require
>> construction using a factory constructor that will detect and return
>> ZERO if the numerator is zero.
> 
> IIUC, this should be avoided, since ZERO will be returned at
> the next opportunity (e.g. if the new instance calls "multiply")
> without incurring any performance cost.

An interesting point. And if the ultimate output for the fraction is conversion 
to a double then it would not matter anyway.

So I will check all the conversions work with the 0 / -1 representation of zero.

> 
>> This would add another if statement to
>> all usage of the factory constructor and effect performance. However
>> performance impact is not likely to be high (see below).
>> 
>> It should solve the previous issue that you can represent zero as 0 /
>> -1. If all computations use a factory constructor then we can make it
>> possible to exclude the 0 / -1 representation completely.
>> 
>>> 
>>>> The easiest change is to just update multiply to return the appropriate
>>>> zero instance and not use Fraction.ZERO. The classes then are consistent
>>>> across the methods. Return the appropriate fraction instance if it
>>>> matches the result, otherwise compute the result.
>>> I don't follow: How do you match before computing?
>> 
>> I missed out that one of the function arguments match the result when
>> zero is involved as an argument. All the arithmetic methods detect zeros
>> in the arguments and quickly return.
>> 
>> It would be cleaner to try and enforce a canonical form for ZERO.
> 
> From the above development, I was arriving to the other
> conclusion...
> 
>> Looking at the code for the standard constructor using Fraction(num,
>> den) there are already a few if statements in there. In most cases it
>> then follows the path using the reduction with the greatest common
>> divisor logic. This is bound to be the largest part of the execution
>> time. Forcing all construction through the of(num, den) factory method
>> and adding another if statement to detect a zero numerator should not
>> impact performance too much. The class can avoid the factory constructor
>> if the value has already been tested and known to be non zero. This is
>> the case for methods that use a numerator and not a denominator (such as
>> pow(int)).
> 
> Well, personally, I don't mind, but I had the impression that,
> in "Complex.java" for example, you tended to go the opposite
> way (i.e. call the constructor directly whenever possible).

In Complex the constructor is dumb and so is the factory constructor 
ofCartesian(x, y). It simply stores/passes through the values. All the work to 
maintain the ISO C99 compliance is done in the methods.

> 
>> Given this I am thinking that using ZERO when possible is a better
>> option and avoid 0 / -1.
> 
> Hmm, then I'm both +0 and -0 (which is the same, right?)
> on this issue. ;-)
> 
>> 
>> Also, did you have an opinion on dropping pow(long) and pow(BigInteger)?
>> I do not think that they can compute a value when pow(int) cannot be used.
> 
> Isn't there a JSR project to extend the range of "BigInteger"? ;-)

I do not know.

> 
> +1 to remove, but perhaps add an explanation in the Javadoc
> for "pow(int)" to the effect that users should request the missing
> methods.

I will remove these pow methods. They can be added back when BigInteger 
supports larger numbers. In that case the unit test using 
BigFraction.of(2).pow(Integer.MAX_VALUE) will no longer throw an exception 
which will notify us that BigFraction can be extended.

I’ll make some changes to return ZERO when possible in methods. When doing this 
I will think some more on whether the factory constructor should do so and the 
changes that it would involve.

Alex


> 
> Best,
> Gilles
> 
>>>> The ZERO constant then is reduced to a convenience as for the ONE constant.
>>>> 
>>>> So my vote is option 2 for code consistency.
>>>> 
>>>> 
>>>> Alex
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org 
> <mailto:dev-unsubscr...@commons.apache.org>
> For additional commands, e-mail: dev-h...@commons.apache.org 
> <mailto:dev-h...@commons.apache.org>

Reply via email to