-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refinement for XIRR Fix #2469. The algorithm used for XIRR is known not to converge in some cases, some of which are because the value is legitimately unsolvable; for others, using a different guess might help. The algorithm uses continual guesses at a rate to hopefully converge on the solution. The code in Python package xirr (https://github.com/tarioch/xirr/) suggests a refinement when this rate falls below -1. Adopting this refinement solves the problem for the data in issue 2469 without any adverse effect on the existing tests. My thanks to @tarioch for that refinement. The data from 2469 is, of course, added to the test cases. The user also mentions that an initial guess equal to the actual result doesn't converge either. A test is also added to confirm that that case now works. The test cases are changed to run in the context of a spreadsheet rather than by direct calls to XIRR calculation routine. This revealed some data validation errors which are also cleaned up with this PR. This suggests that other financial tests might benefit from the same change; I will look into that. * More Unit Tests From https://github.com/RayDeCampo/java-xirr/blob/master/src/test/java/org/decampo/xirr/XirrTest.java https://github.com/tarioch/xirr/blob/master/tests/test_math.py Note that there are some cases where the PHP tests do not converge, but the non-PHP tests do. I have confirmed in each of those cases that Excel does not converge, so the PhpSpreadsheet results are good, at least for now. The discrepancies are noted in comments in the test member.
- Loading branch information
Showing
5 changed files
with
310 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AllSetupTeardown.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial; | ||
|
||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException; | ||
use PhpOffice\PhpSpreadsheet\Calculation\Functions; | ||
use PhpOffice\PhpSpreadsheet\Cell\DataType; | ||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class AllSetupTeardown extends TestCase | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private $compatibilityMode; | ||
|
||
/** | ||
* @var ?Spreadsheet | ||
*/ | ||
private $spreadsheet; | ||
|
||
/** | ||
* @var ?Worksheet | ||
*/ | ||
private $sheet; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->compatibilityMode = Functions::getCompatibilityMode(); | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
Functions::setCompatibilityMode($this->compatibilityMode); | ||
$this->sheet = null; | ||
if ($this->spreadsheet !== null) { | ||
$this->spreadsheet->disconnectWorksheets(); | ||
$this->spreadsheet = null; | ||
} | ||
} | ||
|
||
protected static function setOpenOffice(): void | ||
{ | ||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); | ||
} | ||
|
||
protected static function setGnumeric(): void | ||
{ | ||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); | ||
} | ||
|
||
/** | ||
* @param mixed $expectedResult | ||
*/ | ||
protected function mightHaveException($expectedResult): void | ||
{ | ||
if ($expectedResult === 'exception') { | ||
$this->expectException(CalcException::class); | ||
} | ||
} | ||
|
||
/** | ||
* @param mixed $value | ||
*/ | ||
protected function setCell(string $cell, $value): void | ||
{ | ||
if ($value !== null) { | ||
if (is_string($value) && is_numeric($value)) { | ||
$this->getSheet()->getCell($cell)->setValueExplicit($value, DataType::TYPE_STRING); | ||
} else { | ||
$this->getSheet()->getCell($cell)->setValue($value); | ||
} | ||
} | ||
} | ||
|
||
protected function getSpreadsheet(): Spreadsheet | ||
{ | ||
if ($this->spreadsheet !== null) { | ||
return $this->spreadsheet; | ||
} | ||
$this->spreadsheet = new Spreadsheet(); | ||
|
||
return $this->spreadsheet; | ||
} | ||
|
||
protected function getSheet(): Worksheet | ||
{ | ||
if ($this->sheet !== null) { | ||
return $this->sheet; | ||
} | ||
$this->sheet = $this->getSpreadsheet()->getActiveSheet(); | ||
|
||
return $this->sheet; | ||
} | ||
|
||
/** | ||
* Adjust result if it is close enough to expected by ratio | ||
* rather than offset. | ||
* | ||
* @param mixed $result | ||
* @param mixed $expectedResult | ||
*/ | ||
protected function adjustResult(&$result, $expectedResult): void | ||
{ | ||
if (is_numeric($result) && is_numeric($expectedResult)) { | ||
if ($expectedResult != 0) { | ||
$frac = $result / $expectedResult; | ||
if ($frac > 0.999999 && $frac < 1.000001) { | ||
$result = $expectedResult; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.