Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- import re
- from sys import argv
- from sys import exit
- from sys import stderr
- from math import inf
- # The magic equation solved by this program:
- #
- # growth := 1 + interest
- #
- # growth * periodic_deposit + interest * outcome
- # growth ** periods == ------------------------------------------------------
- # growth * periodic_deposit + interest * initial_deposit
- PERIOD_DELIMITERS = {'×', 'x', 'X'}
- PERIOD_RE = re.compile('|'.join(PERIOD_DELIMITERS))
- def percent(number):
- return f'{round(100 * number, 6)}%'
- def check_inputs(periods, initial_deposit, periodic_deposit, outcome):
- errors = []
- periods_ok, initial_deposit_ok, periodic_deposit_ok, outcome_ok = (
- False, False, False, False)
- subperiods = None
- periods_parts = PERIOD_RE.split(periods)
- if len(periods_parts) > 2:
- errors.append('[# periods]×[# subperiods]: expected one \'×\', got '
- f'{len(periods_parts) - 1}: {periods}')
- else:
- if (len(periods_parts) == 2):
- subperiods = periods_parts[1]
- try:
- subperiods = int(subperiods)
- if subperiods <= 0:
- errors.append(f'[# subperiods]: expected > 0, got: {subperiods}')
- elif subperiods == 1:
- subperiods = None
- except Exception as error:
- errors.append(f'[# subperiods]: {error}')
- periods = periods_parts[0]
- try:
- periods = int(periods)
- if periods <= 0:
- errors.append(f'[# periods]: expected > 0, got: {periods}')
- else:
- periods_ok = True
- except Exception as error:
- errors.append(f'[# periods]: {error}')
- try:
- initial_deposit = int(initial_deposit)
- if initial_deposit < 0:
- errors.append(f'[initial deposit]: expected >= 0, got: {initial_deposit}')
- else:
- initial_deposit_ok = True
- except Exception as error:
- errors.append(f'[initial deposit]: {error}')
- try:
- periodic_deposit = int(periodic_deposit)
- if periodic_deposit < 0:
- errors.append('[periodic deposit]: expected >= 0, got: '
- f'{periodic_deposit}')
- else:
- periodic_deposit_ok = True
- except Exception as error:
- errors.append(f'[periodic deposit]: {error}')
- try:
- outcome = int(outcome)
- if outcome <= 0:
- errors.append(f'[outcome]: expected > 0, got: {outcome}')
- else:
- outcome_ok = True
- except Exception as error:
- errors.append(f'[outcome]: {error}')
- indent = ' ' if subperiods else ''
- print(f'{indent} # periods: {periods}', file=stderr)
- if subperiods:
- print(f' # subperiods: {subperiods}', file=stderr)
- print(f'{indent} initial deposit: {initial_deposit}', file=stderr)
- print(f'{indent}periodic deposit: {periodic_deposit}', file=stderr)
- print(f'{indent} outcome: {outcome}', file=stderr)
- if initial_deposit_ok and periodic_deposit_ok:
- if initial_deposit + periodic_deposit <= 0:
- errors.append('[initial_deposit] + [periodic_deposit]: expected > 0, '
- f'got: {initial_deposit} + {periodic_deposit} <= 0')
- elif periods_ok: # elif, as this makes no sense with both deposits == 0
- sum_of_payments = (initial_deposit +
- periods * (subperiods or 1) * periodic_deposit)
- print(f'{indent} sum of payments: {sum_of_payments}', file=stderr)
- if outcome_ok:
- if outcome < sum_of_payments:
- errors.append('[initial deposit] + [periods×subperiods] * '
- '[periodic deposit]: expected >= [outcome], got: '
- f'{outcome} < {sum_of_payments}')
- else:
- absolute_gain = outcome - sum_of_payments
- relative_gain = f'{percent(absolute_gain / sum_of_payments)}'
- print(f'{indent} absolute gain: {absolute_gain} ', file=stderr)
- print(f'{indent} relative gain: {relative_gain}', file=stderr)
- if errors:
- raise ValueError('\n'.join(errors))
- return periods, subperiods, initial_deposit, periodic_deposit, outcome
- class Savings:
- def __init__(self, initial_deposit, periodic_deposit, outcome):
- self.periodic_deposit = periodic_deposit / outcome # normalized
- self.outcome_periodic = self.periodic_deposit + 1 # 1 == outcome
- self.initial_periodic = self.periodic_deposit + initial_deposit / outcome
- def ugly_fraction(self, interest):
- den = interest * self.initial_periodic + self.periodic_deposit
- return (((interest * self.outcome_periodic + self.periodic_deposit) / den)
- if den else inf)
- def effective_interest(periods, initial_deposit, periodic_deposit, outcome):
- savings = Savings(initial_deposit, periodic_deposit, outcome)
- lower, upper = 0, .01
- while savings.ugly_fraction(upper) > (1 + upper) ** periods:
- upper *= 2
- diff = upper - lower
- last_diff = 2 * diff
- while last_diff > diff:
- interest = (upper + lower) / 2
- if savings.ugly_fraction(interest) > (1 + interest) ** periods:
- lower = interest
- else:
- upper = interest
- diff, last_diff = upper - lower, diff
- return interest
- def usage():
- return (f'\nUsage: {argv[0]} '
- f'''<# periods>[×<# subperiods>] <initial deposit> <periodic deposit> <outcome>
- Examples:
- * 0 coins initially, 1 coin periodically, 20 coins expected in 10 periods:
- $ {argv[0]} 10 0 1 20
- {percent(effective_interest(10, 0, 1, 20))}
- * 1 coin initially, 1 coin periodically, 20 coinst expected in 10 periods:
- $ {argv[0]} 10 1 1 20
- {percent(effective_interest(10, 1, 1, 20))}
- * 10 coins initially, 0 coins periodically, 20 coins expected in 10 periods:
- $ {argv[0]} 10 10 0 20
- {percent(effective_interest(10, 10, 0, 20))}
- * 10 coins initially, 1 coin periodically, 20 coins expected in 10 periods:
- $ {argv[0]} 10 10 1 20
- {percent(effective_interest(10, 10, 1, 20))}
- ''')
- if len(argv) != 5:
- print(usage(), file=stderr)
- exit(1)
- try:
- inputs = check_inputs(*argv[1:])
- subperiods = inputs[1]
- interest = effective_interest(*((inputs[0] * (inputs[1] or 1),) + inputs[2:]))
- if subperiods:
- print(f'subperiod interest: {percent(interest)}\n period interest: ',
- end='', file=stderr, flush=True)
- print(f'{percent((1 + interest) ** subperiods - 1)}', end='', flush=True)
- print(f' over {subperiods} subperiods', end='', file=stderr, flush=True)
- print()
- else:
- print(' period interest: ', file=stderr, end='', flush=True)
- print(f'{percent(interest)}')
- except ValueError as error:
- print(f'\nERRORS:\n{error}', file=stderr)
- print(usage(), file=stderr)
- exit(2)
Advertisement
Add Comment
Please, Sign In to add comment