[ 
https://issues.apache.org/jira/browse/NUMBERS-79?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16852413#comment-16852413
 ] 

Heinrich Bohne edited comment on NUMBERS-79 at 5/30/19 10:33 PM:
-----------------------------------------------------------------

I found this ticket by accident, and it made me think. And the more I have 
thought about it, the more convinced I am that the calculation where the 
original code claims to require 65 bits of precision can, in fact, _always_ be 
performed using only {{long}} values without causing overflow. First, to 
recollect the problem at hand:

The objective is to calculate ^m^⁄~a~ + ^n^⁄~b~, where m is coprime to a and n 
is coprime to b. Now, let d be the greatest common divisor of a and b, then the 
calculation becomes

^m^⁄~p⋅d~ + ^n^⁄~q⋅d~

, where p and q are coprime, and m is coprime to p and d, and n is coprime to q 
and d. Continuing this, we get:

^m^⁄~p⋅d~ + ^n^⁄~q⋅d~
 = ^m⋅q^⁄~p⋅q⋅d~ + ^n⋅p^⁄~p⋅q⋅d~
 = ^m⋅q + n⋅p^⁄~p⋅q⋅d~

The variables m, n, p and q can all be represented as an {{int}}, therefore, 
the terms m⋅q and n⋅p can each be represented as a {{long}}. The question is, 
can m⋅q + n⋅p be represented as a {{long}}? To examine this, let's consider the 
worst case scenario, where every one of these 4 variables has the largest 
possible absolute value an {{int}} can have, which is 2​^31^. This is only 
possible if the {{int}} value is negative, i.e. -2​^31^. So then, our numerator 
term becomes:

m⋅q + n⋅p
 = (-2​^31^) ⋅ (-2​^31^) + (-2​^31^) ⋅ (-2​^31^)
 = 2​^63^

, which exceeds the largest possible {{long}} value only by one. However, this 
scenario will never arise, because m and p are coprime, just like n and q are 
coprime and q and p are coprime. So these 4 variables cannot all be -2​^31^, 
which means that the absolute value of the term m⋅q + n⋅p _must_ be smaller 
than 2​^63^ and therefore representable as a {{long}}.

Perhaps the source the code comments refer to is talking about unsigned 
integers, in which case 64 bits would indeed not be enough, as far as I can 
tell.

However, the current version in the fraction-dev branch, which, I assume, 
contains the fix to this ticket's issue, restricts even the intermediate values 
in the above calculation to {{int}} instead of {{long}} type, which would be 
what this ticket suggests. This creates the problem of throwing an exception 
even if the final value can be represented as an {{int}}, because, while m⋅q + 
n⋅p is definitely coprime to p and q, it could potentially share a factor with 
d, so even if m⋅q + n⋅p cannot be represent as an {{int}}, the final numerator 
could. For example:

^362,564,597^⁄~10~ + ^274,164,323^⁄~6~ = ^1,229,257,703^⁄~15~

All numerators and denominators of these fractions can be expressed as an 
{{int}}, but the current code in fraction-dev fails to calculate this sum, 
because it restricts even intermediate values to type {{int}}, and not just the 
final values. If it were to store and perform calculations on intermediate 
values as {{long}} variables, then it would work.

Since the data type {{long}} will always be sufficient for intermediate values, 
thus eliminating the need for {{BigInteger}}, I don't see a reason to use 
{{int}} instead of {{long}} values if this could cause the method to fail when 
it would otherwise calculate the correct result.


was (Author: schamschi):
I found this ticket by accident, and it made me think. And the more I have 
thought about it, the more convinced I am that the calculation where the 
original code claims to require 65 bits of precision can, in fact, _always_ be 
performed using only {{long}} values without causing overflow. First, to 
recollect the problem at hand:

The objective is to calculate ^m^⁄~a~ + ^n^⁄~b~, where m is coprime to a and n 
is coprime to b. Now, let d be the greatest common divisor of a and b, then the 
calculation becomes

^m^⁄~p⋅d~ + ^n^⁄~q⋅d~

, where p and q are coprime, and m is coprime to p and d, and n is coprime to q 
and d. Continuing this, we get:

^m^⁄~p⋅d~ + ^n^⁄~q⋅d~
 = ^m⋅q^⁄~p⋅q⋅d~ + ^n⋅p^⁄~p⋅q⋅d~
 = ^m⋅q + n⋅p^⁄~p⋅q⋅d~

The variables m, n, p and q can all be represented as an {{int}}, therefore, 
the terms m⋅q and n⋅p can each be represented as a {{long}}. The question is, 
can m⋅q + n⋅p be represented as a {{long}}? To examine this, let's consider the 
worst case scenario, where every one of these 4 variables has the largest 
possible absolute value an {{int}} can have, which is 2​^31^. This is only 
possible if the {{int}} value is negative, i.e. -2​^31^. So then, our numerator 
term becomes:

m⋅q + n⋅p
 = (-2​^31^) ⋅ (-2​^31^) + (-2​^31^) ⋅ (-2​^31^)
 = 2​^63^

, which exceeds the largest possible {{long}} value only by one. However, this 
scenario will never arise, because m and p are coprime, just like n and q are 
coprime and q and p are coprime. So these 4 variables cannot all be -2​^31^, 
which means that the absolute value of the term m⋅q + n⋅p _must_ be smaller 
than 2​^63^ and therefore representable as a {{long}}.

Perhaps the source the code comments refer to is talking about unsigned 
integers, in which case 64 bits would indeed not be enough, as far as I can 
tell.

However, the current version in the fraction-dev branch, which, I assume, 
contains the fix to this ticket's issue, restricts even the intermediate values 
in the above calculation to {{int}}s instead of {{longs}}, which would be what 
this ticket suggests. This creates the problem of throwing an exception even if 
the final value can be represented as an {{int}}, because, while m⋅q + n⋅p is 
definitely coprime to p and q, it could potentially share a factor with d, so 
even if m⋅q + n⋅p cannot be represent as an {{int}}, the final numerator could. 
For example:

^362,564,597^⁄~10~ + ^274,164,323^⁄~6~ = ^1,229,257,703^⁄~15~

All numerators and denominators of these fractions can be expressed as an 
{{int}}, but the current code in fraction-dev fails to calculate this sum, 
because it restricts even intermediate values to {{int}}s, and not just the 
final values. If it were to store and perform calculations on intermediate 
values as {{long}}s, then it would work.

Since the data type {{long}} will always be sufficient for intermediate values, 
thus eliminating the need for {{BigInteger}}s, I don't see a reason to use 
{{int}}s instead of {{long}}s if this could cause the method to fail when it 
would otherwise calculate the correct result.

> Convert fraction addition from BigInteger-based to long-based
> -------------------------------------------------------------
>
>                 Key: NUMBERS-79
>                 URL: https://issues.apache.org/jira/browse/NUMBERS-79
>             Project: Commons Numbers
>          Issue Type: Improvement
>            Reporter: Eric Barnhill
>            Assignee: Eric Barnhill
>            Priority: Minor
>
> Current Fraction class uses BigInteger due to the fact that in extreme cases 
> adding and subtraction of fractions can require 65 bits of precision. However 
> this is very costly in terms of performance for all but the most extreme 
> cases. This ticket proposes using long-based arithmetic for Fraction addition 
> and subtraction, and recommending BigFraction if the operation may run very 
> large.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to