I want to compound interest on a weekly/fortnightly/monthly/annual basis.
I also want an option to have a deposit amount that can be added in.
I have already tried the standard formula of calculating the final amount accrued, as seen here:
(source: gstatic.com)
For example here is my method for calculating the interest compounding weekly:
function calculateWeekly(state: any) {
const { savings, deposit ,interest, timePeriodSelector, timePeriodLength } = state;
let numberOfYears = 0;
if (timePeriodSelector === "weekly") {
numberOfYears = timePeriodLength / weeksInAYear;
} else if (timePeriodSelector === "fortnightly") {
numberOfYears = (timePeriodLength / weeksInAYear) * 2;
} else if (timePeriodSelector === "monthly") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAMonth;
} else if (timePeriodSelector === "annually") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAYear;
}
const weeklyRate = interest / 100 / weeksInAYear;
const lengthOfCompunding = numberOfYears * weeksInAYear;
let startingFigure = parseInt(savings) + parseInt(deposit);
//total gets added on for every time cycle of week
let total =
(startingFigure * (Math.pow(1 + weeklyRate, lengthOfCompunding) - 1)) / weeklyRate;
return roundToTwoDP(total);
}
The issue with the above code is that the deposit gets added into the calculation every time the interest accrues. So a deposit of $10 weekly for 10 weeks will actually get added up to $100.
I attempted a method to accrue the interest using a loop for each week here:
// loops how many times to compound the interest
for(let i = numberOfYears - (1/weeksInAYear); i > 0; i-= (1/weeksInAYear)){
let interestGained = (total * (Math.pow((1 + weeklyRate), lengthOfCompunding))) - total;
total += interestGained + savings;
}
Thanks for any help!
This should do what you want:
const range = (min, max) => {
const size = 1 + max - min
return [...Array(size).keys()].map(n => n + min)
}
const weeksInAYear = 52
const addWeeklyInterest = interestRatePerWeek => (savings, _) => savings + savings * interestRatePerWeek
const calculateTotal = (savings, numberOfYears, interestRatePerWeek) => {
const numberOfWeeks = numberOfYears * weeksInAYear
return range(1, numberOfWeeks).reduce(addWeeklyInterest(interestRatePerWeek), savings)
}
console.log(calculateTotal(1000.00, 1, 0.02))
Output is 2800.328185448178. You might want to round that for display purposes, but also keep in mind that if accuracy is important, you can't use floating-point numbers.
Related
I am trying to generate random intervals between the range: 2,5k - 10M.
Currently, I am doing the following:
const MIN_NUMBER = 2500;
const MAX_NUMBER = 10000000;
const random = (min, max, floating = false) => {
const result = Math.random() * max + min;
return floating ? result : Math.floor(result);
};
const min = random(MIN_NUMBER, MAX_NUMBER / 10);
const max = random(min, min * 10);
const interval = `[${min}, ${max}]`;
console.log(interval);
But as you can see, the probability that the generated interval is small/medium is not very high.
I want to get random intervals like:
[2500, 10400]
[2500, 9919]
[3000000, 3301029]
[500000, 611223]
I am not following any specific rule, but as you can see, in relation with
[2500, 400000]
[2500, 71000]
[3000000, 10000000]
[500000, 3120000]
they are considered "small/medium", because there is not a "really huge" diff between the max and the min).
With my current algorithm, you can check that the generated average diff is high:
const MIN_NUMBER_OF_LIKES = 2500;
const MAX_NUMBER_OF_LIKES = 10000000;
const random = (min, max, floating = false) => {
const result = Math.random() * max + min;
return floating ? result : Math.floor(result);
};
let averageDiff = 0;
const numIterations = 1000;
for (let i = 0; i < numIterations; i++) {
const min = random(MIN_NUMBER_OF_LIKES, MAX_NUMBER_OF_LIKES / 10);
const max = random(min, min * 10);
averageDiff += max - min;
}
averageDiff /= numIterations;
console.log({ averageDiff });
How can I do for getting random small segments instead?
--Note: the difference between the randomly generated intervals is random too, but it has to be "small/medium" (not as huge as with my current solution).
What about first choosing random size of interval within the size you wish - so you get the value of INTERVAL_SIZE. As second step you randomly find the minimum between MIN_NUMBER_OF_LIKES and MAX_NUMBER_OF_LIKES - INTERVAL so you get STARTPOINT.
So final INTERVAL will have STARTPOINT and ENDPOINT = STARTPOINT + INTERVAL_SIZE
Based on #krnz solution:
const MIN = 2500;
const MAX = 10000000;
const random = (min, max, floating = false) => {
const result = Math.random() * max + min;
return floating ? result : Math.floor(result);
};
function generateRandomSmallInterval() {
const intervalSize = random(1000, 10000);
const start = random(MIN, MAX-intervalSize);
const end = start + intervalSize;
return {start, end};
}
const interval = generateRandomSmallInterval();
console.log({ interval });
console.log(`Diff: ${interval.end - interval.start}`);
Refactored and generalized code using lodash:
import { random } from 'lodash';
function generateRandomIntervalInRange(min, max, maxIntervalSize = max - min) {
if (max - min <= 0) {
throw new Error("The `max` argument must be greater than `min`.");
}
if (maxIntervalSize <= 0) {
throw new Error("The maximum interval size must be greater than 0.");
}
if (maxIntervalSize > max - min) {
throw new Error(
`The maximum interval size mustn't be greater than ${max - min}.`
);
}
const intervalSize = random(1, maxIntervalSize);
const start = random(min, max - intervalSize);
const end = start + intervalSize;
return { start, end };
}
//
// MAIN
//
const MIN = 1;
const MAX = 10;
const MAX_INTERVAL_SIZE = 5;
console.log(generateRandomIntervalInRange(MIN, MAX, MAX_INTERVAL_SIZE));
I am trying to generalize the following function that I have implemented:
/**
* Calculates an interval for the given age.
*
* #memberof module:Users/Functions
* #function getAgeInterval
* #param {number} age - The age of the user.
* #param {number} [minimumAge=18] - The minimum age.
* #param {number} [range=10] - The range.
* #throws {Error} The given age must be greater or equal than the minimum age.
* #returns {string} The age interval.
*/
export default (age, minimumAge = 18, range = 10) => {
if (age < minimumAge) {
throw new Error(
"The given age must be greater or equal than the minimum age.";
);
}
const start = Math.floor((age - 1) / range) * range + 1;
const end = start + range - 1;
const interval = `${Math.max(start, minimumAge)}-${end}`;
return interval;
};
Basically, in this method, I group the age of my users using a minimum age and a range. Here is an example:
const getAgeInterval = (age, minimumAge = 18, range = 10) => {
if (age < minimumAge) {
throw new Error(
"The given age must be greater or equal than the minimum age."
);
}
const start = Math.floor((age - 1) / range) * range + 1;
const end = start + range - 1;
const interval = `${Math.max(start, minimumAge)}-${end}`;
return interval;
};
//
// MAIN
//
for (let age = 18; age < 100; age += Math.round(Math.random() * 10)) {
console.log(`${age}: ${getAgeInterval(age)}`);
}
For now, the method is only working for "ages". But I suppose it is possible to make it work with any type of numbers, (i.e. the total followers counter of a user).
Users might have different number of followers, and I need to group it reusing the method I implemented. The output should look like:
0: "0-10"
100: "11-100"
999: "101-1000"
1117: "1001-10000"
9999: "1001-10000"
15201: "10001-100000";
1620620: "1000001-10000000"
As you can see, the only difference, in order to make it work, is the "dynamic" range. If you take a look at the output, the range goes from 10 to millions.
Any ideas? Any generic implementation to allow dynamic ranges?
UPDATE
Here is the generic method:
const calculateInterval = (counter, minimumCounter = 0, range = 10) => {
if (counter < minimumCounter) {
throw new Error(
"The given counter must be greater or equal than the minimum counter."
);
}
const start = Math.floor((counter - 1) / range) * range + 1;
const end = start + range - 1;
const interval = `${Math.max(start, minimumCounter)}-${end}`;
return interval;
};
//
// MAIN
//
const counters = [0, 100, 999, 1117, 9999, 15201, 1620620];
counters.forEach((totalFollowers) => {
console.log(`${totalFollowers}: ${calculateInterval(totalFollowers)}`);
});
//
// Q: HOW DO I MAKE THE RANGE DYNAMIC (BASED ON THE AMOUNT OF FOLLOWERS)?
//
OUTPUT MUST BE:
0: "0-10"
100: "11-100"
999: "101-1000"
1117: "1001-10000"
9999: "1001-10000"
15201: "10001-100000";
1620620: "1000001-10000000"
What you're looking for is called a logarithmic scale. In this case, the interval is not incremented but multiplied by the range in each step.
You can find the beginning of the range by raising r to the floor of the r-base logarithm of n-1, where r is the range and n is the number.
To get the edge cases right though, you need to make some adjustments (add one to the start of the range, add a default for values smaller or equal to the range, etc):
const baseNlog = (base, x) => Math.log(x) / Math.log(base)
const logarithmicInterval = (n, range = 10) => {
if(n <= range)
return `0-${range}`
const start = range ** Math.floor(baseNlog(range, n-1));
const end = start * range;
const interval = `${start + 1}-${end}`;
return interval;
};
//
// MAIN
//
console.log([
0,
1,
10,
11,
100,
999,
1117,
9999,
15201,
1620620
].map(e => `${e}: ${logarithmicInterval(e)}`))
What you can simply do is counting the number of digit in the number and creating your range using this.
For the low range it will be 10 ** (nbDigits - 1) + 1 (or 0 if the number is 0)
For the high range it will be 10 ** (nbDigits)
const calculateInterval = (number, minimumCounter = 0) => {
if (number < minimumCounter) {
throw new Error(
"The given counter must be greater or equal than the minimum counter."
);
}
const nbDigits = number > 0 ? (number - 1).toString().length : 1
const start = number > 0 ? 10**(nbDigits - 1) + 1 : 0
const end = 10 ** (nbDigits)
const interval = `${Math.max(start, minimumCounter)}-${end}`;
return interval;
};
//
// MAIN
//
const counters = [0, 100, 999, 1117, 9999, 15201, 1620620];
counters.forEach((totalFollowers) => {
console.log(`${totalFollowers}: ${calculateInterval(totalFollowers)}`);
});
One simple implementation could use a switch block to return a range string based on the length of the passed number.
Working snippet:
console.log(getRange(100))
console.log(getRange(999))
function getRange(num) {
numdigits= parseInt(num-1).toString().length;
switch(numdigits) {
case 1:
return "0..10"
case 2:
return "11..100"
case 3:
return "101..1000"
case 4:
return "1001..10000"
case 5:
return "10001..100000"
case 6:
return "100001..1000000"
case 7:
return "1000001..10000000"
default:
return "too large"
}
}// end function
Another approach could build the string by catenating zeros to the upper and lower limits in a loop iterating the length of the passed number times.
Like this:
console.log(getRange(100))
console.log(getRange(999))
function getRange(num) {
numdigits= parseInt(num-1).toString().length;
if (numdigits == 0) {return "0..10"}
else {
return `1${"0".repeat(numdigits-2)}..1${"0".repeat(numdigits)}`;
}
} // end function
In a future income calculator, I need to display the data accumulated in 5 years, 10 years and 15 years.
In this account, I take the value of the monthly contributions, after 12 months, I apply the annual profitability, with that the final value of one year is concluded.
To get the value of the second year, I take the initial value of the sum of the 12 months and make the sum with the value of the 12 months with profitability.
The account is as follows ...
contributions = 536,06;
profitability = 4.27;
fixedYearVal = 536,06 * 12; // 6.432,72
profitabilityVal = (profitability / 100) * fixedYearVal;
fixedYearprofitability = fixedYearVal + profitabilityVal;
With that, I discover the first year with profitability.
The value for the second year will be (secondYear = fixedYearVal + fixedYearprofitability).
The final amount for the second year will be
percentSecondYear = (profitability / 100) * secondYear;
finalSecondYear = percentSecondYear + secondYear;
And the value of the third year is going to be
thirYear = finalSecondYear + fixedYearVal;
percentthirdYear = (profitability / 100) * thirYear;
finalThirdyear = percentthirdYear + thirYear;
Anyway, as I said, I need it for 5 years, 10 years and 15 years, and I can't imagine any other way than to make thousands of lines, I thought about doing it with a for in Javascript but with this data boo I found myself lost .
😢
I threw something together. Maybe this can help you get started. The idea is 1) set some base values 2) throw it through a loop of n years you want to calculate. 3) return the final results in an array so you can see a year by year
// Calculate the next year
const caclNextYear = (lastYear, fixedYearVal, profitability) => {
const nextYr = lastYear + fixedYearVal;
const percentSecondYear = (profitability / 100) * nextYr;
const finalYr = percentSecondYear + nextYr;
return finalYr;
};
// Calc years by number of years
const estimateByYears = (years) => {
const contributions = 536.06;
const profitability = 4.27;
const fixedYearVal = 536.06 * 12; // 6.432,72
const profitabilityVal = (profitability / 100) * fixedYearVal;
const fixedYearprofitability = fixedYearVal + profitabilityVal;
const yearByYear = [fixedYearprofitability];
for (let i = 1; i < years; i++) {
let lastYr = yearByYear[yearByYear.length - 1];
yearByYear.push(caclNextYear(lastYr, fixedYearVal, profitability));
}
return yearByYear;
};
// Call
const yearByYear = estimateByYears(5);
// yearByYear : [6707.397144, 13701.200146048799, 20993.63853628508, 28597.46404578445, 36525.97290453945
console.log('yearByYear', yearByYear);
I am working on a calculator that calculates simple interest and compounding interest. All good, with the simple interest, but I can't seem to be able to solve the problem with the compounding interest, using a loop. I need a loop, because pushing the data into an array to use it in a chart later.
I have the formula from here: https://www.moneysmart.gov.au/managing-your-money/saving/compound-interest
I am using this as reference: https://www.bankrate.com/calculators/retirement/roi-calculator.aspx
The code is in work here: http://www.course0001.com/fiverr/iddqd
I have this so far(updated):
// Inputs from user:
// Initial deposit (starting balance)
// Number of years
// Interest
// Frequent Deposit amount
// Deposit and compound frequency (monthly, weekly, yearly)
// Calculations
var investedCapitalArray = [];
var simpleInterestArray = [];
var compoundInterestArray = [];
var compoundPrincipal = 0;
var years = [];
var accumulatedInvestment;
function calculate() {
years = [];
let interest = rateNumeric.getNumber() / 100; // annual interest rate
let additionalDeposit = additionalNumeric.getNumber(); // Regular deposit
let frequency = freqInput.value; // Frequency of regular deposit
let initialDeposit = initialNumeric.getNumber();
let taxRate = taxNumeric.getNumber();
// Invested captal amount first year
investedCapitalArray = [];
investedCapitalArray.push(initialDeposit + (frequency * additionalDeposit));
// simple interest first year
simpleInterestArray = [];
simpleInterestArray.push((investedCapitalArray[0] * ( (interest) / 100)) * (1 - taxRate));
// compund interest first year
compoundInterestArray = [];
let firstYearInvestment = investedCapitalArray[0]; // First deposit + regular deposits in first year
for (let i = 1 ; i < yearsInput.value ; i++) {
// Invested capital over the years (correct results)
investedCapitalArray.push( (investedCapitalArray[i-1]) +
(frequency * additionalDeposit) );
// simple interest over the years (correct results)
simpleInterestArray.push( simpleInterestArray[i-1] +
((firstYearInvestment +
((frequency) * additionalDeposit) * i ) * interest) );
// compound interest over the years (incorrect results)
compoundInterestArray.push( investedCapitalArray[i-1] *
Math.pow(1 + interest / 100, i) - initialDeposit);
years.push('Year' +i);
}
}
The issue is with the paranthesis you should use (investedCapitalArray[i - 1] + compoundInterestArray[i - 1]) * (1 + 0.07). Thanks
I think the problem is with unboxing the object. Try this:
compoundInterestArray.push( compoundInterestArray[i-1] + (parseInt(investedCapitalArray[i-1]) + parseInt(simpleInterestArray[i-1])) * ( rateNumberic.getNumber() / 100)) );
Thank you everyone for the inputs, after thoroughly researching the compounding interest topic, I wrote an algorithm that works perfectly. It's actually quite simple.
My algorithm is based on this explanation:
"What Is Compound Interest? Compound interest (or compounding interest) is interest calculated on the initial principal, which also includes all of the accumulated interest of previous periods of a deposit or loan."
Therefore it works like this in a loop:
compoundInterest += (((simpleInterestArray[i - 1] + compoundInterest) * (interest));
Full code below.
for (let i = 1; i < yearsInput.value; i++) {
// Invested capital over the years (works fine)
investedCapital = (investedCapitalArray[i - 1]) +
(frequency * additionalDeposit);
investedCapitalArray.push(investedCapital);
// imple interest over the years (works fine)
simpleInterest = simpleInterestArray[i - 1] +
((firstYearInvestment + (frequency * additionalDeposit) * i) * interest);
simpleInterestArray.push(simpleInterest);
// compound interest over the years (correct results)
compoundInterest += (((simpleInterestArray[i - 1] + compoundInterest) * (interest)));
compoundInterestArray.push(compoundInterest);
}
I'm working on the classic "making change" problem, which is highly documented in plenty of other languages, but there's not much out there for it in Javascript. So far, I have this:
var total = $('#total').val();
var coins = [];
function makeChange(total, coins) {
var remainder = 0;
if (total % 0.25 < total) {
coins[3] = parseInt(total / 0.25);
remainder = total % 0.25;
total = remainder;
}
if (total % 0.10 < total) {
coins[2] = parseInt(total / 0.10);
remainder = total % 0.10;
total = remainder;
}
if (total % 0.05 < total) {
coins[1] = parseInt(total / 0.05);
remainder = total % 0.05;
total = remainder;
}
coins[0] = parseInt(total / 0.01);
}
function showChange(coins) {
if (coins[3] > 0) {
$('.quarter').html(coins[3] + " quarter(s).");
}
if (coins[2] > 0) {
$('.dime').html(coins[2] + " dime(s).");
}
if (coins[1] > 0) {
$('.nickel').html(coins[1] + " nickel(s).");
}
if (coins[0] > 0) {
$('.penny').html(coins[0] + " pennies.");
}
}
makeChange(total, coins);
showChange(coins);
However, this seems awfully repetitive and I'm finding that with certain values, it's a penny off. How can I make it more accurate and concise?
I'm finding that with certain values, it's a penny off.
Probably due to floating-point issues. And you shouldn't use parseInt to convert a number - it's meant for strings.
this seems awfully repetitive
A loop, with a data structure that represent the different coins will help. You already did something like that for your result: coins is an array, not 4 different variables.
function makeChange(total, values) {
var coins = [],
epsilon = 1e-5; // this is wrong in general!
// assume values are ascending, so we loop backwards
for (var i=values.length; i--; ) {
coins[i] = Math.floor(total / values[i].val + epsilon);
total %= values[i].val;
}
return coins;
}
function showChange(coins, values) {
for (var i=values.length; i--; ) {
var el = $(values[i].sel);
if (coins[i] > 0) {
el.html(coins[i] + " "+values[i].name+".");
} else {
el.empty();
}
}
}
var values = [
{val:0.01, sel:'.penny', name:"pennies"},
{val:0.05, sel:'.nickel', name:"nickel(s)"},
{val:0.10, sel:'.dime', name:"dime(s)"},
{val:0.25, sel:'.quarter', name:"quarter(s)"}
];
showChange(makeChange(parseFloat($('#total').val()), values), values);
Your best bet to avoid rounding problems is to just multiple your total by 100 to convert your dollar amount into all pennies, then do you conversion. For example:
function makeChange(total, coins) {
var remainder = 0;
total = Math.round(total * 100);
coins[3] = Math.floor(total / 25);
remainder = total - coins[3] * 25;
total = remainder;
coins[2] = Math.floor(total / 10);
remainder = total - coins[2] * 10;
total = remainder;
coins[1] = Math.floor(total / 5);
remainder = total - coins[1] * 5;
total = remainder;
coins[0] = total;
}
http://jsfiddle.net/t14cwdph/4/
For making your code easier to manage - see #Bergi's answer.