My project involves creating a table of data with a row for each of the 12 months of the year. The user of the app will define their monthly pay and percentage of pay they want to contribute to his/her 401k. The table shows in which month the user has reached the IRS contribution limit ($22,500) and consequently cannot contribute any more until next year.
I'm using Xcode playground to test my formulae. Since each month from February through December must look back at contributions and sums from the previous months, there is a considerable amount of looping which drives excessive execution time. I have a computed variable titled "personal401kContributionMonthly" which computes what the user has defined for their desired percent of monthly pay to contribute to their 401k. This computed property ran 4,940,531 times before the playground crashed with the following error: "error: Execution was interrupted, reason: signal SIGKILL. [12211:365921] Unable to quarantine process. (Error: -1.)"
I am seeking for advice/techniques to simplify the code to avoid the crash.
var monthlyPay = 15203.0
var personal401kLimit = 22500.0
var personal401kPercentage = 10.0
var roth401kPercentage = 10.0
var personal401kContributionMonthly: Double {
monthlyPay * (personal401kPercentage/100)
}
personal401kContributionMonthly
var persTradContJan: Double {
personal401kContributionMonthly <= personal401kLimit ? (personal401kContributionMonthly * (1 - (roth401kPercentage/100))) : personal401kLimit * (1 - (roth401kPercentage/100))
}
persTradContJan
var persRothContJan: Double {
roth401kPercentage == 0.0 ? 0.0 : personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : personal401kLimit * (roth401kPercentage/100)
}
persRothContJan
var pers401ksumFeb: Double {
persTradContJan + persRothContJan
}
var persTradContFeb: Double {
pers401ksumFeb + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumFeb < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumFeb)) : 0.0
}
persTradContFeb
var persRothContFeb: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumFeb + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumFeb < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumFeb) : 0.0
}
persRothContFeb
var pers401ksumMar: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb
}
var persTradContMar: Double {
pers401ksumMar + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumMar < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumMar)) : 0.0
}
persTradContMar
var persRothContMar: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumMar + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumMar < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumMar) : 0.0
}
persRothContMar
var pers401ksumApr: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar
}
var persTradContApr: Double {
pers401ksumApr + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumApr < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumApr)) : 0.0
}
persTradContApr
var persRothContApr: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumApr + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumApr < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumApr) : 0.0
}
persRothContApr
var pers401ksumMay: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr
}
var persTradContMay: Double {
pers401ksumMay + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumMay < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumMay)) : 0.0
}
persTradContMay
var persRothContMay: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumMay + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumMay < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumMay) : 0.0
}
persRothContMay
var pers401ksumJun: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay
}
var persTradContJun: Double {
pers401ksumJun + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumJun < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumJun)) : 0.0
}
persTradContJun
var persRothContJun: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumJun + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumJun < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumJun) : 0.0
}
persRothContJun
var pers401ksumJul: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay + persTradContJun + persRothContJun
}
var persTradContJul: Double {
pers401ksumJul + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumJul < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumJul)) : 0.0
}
persTradContJul
var persRothContJul: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumJul + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumJul < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumJul) : 0.0
}
persRothContJul
var pers401ksumAug: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay + persTradContJun + persRothContJun + persTradContJul + persRothContJul
}
var persTradContAug: Double {
pers401ksumAug + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumAug < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumAug)) : 0.0
}
persTradContAug
var persRothContAug: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumAug + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumAug < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumAug) : 0.0
}
persRothContAug
var pers401ksumSep: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay + persTradContJun + persRothContJun + persTradContJul + persRothContJul + persTradContAug + persRothContAug
}
var persTradContSep: Double {
pers401ksumSep + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumSep < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumSep)) : 0.0
}
persTradContSep
var persRothContSep: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumSep + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumSep < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumSep) : 0.0
}
persRothContSep
var pers401ksumOct: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay + persTradContJun + persRothContJun + persTradContJul + persRothContJul + persTradContAug + persRothContAug + persTradContSep + persRothContSep
}
var persTradContOct: Double {
pers401ksumOct + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumOct < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumOct)) : 0.0
}
persTradContOct
var persRothContOct: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumOct + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumOct < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumOct) : 0.0
}
persRothContOct
var pers401ksumNov: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay + persTradContJun + persRothContJun + persTradContJul + persRothContJul + persTradContAug + persRothContAug + persTradContSep + persRothContSep + persTradContOct + persRothContOct
}
var persTradContNov: Double {
pers401ksumNov + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumNov < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumNov)) : 0.0
}
persTradContNov
var persRothContNov: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumNov + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumNov < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumNov) : 0.0
}
persRothContNov
var pers401ksumDec: Double {
persTradContJan + persRothContJan + persTradContFeb + persRothContFeb + persTradContMar + persRothContMar + persTradContApr + persRothContApr + persTradContMay + persRothContMay + persTradContJun + persRothContJun + persTradContJul + persRothContJul + persTradContAug + persRothContAug + persTradContSep + persRothContSep + persTradContOct + persRothContOct + persTradContNov + persRothContNov
}
var persTradContDec: Double {
pers401ksumDec + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (1 - (roth401kPercentage/100)) : pers401ksumDec < personal401kLimit ? (1 - (roth401kPercentage/100)) * (personal401kLimit - (pers401ksumDec)) : 0.0
}
persTradContDec
var persRothContDec: Double {
roth401kPercentage == 0.0 ? 0.0 : pers401ksumDec + personal401kContributionMonthly <= personal401kLimit ? personal401kContributionMonthly * (roth401kPercentage/100) : pers401ksumDec < personal401kLimit ? (roth401kPercentage/100) * (personal401kLimit - pers401ksumDec) : 0.0
}
persRothContDec
UPDATE 1:
The intent of this 401k project is to also incorporate employer direct contributions and "back-door-Roth" overflow into a 401a account. Consequently I believe I will need to store and initialize the sum of contributions month-to-month because this running total will need to be referenced for calculating each month's 401a contributions.
For example, some employers offer the "back-door" option which allows an employee who has reached the individual contribution limit ($22,500) to continue contributing that year but those funds are deposited into a 401a (not 401k). But once the sum total of a) personal contributions, b) company contributions (match or direct deposit) and c) 401a "back-door" contributions reach the IRS limit for combined contributions ($66,000), then an employee can no longer contribute to his/her 401a account.
In order to show how much an employee would contribute to their 401a account for Jan, Feb, Mar, etc., I need to reference the previous month totals. For example, for the February print iteration, the formula for 401a contributions needs to reference the sum total of personal contributions in January and February. Using the wonderful example of code provided by Chip Jarred (thank you!!), if we set the value of monthly to 15203.0 and the personal401kPercentage to 100% (for demonstration purposes only), this would be the result:
The sum total of $1,520, $13,683, $730, & $6,567 equals $22,500. But the sum of February only ($730 & $6,567 = $7,297) is less than the amount the employee would like to contribute (100% of monthly pay: $15,203). Consequently, in February the 401a deposit should be $7,906 ($730 + $6,567 + $7,906 = $15,203).
In my view, the first step in achieving this functionality is to store and set in the initializer the cumulative totals for personal contributions for each month. (Later we will probably need the cumulative totals for company AND personal contributions in order to know when we've reached $66,000 in order to stop contributing the 401a account. But we can probably address that later).
Here is my attempt at adding a yearToDate property to MonthlyContributions....
extension MonthlyContributions
{
var total: Decimal { roth + traditional }
init(combinedAmount: Decimal, rothPercentage: Decimal)
{
let roth = roundToNearestPenny(
percentage: rothPercentage,
of: combinedAmount
)
self.init(roth: roth, traditional: combinedAmount - roth)
}
}
extension MonthlyContributions
{
var yearToDate: Decimal { total }
init(combinedAmount: Decimal, limit: Decimal)
{
let totalContributions = calculateCombinedContribution(
monthlyContribution : total,
annualContributionLimit : limit,
contributionsSoFarThisYear: yearToDate
)
}
}
Unfortunately this was unsuccessful as using
print(" Total : $\(curContribution.yearToDate)")
only prints singular month totals, not a running cumulative total.
Your playground crashes because all those computed variables reference other computed variables in ways that explode the call tree exponentially, so as @matt, with tongue in cheek, pointed out in comments, it's a literal stack overflow. Computed variables are just syntactic sugar for function calls. Were you intending lazy assignment instead?
In any case, you asked about refactoring the code.
I think your example is the kind of thing that wants to be in a data structure where you index by the month rather than having individually named computed variables. As is often the case, it can be accomplished multiple ways. What follows is one way. I'll break into part.
I'm going to assume that your playground is intended as a sort of prototyping environment for some code that you want to use in a real app. If it's just a learning exercise, using Double
to represent money is fine, but binary floating point types can't exactly represent all decimal numbers. See here for examples and why this is the case. For this reason it's better to use Decimal
, so that's what I'll do.
Unfortunately Decimal
is slow compared to Double
or Float
, and doesn't have the most fully supported API in Swift
. Actually its Objective-C counterpart, NSDecimalNumber
is pretty crufty in Objective-C too. Still, it is the best standardly available type to do financial computations.
Next I don't see any explicit rounding in your code example, and as far as I know fractional penny contributions to 401ks (or IRAs) are not a thing, so you need a way to round to whole pennies. It's probably the most awkward code I'm going to present, so let's get it out of the way first.
There are two ways to do rounding on Decimal
, and unfortunately neither of them are especially convenient. Unlike Double
, Decimal
does not provide .rounded()
method.
One way is to call the NSDecimalRound
free function, which requires getting a pointer to the value to round. I'd probably use that way in my own code, but let's avoid pointers.
The second way is to make use of the fact that Decimal
bridges to NSDecimalNumber
which provides a rounding(accordingToBehavior:)
method. That requires creating a NSDecimalNumberBehaviors
instance that configures how the rounding will be done... Did I mention the API is crufty? We have to cast to NSDecimalNumber
then call rounding(accordingToBehavior:)
and then cast back to Decimal
. It looks like this:
import Foundation
let pennyRoundingBehavior = NSDecimalNumberHandler(
roundingMode: .bankers,
scale: 2,
raiseOnExactness: false,
raiseOnOverflow: true,
raiseOnUnderflow: true,
raiseOnDivideByZero: true
)
/**
Computes `dollarAmount * percentage`, rounded to the nearest penny.
Uses "Banker's" rounding: If truncated digits represent a value greater than
half a penny, the result is rounded up. If it's less than half a penny, the
result is rounded down. If it's exactly half a penny, the value is rounded to
the nearest even penny. So `10.015 is rounded to `10.02`, while `10.025` is
also rounded to `10.02`.
- Parameters:
- percentage: `Decimal` value in the range 0...100
- amount: `Decimal` to which `percentage` is applied.
*/
func roundToNearestPenny(percentage: Decimal, of dollarAmount: Decimal) -> Decimal
{
assert((0...100).contains(percentage))
let x = ((dollarAmount * percentage / 100) as NSDecimalNumber)
return x.rounding(accordingToBehavior: pennyRoundingBehavior) as Decimal
}
Fortunately, all the other math we need to do with Decimal
can be done just like for Double
.
Since you want contributions per month, I define an enum
to represent months:
enum ContributionMonth: Int, CaseIterable
{
case January, February, March, April, May, June,
July, August, September, October, November, December
static var count:Int { allCases.count }
}
I represent the contributions for a given month as a struct
struct MonthlyContributions
{
var roth : Decimal = 0
var traditional : Decimal = 0
}
extension MonthlyContributions
{
var total: Decimal { roth + traditional }
init(combinedAmount: Decimal, rothPercentage: Decimal)
{
let roth = roundToNearestPenny(
percentage: rothPercentage,
of: combinedAmount
)
self.init(roth: roth, traditional: combinedAmount - roth)
}
}
Note the init
in the extension computes the contribution distribution, which will simplify later code.
Since I'll be storing instances of MonthlyContributions
in an Array
with elements that correspond to the months starting with January
it would be nice to just index that array by the month. To do that I extend Array
when it holds MonthlyContributions
as elements:
extension Array where Element == MonthlyContributions {
subscript (index: ContributionMonth) -> Element { self[index.rawValue] }
}
An alternative would be to use a Dictionary
instead of an Array
. For this example, I think Array
is slightly simpler, but it doesn't really matter much. With Dictionary
you wouldn't need the extension, but adding a value requires using both a key and a value, whereas with Array
I can just append
. Dictionary
would be the way to go if you don't want to populate it with contributions for all months.
So next up, for a given month, we need to adjust combined contribution so that total annual contributions will be capped to the legal limit:
func calculateCombinedContribution(
monthlyContribution monthly : Decimal,
annualContributionLimit limit : Decimal,
contributionsSoFarThisYear cummulative: Decimal) -> Decimal
{
min(max(0, limit - cummulative), monthly)
}
Now all of that is used to compute a whole years worth of monthly contributions:
func computeMonthlyContributions(
monthlyPay : Decimal,
contributionPercentage: Decimal,
rothPercentage : Decimal,
contributionLimit : Decimal) -> [MonthlyContributions]
{
assert((0...100).contains(contributionPercentage))
assert((0...100).contains(rothPercentage))
assert(monthlyPay >= 0)
assert(contributionLimit >= 0)
var totalContributions : Decimal = 0
var monthlyContributions: [MonthlyContributions] = []
monthlyContributions.reserveCapacity(ContributionMonth.count)
let monthlyContribution = roundToNearestPenny(
percentage: contributionPercentage,
of: monthlyPay
)
for _ in ContributionMonth.allCases
{
let combinedAmount = calculateCombinedContribution(
monthlyContribution : monthlyContribution,
annualContributionLimit : contributionLimit,
contributionsSoFarThisYear: totalContributions
)
let contribution = MonthlyContributions(
combinedAmount: combinedAmount,
rothPercentage: rothPercentage
)
monthlyContributions.append(contribution)
totalContributions += contribution.total
}
return monthlyContributions
}
The assertions are just there to validate basic numeric assumptions, at least in DEBUG
builds. For each month we determine how much the combined contribution will be for that month, which takes into account whether the total contributions have reached the legal annual limit, then create a MonthlyContributions
instance, which distributes the amount internally between roth
and traditional
, append it to the array, and update the total contributions.
Now we can call it with some values, and print out the calculated monthly contributions:
var monthlyPay : Decimal = 15203.0
var personal401kLimit : Decimal = 22500.0
var personal401kPercentage: Decimal = 10.0
var roth401kPercentage : Decimal = 10.0
let monthlyContributions = computeMonthlyContributions(
monthlyPay : monthlyPay,
contributionPercentage: personal401kPercentage,
rothPercentage : roth401kPercentage,
contributionLimit : personal401kLimit
)
for month in ContributionMonth.allCases
{
let curContribution = monthlyContributions[month]
print("Contribution for \(month)")
print(" Roth : $\(curContribution.roth)")
print(" Traditional: $\(curContribution.traditional)")
print()
}
This just uses the values you provide. The output is:
Contribution for January Roth : $152.03 Traditional: $1368.27 Contribution for February Roth : $152.03 Traditional: $1368.27 Contribution for March Roth : $152.03 Traditional: $1368.27 Contribution for April Roth : $152.03 Traditional: $1368.27 Contribution for May Roth : $152.03 Traditional: $1368.27 Contribution for June Roth : $152.03 Traditional: $1368.27 Contribution for July Roth : $152.03 Traditional: $1368.27 Contribution for August Roth : $152.03 Traditional: $1368.27 Contribution for September Roth : $152.03 Traditional: $1368.27 Contribution for October Roth : $152.03 Traditional: $1368.27 Contribution for November Roth : $152.03 Traditional: $1368.27 Contribution for December Roth : $152.03 Traditional: $1368.27
As you can see the annual contribution limit isn't triggered by that monthlyPay
value, so increasing it to 30000.0
does trigger it:
Contribution for January Roth : $300 Traditional: $2700 Contribution for February Roth : $300 Traditional: $2700 Contribution for March Roth : $300 Traditional: $2700 Contribution for April Roth : $300 Traditional: $2700 Contribution for May Roth : $300 Traditional: $2700 Contribution for June Roth : $300 Traditional: $2700 Contribution for July Roth : $300 Traditional: $2700 Contribution for August Roth : $150 Traditional: $1350 Contribution for September Roth : $0 Traditional: $0 Contribution for October Roth : $0 Traditional: $0 Contribution for November Roth : $0 Traditional: $0 Contribution for December Roth : $0 Traditional: $0
In comments, it became known that a year-to-date value needed to be stored in MonthlyContributions
. I proposed adding the year-to-date value as another stored property, and pass it's value in as another parameter to the init
in the extension
, but thinking on it more, that creates an API that's either not clear as to whether the value should include the current month's contributions or to make it clear would be unnecessarily wordy. Super-long variable names are almost as bad overly short ones. So the way I'd do it is to use the existing MonthlyContributions
to create the one for the next month.
To do that I'd update MonthlyContributions
like this:
struct MonthlyContributions
{
var roth : Decimal = 0
var traditional : Decimal = 0
var yearToDate : Decimal = 0 // <-- ADDED THIS
}
extension MonthlyContributions
{
var total: Decimal { roth + traditional }
// CHANGED INITIALIZER TO INSTANCE METHOD
func nextMonth(
combinedAmount: Decimal,
rothPercentage: Decimal) -> Self
{
let roth = roundToNearestPenny(
percentage: rothPercentage,
of: combinedAmount
)
return Self(
roth: roth,
traditional: combinedAmount - roth,
yearToDate: combinedAmount + yearToDate
)
}
}
Then I'd change computeMonthlyContributions
to create an empty (zeroed) MonthlyContributions
, which is used in each iteration of the loop to create the MonthlyContributions
for the next month. The totalContributions
variable is no longer needed because each month already has the total in it:
func computeMonthlyContributions(
monthlyPay : Decimal,
contributionPercentage: Decimal,
rothPercentage : Decimal,
contributionLimit : Decimal) -> [MonthlyContributions]
{
assert((0...100).contains(contributionPercentage))
assert((0...100).contains(rothPercentage))
assert(monthlyPay >= 0)
assert(contributionLimit >= 0)
// Start with a zeroed MonthlyContributions
var contribution = MonthlyContributions()
var monthlyContributions: [MonthlyContributions] = []
monthlyContributions.reserveCapacity(ContributionMonth.count)
let monthlyContribution = roundToNearestPenny(
percentage: contributionPercentage,
of: monthlyPay
)
for _ in ContributionMonth.allCases
{
// Apply limits to `contribution.yearToDate`
let combinedAmount = calculateCombinedContribution(
monthlyContribution : monthlyContribution,
annualContributionLimit : contributionLimit,
contributionsSoFarThisYear: contribution.yearToDate
)
contribution = contribution.nextMonth(
combinedAmount: combinedAmount,
rothPercentage: rothPercentage
)
monthlyContributions.append(contribution)
}
return monthlyContributions
}
If you like you can now add the year-to-date contributions in the print loop:
for month in ContributionMonth.allCases
{
let curContribution = monthlyContributions[month]
print(" Roth : $\(curContribution.roth)")
print(" Traditional : $\(curContribution.traditional)")
print(" Year-To-Date: $\(curContribution.yearToDate)")
print()
}