Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sqrt feature #19

Merged
merged 12 commits into from
Dec 6, 2018
Merged

Add sqrt feature #19

merged 12 commits into from
Dec 6, 2018

Conversation

peter279k
Copy link
Contributor

As title, it's related to issue #18.

{
$x = $a;
if ($a[0] === '-') {
return NAN;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAN is a float, not a string; you cannot return NAN. We should throw an exception here. Actually we can even do nothing: Calculator is not part of the public API, and assumes that all input is already validated.

As such, we can perform the test only in the BigNumber classes, you can safely remove this check.

@BenMorel
Copy link
Member

BenMorel commented Dec 4, 2018

This looks like a good start! 👍

A few guidelines:

  • you need to add an abstract sqrt() method to the base Calculator class, to be used by BigNumber classes
  • the sqrt() method should be added to BigInteger: BigInteger sqrt() : BigInteger
  • ideally, if you can handle scale, the sqrt() method should be added to BigDecimal: BigDecimal sqrt(int $scale) : BigDecimal (1)
  • ideally, all rounding modes should be supported (2)
  • We'll need a lot of tests, not targeted to invididual calculators, but to BigInteger and BigDecimal directly; some with values that have an exact square root, and others that must be rounded

(1) I don't mind if the sqrt() method only exists on BigInteger for now. We can always refactor later to add BigDecimal support with scale.

(2) I don't mind if, for now, a single rounding is performed (most likely, rounding down), but it needs to be documented, consistent across calculators, and well tested.

@peter279k
Copy link
Contributor Author

peter279k commented Dec 4, 2018

@BenMorel, thank you for your reply. I think we can do the following works at this time:

  • Adding an abstract sqrt() method to the base Calculator class, to be used by BigNumber classes
  • The sqrt() method should be added to BigInteger: BigInteger sqrt() : BigInteger.

Can you explain more details about how to add the sqrt method to the BigInteger class?

Thanks.

@BenMorel
Copy link
Member

BenMorel commented Dec 4, 2018

Calculator is an internal class, it's meant to be consumed by BigInteger, BigDecimal and BigRational, but never be used directly by the end user.

So adding sqrt() only in Calculator makes it unavailable to users of the library, what they'll be looking for is BigInteger::sqrt() or BigDecimal::sqrt()!

Check how other methods are implemented (add(), mul() etc.) , they basically:

  • validate the input
  • perform the operation on the Calculator
  • wrap the output in a BigInteger or BigDecimal class

@peter279k
Copy link
Contributor Author

Hi @BenMorel, thank you for your reply. I've added the sqrt method inside BigInteger class.

Please check that. I also do the input validation, perform operation with Calculator class and return the BigInteger wrapper.

If having any problem, please write the comment on that line of code.

Thanks.

src/BigInteger.php Outdated Show resolved Hide resolved
src/BigInteger.php Outdated Show resolved Hide resolved
src/BigInteger.php Outdated Show resolved Hide resolved
src/BigInteger.php Outdated Show resolved Hide resolved
src/BigInteger.php Outdated Show resolved Hide resolved
@BenMorel
Copy link
Member

BenMorel commented Dec 5, 2018

Hi @peter279k, I've actually fixed the issues above myself, and also added a good number of tests for BigInteger::sqrt().

It seems to work fine, however the current implementation of sqrt() in NativeCalculator is awfully slow: on my machine it takes roughly 1s to calculate the square root of a number with 100 digits.

This makes tests fail as they take too long to complete.

Of course NativeCalculator will always be slower than GMP or BCMath, but to give you an idea, the same sqrt() on GMP takes 0.000002 seconds (2 µs!), so there must be a better, faster algorithm to calculate the square root.

Can you please investigate? Thanks!

@peter279k
Copy link
Contributor Author

Hi @BenMorel, thank you for your reply.

Actually I use the Newton method to calculate square root number.

I don't think other methods can be faster than this method. And other methods will be slower than Newton method.

@BenMorel
Copy link
Member

BenMorel commented Dec 5, 2018

Actually the issue with the tests is not the slowness of the algorithm, it's that there's an infinite loop. Try:

BigInteger::of('3')->sqrt();

Could you please fix this?

@peter279k
Copy link
Contributor Author

@BenMorel, thank you for your reply. I will fix that later.

@BenMorel
Copy link
Member

BenMorel commented Dec 5, 2018

Why the last 2 commits? You explicitly created another infinite loop.

@peter279k
Copy link
Contributor Author

@BenMorel, I misunderstand your request. I will revert them.

Forgive me I'm tired recently....

/**
* {@inheritDoc}
*/
public function sqrt(string $a) : string
Copy link
Contributor Author

@peter279k peter279k Dec 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @BenMorel, after tracing source code, It looks strange.

Firstly, consider following approach:

$ans = 3;
$x = 3;
$del = 0;
$pre = 0;

while (abs($ans - $pre) > 0) {
    $pre = $ans;
    $ans = ($ans + $x / $ans) / 2;
}
echo $ans;

It worked fine when we let $ans=3 calculate sqrt result.

If we use this sqrt method, the result will happen infinite loop.

I guess the operator calculations have some problems and this will cause the infinite loop.

Copy link
Contributor Author

@peter279k peter279k Dec 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we have to let the division operator calculation allow float string result?

I think it can solve this infinite loop problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the other operators are bugged, they're well tested, the problem most likely lies in the sqrt algorithm. And no, you can't allow float, this would defeat the whole purpose of an arbitrary precision library, that must deal with exact calculations of any size.

Copy link
Contributor Author

@peter279k peter279k Dec 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I think it's still about the numbers problem. Consider following code snippet:

$ans = 3;
$x = 3;
$del = 0;
$pre = 0;

while (abs($ans - $pre) > 0) {
    $pre = $ans;
    $ans = (int) ((int) ($ans +  (int) $x / $ans) / 2);
    var_dump($ans);
}
echo $ans;

It lets all division operator calculation result will be the integer. And this will cause the infinite loop problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You most likely adapted some code that is supposed to deal with floats, and trying to make it work with integer arithmetic. You need to fix this algo, or find another one.

Note that it works fine for other sqrt() tests, so it can probably work with a few tweaks, if you really take the time to understand it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS in this Newton pseudocode, it looks like you're using a zero Epsilon (error tolerance), which returns the exact result but may end up with an infinite loop with some numbers in our case.

When I change cmp($checkedSqrtNumber, '0') to cmp($checkedSqrtNumber, '1'), the infinite loop is gone, but some tests fail, for example:

--- Expected
+++ Actual

-'7'
+'8'

-'66291728919896041699308382947726934'
+'66291728919896041699308382947726935'

Always wrong by 1 on the last digit. I guess this is where the issue lies, we need to use a zero epsilon indeed, but find a way to detect and prevent the infinite loop.

@BenMorel
Copy link
Member

BenMorel commented Dec 6, 2018

OK I changed the algorithm to the one described here:

https://cp-algorithms.com/num_methods/roots_newton.html

Which describes the problem well:

Another common variant of the problem is when we need to calculate the integer root (for the given n find the largest x such that x²≤n).

And now all tests pass. I'll merge this now, and will polish it later: we can speed up the algorithm by starting with a closer approximation (they use 2^(bits/2), but we can probably use something less expensive to calculate in NativeCalculator).

I'll probably release it later today. 👍

@BenMorel BenMorel merged commit b0f05e0 into brick:master Dec 6, 2018
@BenMorel BenMorel mentioned this pull request Dec 6, 2018
@BenMorel
Copy link
Member

BenMorel commented Dec 6, 2018

@peter279k This little change on the initial approximation made sqrt() nearly 20x faster!

There is always room for improvement :)

@peter279k
Copy link
Contributor Author

peter279k commented Dec 6, 2018

@BenMorel, thank you for your reply. I appreciate your help and review.

I also find another way to calculate the square root number.

The latest Travis CI build log is available here and here is the code.

I will keep this finding the better way to optimize this sqrt calculation.

@BenMorel
Copy link
Member

BenMorel commented Dec 6, 2018

Released in 0.8.3 👍

@BenMorel
Copy link
Member

BenMorel commented Dec 6, 2018

@peter279k If you find a faster way, don't hesitate to open a pull request!

If you are interested, I would be happy if you could investigate:

@BenMorel
Copy link
Member

BenMorel commented Dec 7, 2018

Note: added support for BigDecimal::sqrt() in 41eeaed.

@peter279k peter279k deleted the issue_#18 branch March 27, 2019 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants