diff --git a/src/BigDecimal.php b/src/BigDecimal.php index 02d7cad..e78ae89 100644 --- a/src/BigDecimal.php +++ b/src/BigDecimal.php @@ -6,6 +6,7 @@ use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; +use Brick\Math\Exception\NegativeNumberException; use Brick\Math\Internal\Calculator; /** @@ -408,6 +409,51 @@ public function quotientAndRemainder($that) : array return [$quotient, $remainder]; } + /** + * Returns an approximation to the square root of this number, rounded down to the given number of decimals. + * + * @param int $scale + * + * @return BigDecimal + * + * @throws \InvalidArgumentException If the scale is negative. + * @throws NegativeNumberException If this number is negative. + */ + public function sqrt(int $scale) : BigDecimal + { + if ($scale < 0) { + throw new \InvalidArgumentException('Scale cannot be negative.'); + } + + if ($this->value === '0') { + return new BigDecimal('0', $scale); + } + + if ($this->value[0] === '-') { + throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); + } + + $value = $this->value; + $addDigits = 2 * $scale - $this->scale; + + if ($addDigits > 0) { + // add zeros + $value .= str_repeat('0', $addDigits); + } elseif ($addDigits < 0) { + // trim digits + if (-$addDigits >= strlen($this->value)) { + // requesting a scale too low, will always yield a zero result + return new BigDecimal('0', $scale); + } + + $value = substr($value, 0, $addDigits); + } + + $value = Calculator::get()->sqrt($value); + + return new BigDecimal($value, $scale); + } + /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the left. * diff --git a/tests/BigDecimalTest.php b/tests/BigDecimalTest.php index 13a7fc5..8445708 100644 --- a/tests/BigDecimalTest.php +++ b/tests/BigDecimalTest.php @@ -3,9 +3,10 @@ namespace Brick\Math\Tests; use Brick\Math\BigDecimal; -use Brick\Math\RoundingMode; use Brick\Math\Exception\DivisionByZeroException; +use Brick\Math\Exception\NegativeNumberException; use Brick\Math\Exception\RoundingNecessaryException; +use Brick\Math\RoundingMode; /** * Unit tests for class BigDecimal. @@ -1376,6 +1377,234 @@ public function testQuotientAndRemainderOfZeroThrowsException() BigDecimal::of(1.2)->quotientAndRemainder(0); } + /** + * @dataProvider providerSqrt + * + * @param string $number + * @param int $scale + * @param string $sqrt + */ + public function testSqrt(string $number, int $scale, string $sqrt) + { + $number = BigDecimal::of($number); + + $this->assertBigDecimalEquals($sqrt, $number->sqrt($scale)); + } + + /** + * @return array + */ + public function providerSqrt() + { + return [ + ['0', 0, '0'], + ['0', 1, '0.0'], + ['0', 2, '0.00'], + ['0.9', 0, '0'], + ['0.9', 1, '0.9'], + ['0.9', 2, '0.94'], + ['0.9', 20, '0.94868329805051379959'], + + ['1', 0, '1'], + ['1', 1, '1.0'], + ['1', 2, '1.00'], + ['1.01', 0, '1'], + ['1.01', 1, '1.0'], + ['1.01', 2, '1.00'], + ['1.01', 50, '1.00498756211208902702192649127595761869450234700263'], + + ['2', 0, '1'], + ['2', 1, '1.4'], + ['2', 2, '1.41'], + ['2', 3, '1.414'], + ['2.0', 10, '1.4142135623'], + ['2.00', 100, '1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727'], + ['2.01', 100, '1.4177446878757825202955618542708577926112284524295925478183838620667251915680282359142910339946198902'], + + ['3', 0, '1'], + ['3', 1, '1.7'], + ['3', 2, '1.73'], + ['3.0', 3, '1.732'], + ['3.00', 100, '1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485756'], + ['3.01', 100, '1.7349351572897472412324994276999816954904345949805056180301062018688462654791174593725963060697252989'], + + ['4', 0, '2'], + ['4.0', 1, '2.0'], + ['4.00', 2, '2.00'], + ['4.000', 50, '2.00000000000000000000000000000000000000000000000000'], + ['4.001', 50, '2.00024998437695281987761450010498155779765165614814'], + + ['8', 0, '2'], + ['8', 1, '2.8'], + ['8', 2, '2.82'], + ['8', 3, '2.828'], + ['8', 100, '2.8284271247461900976033774484193961571393437507538961463533594759814649569242140777007750686552831454'], + + ['9', 0, '3'], + ['9', 1, '3.0'], + ['9', 2, '3.00'], + ['9.0', 3, '3.000'], + ['9.00', 50, '3.00000000000000000000000000000000000000000000000000'], + ['9.000000000001', 100, '3.0000000000001666666666666620370370370372942386831275541552354823973654295585021450670206100119695201'], + + ['15', 0, '3'], + ['15', 1, '3.8'], + ['15', 2, '3.87'], + ['15', 3, '3.872'], + ['15', 100, '3.8729833462074168851792653997823996108329217052915908265875737661134830919369790335192873768586735179'], + + ['16', 0, '4'], + ['16', 1, '4.0'], + ['16.0', 2, '4.00'], + ['16.0', 50, '4.00000000000000000000000000000000000000000000000000'], + ['16.9', 100, '4.1109609582188931315985616077625340938354216811227818749147563086303727702310096877475225408930903837'], + + ['24.000000', 0, '4'], + ['24.000000', 1, '4.8'], + ['24.000000', 100, '4.8989794855663561963945681494117827839318949613133402568653851345019207549146300530797188662092804696'], + + ['25.0', 0, '5'], + ['25.0', 1, '5.0'], + ['25.0', 2, '5.00'], + ['25.0', 50, '5.00000000000000000000000000000000000000000000000000'], + + ['35.0', 0, '5'], + ['35.0', 1, '5.9'], + ['35.0', 2, '5.91'], + ['35.0', 3, '5.916'], + ['35.0', 4, '5.9160'], + ['35.0', 5, '5.91607'], + ['35.0', 100, '5.9160797830996160425673282915616170484155012307943403228797196691428224591056530367657525271831091780'], + ['35.000000000000001', 100, '5.9160797830996161270827537644132741956957234470198942745396537100863774127246283998019188486148209315'], + ['35.999999999999999999999999', 100, '5.9999999999999999999999999166666666666666666666666660879629629629629629629629549254115226337448559670'], + + ['36.00', 0, '6'], + ['36.00', 1, '6.0'], + ['36.00', 2, '6.00'], + ['36.00', 3, '6.000'], + ['36.00', 50, '6.00000000000000000000000000000000000000000000000000'], + + ['48.00', 0, '6'], + ['48.00', 2, '6.92'], + ['48.00', 10, '6.9282032302'], + ['48.00', 100, '6.9282032302755091741097853660234894677712210152415225122232279178077320676352001483245847470289943027'], + ['48.99', 100, '6.9992856778388464346356995151906110076016504604210370025102717611026824990288822856842902895079113686'], + + ['49.000', 0, '7'], + ['49.000', 1, '7.0'], + ['49.000', 2, '7.00'], + ['49.000', 50, '7.00000000000000000000000000000000000000000000000000'], + + ['63.000', 0, '7'], + ['63.000', 1, '7.9'], + ['63.000', 50, '7.93725393319377177150484726091778127713077754924735'], + ['63.000', 100, '7.9372539331937717715048472609177812771307775492473505411050033776032064696908508832811786594236308318'], + ['63.999', 100, '7.9999374997558574676327405322784897796491608172719005229581557200716046333750586163480165729931946120'], + + ['64.000', 0, '8'], + ['64.000', 1, '8.0'], + ['64.000', 2, '8.00'], + ['64.000', 3, '8.000'], + ['64.000', 5, '8.00000'], + ['64.000', 10, '8.0000000000'], + ['64.001', 100, '8.0000624997558612823300065647321162325407871131077227756517693705917932138407362275702583154308502098'], + + ['80.0000', 0, '8'], + ['80.0000', 1, '8.9'], + ['80.0000', 2, '8.94'], + ['80.0000', 3, '8.944'], + ['80.0000', 100, '8.9442719099991587856366946749251049417624734384461028970835889816420837025512195976576576335151290998'], + ['80.9999', 100, '8.9999944444427297657453970731875168384468609868424736666730189165386046474591090720552569708711327582'], + + ['81.0000', 0, '9'], + ['81.0000', 1, '9.0'], + ['81.0000', 2, '9.00'], + ['81.0000', 3, '9.000'], + ['81.0000', 100, '9.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'], + ['81.0001', 100, '9.0000055555538408789733941603538253684017661110877201067416238610513773808272313229667905649560045446'], + + ['99.0000', 0, '9'], + ['99.0000', 1, '9.9'], + ['99.0000', 2, '9.94'], + ['99.0000', 3, '9.949'], + ['99.0000', 100, '9.9498743710661995473447982100120600517812656367680607911760464383494539278271315401265301973848719527'], + ['99.9999', 100, '9.9999949999987499993749996093747265622949217138670565794807433154677543830170797681772155388691566433'], + + ['100.00000', 0, '10'], + ['100.00000', 1, '10.0'], + ['100.00000', 2, '10.00'], + ['100.00000', 3, '10.000'], + ['100.00000', 100, '10.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'], + ['100.00001', 100, '10.0000004999999875000006249999609375027343747949218911132799407960075378325233467481612458396019939178'], + + ['536137214136734800142146901786039940282473271927911507640625', 100, '732213912826528310663262741625.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'], + ['536137214136734800142146901787504368108126328549238033123875', 100, '732213912826528310663262741625.9999999999999999999999999999993171394434860226777473099041602996062918768042176806905944729779944325'], + ['536137214136734800142146901787504368108126328549238033123876', 100, '732213912826528310663262741626.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'], + ['5651495859544574019979802175954184725583245698990648064256.0000000001', 100, '75176431543034642899535752016.0000000000000000000000000000000000000006651020668808623891656648072197795077909627885735661691784990'], + ['5651495859544574019979802176104537588669314984789719568288.9999999999', 100, '75176431543034642899535752016.9999999999999999999999999999999999999993348979331191376108343351927890677073964211143577833889761307'], + ['5651495859544574019979802176104537588669314984789719568289.00000000001', 100, '75176431543034642899535752017.0000000000000000000000000000000000000000665102066880862389165664807210932292603578885642216611023869'], + + ['17', 60, '4.123105625617660549821409855974077025147199225373620434398633'], + ['17', 61, '4.1231056256176605498214098559740770251471992253736204343986335'], + ['17', 62, '4.12310562561766054982140985597407702514719922537362043439863357'], + ['17', 63, '4.123105625617660549821409855974077025147199225373620434398633573'], + ['17', 64, '4.1231056256176605498214098559740770251471992253736204343986335730'], + ['17', 65, '4.12310562561766054982140985597407702514719922537362043439863357309'], + ['17', 66, '4.123105625617660549821409855974077025147199225373620434398633573094'], + ['17', 67, '4.1231056256176605498214098559740770251471992253736204343986335730949'], + ['17', 68, '4.12310562561766054982140985597407702514719922537362043439863357309495'], + ['17', 69, '4.123105625617660549821409855974077025147199225373620434398633573094954'], + ['17', 70, '4.1231056256176605498214098559740770251471992253736204343986335730949543'], + + ['0.0019', 0, '0'], + ['0.0019', 1, '0.0'], + ['0.0019', 2, '0.04'], + ['0.0019', 3, '0.043'], + ['0.0019', 10, '0.0435889894'], + ['0.0019', 70, '0.0435889894354067355223698198385961565913700392523244493689034413815955'], + + ['0.00000000015727468406479', 0, '0'], + ['0.00000000015727468406479', 1, '0.0'], + ['0.00000000015727468406479', 2, '0.00'], + ['0.00000000015727468406479', 3, '0.000'], + ['0.00000000015727468406479', 4, '0.0000'], + ['0.00000000015727468406479', 5, '0.00001'], + ['0.00000000015727468406479', 6, '0.000012'], + ['0.00000000015727468406479', 7, '0.0000125'], + ['0.00000000015727468406479', 8, '0.00001254'], + ['0.00000000015727468406479', 9, '0.000012540'], + ['0.00000000015727468406479', 10, '0.0000125409'], + ['0.00000000015727468406479', 100, '0.0000125409203834802332262270521125445995500262491027973910117525063503841909945796984522050136239469'], + + ['0.04', 0, '0'], + ['0.04', 1, '0.2'], + ['0.04', 2, '0.20'], + ['0.04', 10, '0.2000000000'], + + ['0.0004', 4, '0.0200'], + ['0.00000000000000000000000000000004', 8, '0.00000000'], + ['0.00000000000000000000000000000004', 16, '0.0000000000000002'], + ['0.00000000000000000000000000000004', 32, '0.00000000000000020000000000000000'], + ['0.000000000000000000000000000000004', 32, '0.00000000000000006324555320336758'], + + ['111111111111111111111.11111111111111', 90, '10540925533.894597773329645148109061726360556128277733889543457102096672435043305908711407747018689086'] + ]; + } + + public function testSqrtOfNegativeNumber() + { + $number = BigDecimal::of(-1); + $this->expectException(NegativeNumberException::class); + $number->sqrt(0); + } + + public function testSqrtWithNegativeScale() + { + $number = BigDecimal::of(1); + $this->expectException(\InvalidArgumentException::class); + $number->sqrt(-1); + } + /** * @dataProvider providerPower *