From 5ad5f787abe6a20e0ca0d76235c56eaacc532a83 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Sun, 21 Mar 2021 21:40:49 +0100 Subject: [PATCH] Financial functions next stage of refactoring (#1943) * First steps splitting out the Amortization and Deprecation Excel functions from Financials * Verify which methods allow negative values for arguments * Additional unit tests for SLN() and SYD() * Additional unit tests for DDB() * Additional unit tests for DB() * Verify Amortization cases where salvage is greater than cost * More unit tests for Amortization * Resolve broken test in AMORLINC() and extract amortizationCoefficient calculation * verify amortizationCoefficient calculation * Extract YIELDDISC() and YIELDMAT() to Financial\Securities * Additional validation for Securities Yield functions --- .../Calculation/Calculation.php | 18 +- src/PhpSpreadsheet/Calculation/Financial.php | 337 ++++-------------- .../Calculation/Financial/Amortization.php | 163 +++++++++ .../Calculation/Financial/Depreciation.php | 287 +++++++++++++++ .../Financial/Securities/BaseValidations.php | 145 ++++++++ .../Financial/Securities/Constants.php | 10 + .../{Securities.php => Securities/Price.php} | 134 +------ .../Financial/Securities/Yields.php | 136 +++++++ .../data/Calculation/Financial/AMORDEGRC.php | 28 ++ tests/data/Calculation/Financial/AMORLINC.php | 16 +- tests/data/Calculation/Financial/DB.php | 121 ++++++- tests/data/Calculation/Financial/DDB.php | 113 ++++++ tests/data/Calculation/Financial/SLN.php | 34 +- tests/data/Calculation/Financial/SYD.php | 52 +++ .../data/Calculation/Financial/YIELDDISC.php | 24 ++ tests/data/Calculation/Financial/YIELDMAT.php | 28 ++ 16 files changed, 1225 insertions(+), 421 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/Financial/Amortization.php create mode 100644 src/PhpSpreadsheet/Calculation/Financial/Depreciation.php create mode 100644 src/PhpSpreadsheet/Calculation/Financial/Securities/BaseValidations.php create mode 100644 src/PhpSpreadsheet/Calculation/Financial/Securities/Constants.php rename src/PhpSpreadsheet/Calculation/Financial/{Securities.php => Securities/Price.php} (73%) create mode 100644 src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index dbea0850..9317699b 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -273,12 +273,12 @@ class Calculation ], 'AMORDEGRC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'AMORDEGRC'], + 'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'], 'argumentCount' => '6,7', ], 'AMORLINC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'AMORLINC'], + 'functionCall' => [Financial\Amortization::class, 'AMORLINC'], 'argumentCount' => '6,7', ], 'AND' => [ @@ -1983,17 +1983,17 @@ class Calculation ], 'PRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial\Securities::class, 'price'], + 'functionCall' => [Financial\Securities\Price::class, 'price'], 'argumentCount' => '6,7', ], 'PRICEDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial\Securities::class, 'discounted'], + 'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'], 'argumentCount' => '4,5', ], 'PRICEMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial\Securities::class, 'maturity'], + 'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'], 'argumentCount' => '5,6', ], 'PROB' => [ @@ -2225,7 +2225,7 @@ class Calculation ], 'SLN' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'SLN'], + 'functionCall' => [Financial\Depreciation::class, 'SLN'], 'argumentCount' => '3', ], 'SLOPE' => [ @@ -2356,7 +2356,7 @@ class Calculation ], 'SYD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'SYD'], + 'functionCall' => [Financial\Depreciation::class, 'SYD'], 'argumentCount' => '4', ], 'T' => [ @@ -2641,12 +2641,12 @@ class Calculation ], 'YIELDDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'YIELDDISC'], + 'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'], 'argumentCount' => '4,5', ], 'YIELDMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'YIELDMAT'], + 'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'], 'argumentCount' => '5,6', ], 'ZTEST' => [ diff --git a/src/PhpSpreadsheet/Calculation/Financial.php b/src/PhpSpreadsheet/Calculation/Financial.php index 9735e9f4..2b54e1cd 100644 --- a/src/PhpSpreadsheet/Calculation/Financial.php +++ b/src/PhpSpreadsheet/Calculation/Financial.php @@ -2,8 +2,13 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Amortization; use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Depreciation; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar; use PhpOffice\PhpSpreadsheet\Calculation\Financial\InterestRate; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill; class Financial { @@ -147,6 +152,10 @@ class Financial * Excel Function: * AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * + * @Deprecated 1.18.0 + * + * @see Use the AMORDEGRC() method in the Financial\Amortization class instead + * * @param float $cost The cost of the asset * @param mixed $purchased Date of the purchase of the asset * @param mixed $firstPeriod Date of the end of the first period @@ -164,57 +173,7 @@ class Financial */ public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0) { - $cost = Functions::flattenSingleValue($cost); - $purchased = Functions::flattenSingleValue($purchased); - $firstPeriod = Functions::flattenSingleValue($firstPeriod); - $salvage = Functions::flattenSingleValue($salvage); - $period = floor(Functions::flattenSingleValue($period)); - $rate = Functions::flattenSingleValue($rate); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis); - if (is_string($yearFrac)) { - return $yearFrac; - } - - // The depreciation coefficients are: - // Life of assets (1/rate) Depreciation coefficient - // Less than 3 years 1 - // Between 3 and 4 years 1.5 - // Between 5 and 6 years 2 - // More than 6 years 2.5 - $fUsePer = 1.0 / $rate; - if ($fUsePer < 3.0) { - $amortiseCoeff = 1.0; - } elseif ($fUsePer < 5.0) { - $amortiseCoeff = 1.5; - } elseif ($fUsePer <= 6.0) { - $amortiseCoeff = 2.0; - } else { - $amortiseCoeff = 2.5; - } - - $rate *= $amortiseCoeff; - $fNRate = round($yearFrac * $rate * $cost, 0); - $cost -= $fNRate; - $fRest = $cost - $salvage; - - for ($n = 0; $n < $period; ++$n) { - $fNRate = round($rate * $cost, 0); - $fRest -= $fNRate; - - if ($fRest < 0.0) { - switch ($period - $n) { - case 0: - case 1: - return round($cost * 0.5, 0); - default: - return 0.0; - } - } - $cost -= $fNRate; - } - - return $fNRate; + return Amortization::AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis); } /** @@ -227,6 +186,10 @@ class Financial * Excel Function: * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * + * @Deprecated 1.18.0 + * + * @see Use the AMORLINC() method in the Financial\Amortization class instead + * * @param float $cost The cost of the asset * @param mixed $purchased Date of the purchase of the asset * @param mixed $firstPeriod Date of the end of the first period @@ -244,39 +207,7 @@ class Financial */ public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0) { - $cost = Functions::flattenSingleValue($cost); - $purchased = Functions::flattenSingleValue($purchased); - $firstPeriod = Functions::flattenSingleValue($firstPeriod); - $salvage = Functions::flattenSingleValue($salvage); - $period = Functions::flattenSingleValue($period); - $rate = Functions::flattenSingleValue($rate); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - $fOneRate = $cost * $rate; - $fCostDelta = $cost - $salvage; - // Note, quirky variation for leap years on the YEARFRAC for this function - $purchasedYear = DateTime::YEAR($purchased); - $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis); - if (is_string($yearFrac)) { - return $yearFrac; - } - - if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($purchasedYear))) { - $yearFrac *= 365 / 366; - } - - $f0Rate = $yearFrac * $rate * $cost; - $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); - - if ($period == 0) { - return $f0Rate; - } elseif ($period <= $nNumOfFullPeriods) { - return $fOneRate; - } elseif ($period == ($nNumOfFullPeriods + 1)) { - return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; - } - - return 0.0; + return Amortization::AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis); } /** @@ -613,6 +544,10 @@ class Financial * Excel Function: * DB(cost,salvage,life,period[,month]) * + * @Deprecated 1.18.0 + * + * @see Use the DB() method in the Financial\Depreciation class instead + * * @param float $cost Initial cost of the asset * @param float $salvage Value at the end of the depreciation. * (Sometimes called the salvage value of the asset) @@ -627,46 +562,7 @@ class Financial */ public static function DB($cost, $salvage, $life, $period, $month = 12) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - $month = Functions::flattenSingleValue($month); - - // Validate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) { - $cost = (float) $cost; - $salvage = (float) $salvage; - $life = (int) $life; - $period = (int) $period; - $month = (int) $month; - if ($cost == 0) { - return 0.0; - } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) { - return Functions::NAN(); - } - // Set Fixed Depreciation Rate - $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); - $fixedDepreciationRate = round($fixedDepreciationRate, 3); - - // Loop through each period calculating the depreciation - $previousDepreciation = 0; - $depreciation = 0; - for ($per = 1; $per <= $period; ++$per) { - if ($per == 1) { - $depreciation = $cost * $fixedDepreciationRate * $month / 12; - } elseif ($per == ($life + 1)) { - $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; - } else { - $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; - } - $previousDepreciation += $depreciation; - } - - return $depreciation; - } - - return Functions::VALUE(); + return Depreciation::DB($cost, $salvage, $life, $period, $month); } /** @@ -678,6 +574,10 @@ class Financial * Excel Function: * DDB(cost,salvage,life,period[,factor]) * + * @Deprecated 1.18.0 + * + * @see Use the DDB() method in the Financial\Depreciation class instead + * * @param float $cost Initial cost of the asset * @param float $salvage Value at the end of the depreciation. * (Sometimes called the salvage value of the asset) @@ -693,38 +593,7 @@ class Financial */ public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - $factor = Functions::flattenSingleValue($factor); - - // Validate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) { - $cost = (float) $cost; - $salvage = (float) $salvage; - $life = (int) $life; - $period = (int) $period; - $factor = (float) $factor; - if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) { - return Functions::NAN(); - } - // Set Fixed Depreciation Rate - $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); - $fixedDepreciationRate = round($fixedDepreciationRate, 3); - - // Loop through each period calculating the depreciation - $previousDepreciation = 0; - $depreciation = 0; - for ($per = 1; $per <= $period; ++$per) { - $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation)); - $previousDepreciation += $depreciation; - } - - return $depreciation; - } - - return Functions::VALUE(); + return Depreciation::DDB($cost, $salvage, $life, $period, $factor); } /** @@ -800,7 +669,7 @@ class Financial */ public static function DOLLARDE($fractional_dollar = null, $fraction = 0) { - return Financial\Dollar::decimal($fractional_dollar, $fraction); + return Dollar::decimal($fractional_dollar, $fraction); } /** @@ -824,7 +693,7 @@ class Financial */ public static function DOLLARFR($decimal_dollar = null, $fraction = 0) { - return Financial\Dollar::fractional($decimal_dollar, $fraction); + return Dollar::fractional($decimal_dollar, $fraction); } /** @@ -1368,7 +1237,7 @@ class Financial * * @Deprecated 1.18.0 * - * @see Use the price() method in the Financial\Securities class instead + * @see Use the price() method in the Financial\Securities\Price class instead * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue date when the security @@ -1393,7 +1262,7 @@ class Financial */ public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0) { - return Financial\Securities::price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis); + return Securities\Price::price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis); } /** @@ -1403,12 +1272,13 @@ class Financial * * @Deprecated 1.18.0 * - * @see Use the discounted() method in the Financial\Securities class instead + * @see Use the priceDiscounted() method in the Financial\Securities\Price class instead * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $discount The security's discount rate * @param int $redemption The security's redemption value per $100 face value * @param int $basis The type of day count to use. @@ -1422,7 +1292,7 @@ class Financial */ public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0) { - return Financial\Securities::discounted($settlement, $maturity, $discount, $redemption, $basis); + return Securities\Price::priceDiscounted($settlement, $maturity, $discount, $redemption, $basis); } /** @@ -1432,12 +1302,13 @@ class Financial * * @Deprecated 1.18.0 * - * @see Use the maturity() method in the Financial\Securities class instead + * @see Use the priceAtMaturity() method in the Financial\Securities\Price class instead * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param int $rate The security's interest rate at date of issue * @param int $yield The security's annual yield @@ -1452,7 +1323,7 @@ class Financial */ public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0) { - return Financial\Securities::maturity($settlement, $maturity, $issue, $rate, $yield, $basis); + return Securities\Price::priceAtMaturity($settlement, $maturity, $issue, $rate, $yield, $basis); } /** @@ -1640,6 +1511,10 @@ class Financial * * Returns the straight-line depreciation of an asset for one period * + * @Deprecated 1.18.0 + * + * @see Use the SLN() method in the Financial\Depreciation class instead + * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated @@ -1648,20 +1523,7 @@ class Financial */ public static function SLN($cost, $salvage, $life) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - - // Calculate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) { - if ($life < 0) { - return Functions::NAN(); - } - - return ($cost - $salvage) / $life; - } - - return Functions::VALUE(); + return Depreciation::SLN($cost, $salvage, $life); } /** @@ -1669,6 +1531,10 @@ class Financial * * Returns the sum-of-years' digits depreciation of an asset for a specified period. * + * @Deprecated 1.18.0 + * + * @see Use the SYD() method in the Financial\Depreciation class instead + * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated @@ -1678,21 +1544,7 @@ class Financial */ public static function SYD($cost, $salvage, $life, $period) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - - // Calculate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) { - if (($life < 1) || ($period > $life)) { - return Functions::NAN(); - } - - return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); - } - - return Functions::VALUE(); + return Depreciation::SYD($cost, $salvage, $life, $period); } /** @@ -1714,7 +1566,7 @@ class Financial */ public static function TBILLEQ($settlement, $maturity, $discount) { - return Financial\TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount); + return TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount); } /** @@ -1737,7 +1589,7 @@ class Financial */ public static function TBILLPRICE($settlement, $maturity, $discount) { - return Financial\TreasuryBill::price($settlement, $maturity, $discount); + return TreasuryBill::price($settlement, $maturity, $discount); } /** @@ -1760,7 +1612,7 @@ class Financial */ public static function TBILLYIELD($settlement, $maturity, $price) { - return Financial\TreasuryBill::yield($settlement, $maturity, $price); + return TreasuryBill::yield($settlement, $maturity, $price); } private static function bothNegAndPos($neg, $pos) @@ -1977,10 +1829,13 @@ class Financial * * Returns the annual yield of a security that pays interest at maturity. * + * @see Use the yieldDiscounted() method in the Financial\Securities\Yields class instead + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $price The security's price per $100 face value * @param int $redemption The security's redemption value per $100 face value * @param int $basis The type of day count to use. @@ -1994,32 +1849,7 @@ class Financial */ public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - $redemption = Functions::flattenSingleValue($redemption); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($price) && is_numeric($redemption)) { - if (($price <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Yields::yieldDiscounted($settlement, $maturity, $price, $redemption, $basis); } /** @@ -2027,10 +1857,15 @@ class Financial * * Returns the annual yield of a security that pays interest at maturity. * + * @Deprecated 1.18.0 + * + * @see Use the yieldAtMaturity() method in the Financial\Securities\Yields class instead + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param int $rate The security's interest rate at date of issue * @param int $price The security's price per $100 face value @@ -2045,46 +1880,6 @@ class Financial */ public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $issue = Functions::flattenSingleValue($issue); - $rate = Functions::flattenSingleValue($rate); - $price = Functions::flattenSingleValue($price); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($rate) && is_numeric($price)) { - if (($rate <= 0) || ($price <= 0)) { - return Functions::NAN(); - } - $daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - $daysBetweenIssueAndSettlement *= $daysPerYear; - $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis); - if (!is_numeric($daysBetweenIssueAndMaturity)) { - // return date error - return $daysBetweenIssueAndMaturity; - } - $daysBetweenIssueAndMaturity *= $daysPerYear; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * - ($daysPerYear / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Yields::yieldAtMaturity($settlement, $maturity, $issue, $rate, $price, $basis); } } diff --git a/src/PhpSpreadsheet/Calculation/Financial/Amortization.php b/src/PhpSpreadsheet/Calculation/Financial/Amortization.php new file mode 100644 index 00000000..76be7e12 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Amortization.php @@ -0,0 +1,163 @@ +getMessage(); + } + + if ($cost === 0.0) { + return 0.0; + } + + // Set Fixed Depreciation Rate + $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); + $fixedDepreciationRate = round($fixedDepreciationRate, 3); + + // Loop through each period calculating the depreciation + // TODO Handle period value between 0 and 1 (e.g. 0.5) + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + if ($per == 1) { + $depreciation = $cost * $fixedDepreciationRate * $month / 12; + } elseif ($per == ($life + 1)) { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; + } else { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; + } + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * DDB. + * + * Returns the depreciation of an asset for a specified period using the + * double-declining balance method or some other method you specify. + * + * Excel Function: + * DDB(cost,salvage,life,period[,factor]) + * + * @param float $cost Initial cost of the asset + * @param float $salvage Value at the end of the depreciation. + * (Sometimes called the salvage value of the asset) + * @param int $life Number of periods over which the asset is depreciated. + * (Sometimes called the useful life of the asset) + * @param int $period The period for which you want to calculate the + * depreciation. Period must use the same units as life. + * @param float $factor The rate at which the balance declines. + * If factor is omitted, it is assumed to be 2 (the + * double-declining balance method). + * + * @return float|string + */ + public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + $factor = Functions::flattenSingleValue($factor); + + try { + $cost = self::validateCost($cost); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + $factor = self::validateFactor($factor); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return Functions::NAN(); + } + + // Loop through each period calculating the depreciation + // TODO Handling for fractional $period values + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation)); + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * SLN. + * + * Returns the straight-line depreciation of an asset for one period + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * + * @return float|string Result, or a string containing an error + */ + public static function SLN($cost, $salvage, $life) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage, true); + $life = self::validateLife($life, true); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($life === 0.0) { + return Functions::DIV0(); + } + + return ($cost - $salvage) / $life; + } + + /** + * SYD. + * + * Returns the sum-of-years' digits depreciation of an asset for a specified period. + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * @param mixed $period Period + * + * @return float|string Result, or a string containing an error + */ + public static function SYD($cost, $salvage, $life, $period) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return Functions::NAN(); + } + + $syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); + + return $syd; + } + + private static function validateCost($cost, bool $negativeValueAllowed = false): float + { + if (!is_numeric($cost)) { + throw new Exception(Functions::VALUE()); + } + + $cost = (float) $cost; + if ($cost < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $cost; + } + + private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float + { + if (!is_numeric($salvage)) { + throw new Exception(Functions::VALUE()); + } + + $salvage = (float) $salvage; + if ($salvage < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $salvage; + } + + private static function validateLife($life, bool $negativeValueAllowed = false): float + { + if (!is_numeric($life)) { + throw new Exception(Functions::VALUE()); + } + + $life = (float) $life; + if ($life < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $life; + } + + private static function validatePeriod($period, bool $negativeValueAllowed = false): float + { + if (!is_numeric($period)) { + throw new Exception(Functions::VALUE()); + } + + $period = (float) $period; + if ($period <= 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $period; + } + + private static function validateMonth($month): int + { + if (!is_numeric($month)) { + throw new Exception(Functions::VALUE()); + } + + $month = (int) $month; + if ($month < 1) { + throw new Exception(Functions::NAN()); + } + + return $month; + } + + private static function validateFactor($factor): float + { + if (!is_numeric($factor)) { + throw new Exception(Functions::VALUE()); + } + + $factor = (float) $factor; + if ($factor <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $factor; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/BaseValidations.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/BaseValidations.php new file mode 100644 index 00000000..88cb8660 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/BaseValidations.php @@ -0,0 +1,145 @@ += $maturity) { + throw new Exception(Functions::NAN()); + } + } + + protected static function validateRate($rate): float + { + if (!is_numeric($rate)) { + throw new Exception(Functions::VALUE()); + } + + $rate = (float) $rate; + if ($rate < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $rate; + } + + protected static function validatePrice($price): float + { + if (!is_numeric($price)) { + throw new Exception(Functions::VALUE()); + } + + $price = (float) $price; + if ($price < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $price; + } + + protected static function validateYield($yield): float + { + if (!is_numeric($yield)) { + throw new Exception(Functions::VALUE()); + } + + $yield = (float) $yield; + if ($yield < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $yield; + } + + protected static function validateRedemption($redemption): float + { + if (!is_numeric($redemption)) { + throw new Exception(Functions::VALUE()); + } + + $redemption = (float) $redemption; + if ($redemption <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $redemption; + } + + protected static function validateDiscount($discount): float + { + if (!is_numeric($discount)) { + throw new Exception(Functions::VALUE()); + } + + $discount = (float) $discount; + if ($discount <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $discount; + } + + protected static function validateFrequency($frequency): int + { + if (!is_numeric($frequency)) { + throw new Exception(Functions::VALUE()); + } + + $frequency = (int) $frequency; + if ( + ($frequency !== SecuritiesConstants::FREQUENCY_ANNUAL) && + ($frequency !== SecuritiesConstants::FREQUENCY_SEMI_ANNUAL) && + ($frequency !== SecuritiesConstants::FREQUENCY_QUARTERLY) + ) { + throw new Exception(Functions::NAN()); + } + + return $frequency; + } + + protected static function validateBasis($basis): int + { + if (!is_numeric($basis)) { + throw new Exception(Functions::VALUE()); + } + + $basis = (int) $basis; + if (($basis < 0) || ($basis > 4)) { + throw new Exception(Functions::NAN()); + } + + return $basis; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/Constants.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/Constants.php new file mode 100644 index 00000000..ba9d2389 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/Constants.php @@ -0,0 +1,10 @@ += $maturity) { - throw new Exception(Functions::NAN()); - } - } - - private static function validateRate($rate): float - { - if (!is_numeric($rate)) { - throw new Exception(Functions::VALUE()); - } - - $rate = (float) $rate; - if ($rate < 0.0) { - throw new Exception(Functions::NAN()); - } - - return $rate; - } - - private static function validateYield($yield): float - { - if (!is_numeric($yield)) { - throw new Exception(Functions::VALUE()); - } - - $yield = (float) $yield; - if ($yield < 0.0) { - throw new Exception(Functions::NAN()); - } - - return $yield; - } - - private static function validateRedemption($redemption): float - { - if (!is_numeric($redemption)) { - throw new Exception(Functions::VALUE()); - } - - $redemption = (float) $redemption; - if ($redemption <= 0.0) { - throw new Exception(Functions::NAN()); - } - - return $redemption; - } - - private static function validateDiscount($discount): float - { - if (!is_numeric($discount)) { - throw new Exception(Functions::VALUE()); - } - - $discount = (float) $discount; - if ($discount <= 0.0) { - throw new Exception(Functions::NAN()); - } - - return $discount; - } - - private static function validateFrequency($frequency): int - { - if (!is_numeric($frequency)) { - throw new Exception(Functions::VALUE()); - } - - $frequency = (int) $frequency; - if ( - ($frequency !== self::FREQUENCY_ANNUAL) && - ($frequency !== self::FREQUENCY_SEMI_ANNUAL) && - ($frequency !== self::FREQUENCY_QUARTERLY) - ) { - throw new Exception(Functions::NAN()); - } - - return $frequency; - } - - private static function validateBasis($basis): int - { - if (!is_numeric($basis)) { - throw new Exception(Functions::VALUE()); - } - - $basis = (int) $basis; - if (($basis < 0) || ($basis > 4)) { - throw new Exception(Functions::NAN()); - } - - return $basis; - } } diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php new file mode 100644 index 00000000..0918d637 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php @@ -0,0 +1,136 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); + } + + /** + * YIELDMAT. + * + * Returns the annual yield of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param int $rate The security's interest rate at date of issue + * @param int $price The security's price per $100 face value + * @param int $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function yieldAtMaturity($settlement, $maturity, $issue, $rate, $price, $basis = 0) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $price = Functions::flattenSingleValue($price); + $basis = Functions::flattenSingleValue($basis); + + try { + $settlement = self::validateSettlementDate($settlement); + $maturity = self::validateMaturityDate($maturity); + self::validateSecurityPeriod($settlement, $maturity); + $issue = self::validateIssueDate($issue); + $rate = self::validateRate($rate); + $price = self::validatePrice($price); + $basis = self::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * + ($daysPerYear / $daysBetweenSettlementAndMaturity); + } +} diff --git a/tests/data/Calculation/Financial/AMORDEGRC.php b/tests/data/Calculation/Financial/AMORDEGRC.php index 59549e78..f4007033 100644 --- a/tests/data/Calculation/Financial/AMORDEGRC.php +++ b/tests/data/Calculation/Financial/AMORDEGRC.php @@ -7,6 +7,30 @@ return [ 776, 2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 1, ], + [ + 820, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.2, 1, + ], + [ + 492, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.2, 1, + ], + [ + 886, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.22, 1, + ], + [ + 949, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.24, 1, + ], + [ + 494, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 1, + ], + [ + 902, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.3, 1, + ], [ 42, 150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 4, @@ -27,4 +51,8 @@ return [ '#VALUE!', 550, 'notADate', '2020-12-25', 20, 1, 0.2, 4, ], + [ + '#VALUE!', + 550, '2011-01-01', 'notADate', 20, 1, 0.2, 4, + ], ]; diff --git a/tests/data/Calculation/Financial/AMORLINC.php b/tests/data/Calculation/Financial/AMORLINC.php index 46f19332..34485c8a 100644 --- a/tests/data/Calculation/Financial/AMORLINC.php +++ b/tests/data/Calculation/Financial/AMORLINC.php @@ -5,26 +5,30 @@ return [ [ 360, - 2400, '2008-08-19', '2008-12-31', 300, 1, 0.14999999999999999, 1, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 1, + ], + [ + 576, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 1, ], [ 30, - 150, '2011-01-01', '2011-09-30', 20, 1, 0.20000000000000001, 4, + 150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 4, ], [ 22.41666667, - 150, '2011-01-01', '2011-09-30', 20, 0, 0.20000000000000001, 4, + 150, '2011-01-01', '2011-09-30', 20, 0, 0.2, 4, ], [ 17.58333333, - 150, '2011-01-01', '2011-09-30', 20, 4, 0.20000000000000001, 4, + 150, '2011-01-01', '2011-09-30', 20, 4, 0.2, 4, ], [ 0.0, - 150, '2011-01-01', '2011-09-30', 20, 5, 0.20000000000000001, 4, + 150, '2011-01-01', '2011-09-30', 20, 5, 0.2, 4, ], [ '#VALUE!', - 150, 'notADate', '2011-09-30', 20, 1, 0.20000000000000001, 4, + 150, 'notADate', '2011-09-30', 20, 1, 0.2, 4, ], ]; diff --git a/tests/data/Calculation/Financial/DB.php b/tests/data/Calculation/Financial/DB.php index 7f8fc6fa..89bd22f2 100644 --- a/tests/data/Calculation/Financial/DB.php +++ b/tests/data/Calculation/Financial/DB.php @@ -115,6 +115,47 @@ return [ 6, 6, ], + [ + 4651.199, + 12000, + 2000, + 3.5, + 2, + 1, + ], + [ + 3521.399, + 12000, + 2000, + 5, + 2.5, + 1, + ], + [ + 3521.399, + 12000, + 2000, + 5, + 2.5, + 1.2, + ], + // Period value between 0 and 1 not yet handled in code + // [ + // 301.0, + // 12000, + // 2000, + // 5, + // 0.5, + // 1, + // ], + [ + -554.116, + 12000, + 15000, + 5, + 2, + 1, + ], [ '#NUM!', -1000, @@ -125,10 +166,82 @@ return [ ], [ '#VALUE!', - 'ABC', - 100, + 'Invalid', + 1000, 5, - 6, - 6, + 2, + 1, + ], + [ + '#VALUE!', + 12000, + 'Invalid', + 5, + 2, + 1, + ], + [ + '#VALUE!', + 12000, + 1000, + 'Invalid', + 2, + 1, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 'Invalid', + 1, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 2, + 'Invalid', + ], + [ + '#NUM!', + -12000, + 1000, + 5, + 2, + 1, + ], + [ + '#NUM!', + 12000, + -1000, + 5, + 2, + 1, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 0, + 1, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + -2, + 1, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 2, + 0, ], ]; diff --git a/tests/data/Calculation/Financial/DDB.php b/tests/data/Calculation/Financial/DDB.php index 879224ca..67962ea2 100644 --- a/tests/data/Calculation/Financial/DDB.php +++ b/tests/data/Calculation/Financial/DDB.php @@ -98,6 +98,47 @@ return [ 5, 5, ], + [ + 972.0, + 12000, + 1000, + 5, + 3, + 0.5, + ], + [ + 1259.4752186588921, + 12000, + 1000, + 3.5, + 3, + 0.5, + ], + [ + 1080.00, + 12000, + 1000, + 5, + 2, + 0.5, + ], + [ + 0.0, + 12000, + 15000, + 5, + 2, + 0.5, + ], + // Code does not yet handle fractional period values for DDB, only integer + // [ + // 1024.58, + // 12000, + // 1000, + // 5, + // 2.5, + // 0.5, + // ], [ '#NUM!', -2400, @@ -112,4 +153,76 @@ return [ 36500, 1, ], + [ + '#VALUE!', + 12000, + 'INVALID', + 5, + 3, + 0.5, + ], + [ + '#VALUE!', + 12000, + 1000, + 'INVALID', + 3, + 0.5, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 'INVALID', + 0.5, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 3, + 'INVALID', + ], + [ + '#NUM!', + 12000, + -1000, + 5, + 3, + 0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + -3, + 0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 3, + -0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 0, + 0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 2, + 3, + 0.5, + ], ]; diff --git a/tests/data/Calculation/Financial/SLN.php b/tests/data/Calculation/Financial/SLN.php index 5dcf9eda..f63120c1 100644 --- a/tests/data/Calculation/Financial/SLN.php +++ b/tests/data/Calculation/Financial/SLN.php @@ -30,11 +30,39 @@ return [ [45000, 7500, 10], ], [ - '#NUM!', - [10000, 1000, -1], + -10500, + [12000, 1500, -1], + ], + [ + 21000, + [12000, 1500, 0.5], + ], + [ + 3250, + [12000, -1000, 4], + ], + [ + -250, + [0, 1000, 4], + ], + [ + -600, + [12000, 15000, 5], + ], + [ + '#DIV/0!', + [12000, 1500, 0], ], [ '#VALUE!', - ['INVALID', 1000, -1], + ['INVALID', 1000, 1], + ], + [ + '#VALUE!', + [12000, 'INVALID', 1], + ], + [ + '#VALUE!', + [12000, 1000, 'INVALID'], ], ]; diff --git a/tests/data/Calculation/Financial/SYD.php b/tests/data/Calculation/Financial/SYD.php index e6b612b6..a8dd078c 100644 --- a/tests/data/Calculation/Financial/SYD.php +++ b/tests/data/Calculation/Financial/SYD.php @@ -33,6 +33,30 @@ return [ 409.09090909090907, [30000, 7500, 10, 10], ], + [ + -800, + [-2000, 1000, 5, 2], + ], + [ + 3771.4285714285716, + [12000, 1000, 2.5, 2], + ], + [ + 5028.571428571428, + [12000, 1000, 2.5, 1.5], + ], + [ + -600, + [-2000, 1000, 5, 3], + ], + [ + -800, + [12000, 15000, 5, 2], + ], + [ + '#NUM!', + [12000, -1000, 5, 3], + ], [ '#NUM!', [10000, 1000, 5, 10], @@ -41,4 +65,32 @@ return [ '#VALUE!', ['INVALID', 1000, 5, 1], ], + [ + '#VALUE!', + [12000, 'INVALID', 5, 1], + ], + [ + '#VALUE!', + [12000, 1000, 'INVALID', 1], + ], + [ + '#VALUE!', + [12000, 1000, 5, 'INVALID'], + ], + [ + '#NUM!', + [12000, -1, 5, 2], + ], + [ + '#NUM!', + [12000, 1000, -5, 1], + ], + [ + '#NUM!', + [12000, 1000, 5, 0], + ], + [ + '#NUM!', + [12000, 1000, 5, -1], + ], ]; diff --git a/tests/data/Calculation/Financial/YIELDDISC.php b/tests/data/Calculation/Financial/YIELDDISC.php index e6260a86..8750e98a 100644 --- a/tests/data/Calculation/Financial/YIELDDISC.php +++ b/tests/data/Calculation/Financial/YIELDDISC.php @@ -9,4 +9,28 @@ return [ 0.06220123250590336, '1-Jan-2017', '30-Jun-2017', 97, 100, ], + [ + '#VALUE!', + 'Invalid', '30-Jun-2017', 97, 100, + ], + [ + '#VALUE!', + '1-Jan-2017', 'Invalid', 97, 100, + ], + [ + '#VALUE!', + '1-Jan-2017', '30-Jun-2017', 'NaN', 100, + ], + [ + '#VALUE!', + '1-Jan-2017', '30-Jun-2017', 97, 'NaN', + ], + [ + '#NUM!', + '1-Jan-2017', '30-Jun-2017', -97, 100, + ], + [ + '#NUM!', + '1-Jan-2017', '30-Jun-2017', 97, -100, + ], ]; diff --git a/tests/data/Calculation/Financial/YIELDMAT.php b/tests/data/Calculation/Financial/YIELDMAT.php index 6f26b15c..49ced033 100644 --- a/tests/data/Calculation/Financial/YIELDMAT.php +++ b/tests/data/Calculation/Financial/YIELDMAT.php @@ -9,4 +9,32 @@ return [ 0.04210977320221025, '1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 0.055, 101, ], + [ + '#VALUE!', + 'Invalid', '30-Jun-2018', '01-Jul-2014', 0.055, 101, + ], + [ + '#VALUE!', + '1-Jan-2017', 'Invalid', '01-Jul-2014', 0.055, 101, + ], + [ + '#VALUE!', + '1-Jan-2017', '30-Jun-2018', 'Invalid', 0.055, 101, + ], + [ + '#VALUE!', + '1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 'NaN', 101, + ], + [ + '#VALUE!', + '1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 0.055, 'NaN', + ], + [ + '#NUM!', + '1-Jan-2017', '30-Jun-2018', '01-Jul-2014', -0.055, 101, + ], + [ + '#NUM!', + '1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 0.055, -101, + ], ];