JavaScript - converting 3 numbers to percentage, doesn't yield 100% total - javascript

I have a scenario where I have three numbers:
17
10
90
I need to convert those into whole percentage values (so that when added, total 100% as you'd expect). I have this function:
function roundPercentageTotals(num1, num2, num3) {
var total = num1 + num2 + num3; // 117
var num1Total = (num1 / total) * 100; // 14.529914529914531
var num2Total = (num2 / total) * 100; // 8.547008547008546
var num3Total = (num3 / total) * 100; // 76.92307692307693
var num1ToDecimal = num1Total.toFixed(1); // 14.5
var num2ToDecimal = num2Total.toFixed(1); // 8.5
var num3ToDecimal = num3Total.toFixed(1); // 76.9
var totalPercentage = parseInt(num1ToDecimal) + parseInt(num2ToDecimal) + parseInt(num3ToDecimal); // 98
return { percentage1: Math.round(num1ToDecimal, percentage2: Math.round(num2ToDecimal), percentage3: Math.round(num3ToDecimal) };
}
In my example, the total percentage calculated is 98%. Followed by:
Percentage1 = 15
Percentage2 = 9
Percentage3 = 77
Which adds up to 101%, where am I going wrong?
Thanks for any help in advance!

You're getting 98% in the first calculation because you're rounding the values down, and then getting 101% in your second because you're rounding them up.
Change your parseInt() to parseFloat() to get your totals to be closer to 100% instead of 98%. parseInt() floors decimals, it does not round them.
In regards to your second calculation totaling 101%: By rounding up 14.5 to 15, and 8.5 to 9, you've just added a full 1%. This leaves you with 101% instead of 100%.
The bottom line is that you cannot consistently achieve an even 100% if you're going to round the exact values, unless you fudge your percentages to fit somewhere along the way.

Ok, so it looks like mathematically, I cannot achieve exactly what I was looking for. However, I needed to round figures up so it equalled 100% in the end (all be in that some of the figures where rounded, so not totally accurate).
Here's my solution, just in case this is useful to someone else:
function roundPercentageTotals(numArr) {
// Total of all numbers passed.
var total = numArr[0] + numArr[1] + numArr[2];
// Percentage representations of each number (out of 100).
var num1Percent = Math.round((numArr[0] / total) * 100);
var num2Percent = Math.round((numArr[1] / total) * 100);
var num3Percent = Math.round((numArr[2] / total) * 100);
// Total percent of the 3 numbers combined (doesnt always equal 100%).
var totalPercentage = num1Percent + num2Percent + num3Percent;
// If not 100%, then we need to work around it by subtracting from the largest number (not as accurate but works out).
if (totalPercentage != 100) {
// Get the index of the largest number in the array.
var index = getLargestNumInArrayIndex(numArr);
// Take the difference away from the largest number.
numArr[index] = numArr[index] - (totalPercentage - 100);
// Re-run this method recursively, until we get a total percentage of 100%.
return roundPercentageTotals(numArr);
}
// Return the percentage version of the array passed in.
return [num1Percent, num2Percent, num3Percent];
}
function getLargestNumInArrayIndex(array) {
return array.indexOf(Math.max.apply(Math, array));
}
Pass an array of the numbers into roundPercentageTotals, such as roundPercentageTotals([13,54,38]) and it will return the whole percentage (or nearest percentage I should say) figures in an array.

percent-round is doing just that. did the job for me.
just pass values and get percentages back that always add up to 100%.
const percentRound = require ('percent-round');
percentRound([16, 56, 18]); // [18, 62, 20]

You cannot convert those numbers in percentage without decimals. It will work only if the numbers are divided by 100. So the answere here must be (1. 14.5 , 2. 8.5 , 3. 76.9). And as you can see there is a "0.1" percent missing for the same reason of the decimals you threw (i.e by converting 14.529914529914531 to 14.5).

Related

Prompting for a variable not working when trying to find random number

My assignment for Intro to Javascript is to: "Write a function that accepts two numbers and returns a random number between the two values." It seems easy enough until I attempt to prompt the variables to input, at which point the output seems incorrect.
This is code that is most recommended for finding a random number between two ints, in this case 1 and 6:
function getRandomizer(bottom, top) {
return function() {
return Math.floor( Math.random() * ( 1 + top - bottom ) ) + bottom;
}
}
var rolldie = getRandomizer(1, 6);
document.write(rolldie());
The output I get is what it should be, a random integer between 1 and 6. However my assignment is to prompt for the numbers, which can be anything. So I do this, using 10 and 1 as example numbers:
var max = prompt("input 10");
var min = prompt("input 1");
function getRandomizer(bottom, top) {
return function() {
return Math.floor( Math.random() * ( 1 + top - bottom ) ) + bottom;
}
}
var rolldie = getRandomizer(min, max);
document.write(rolldie());
The output: 961. Second try: 231. etc. If I set the variables max and min directly to 10 and 1, the code works perfectly, returning numbers between 1 and 10. But for some reason, prompting input and then entering the exact same numbers gives completely different output. Why does this happen and how can I fix it?
The reason this happens is that the prompts are being treated as strings. So you're actually getting numbers between 1101 and 1.
You can ensure the vars min and max are numbers by using parseInt:
var max = parseInt(prompt("input 10"));
Try this Code below:
var max = Number(prompt("input 10"));
var min = Number(prompt("input 1"));

Javascript Rounding to the nearest 2 decimal (however 5 rounds down)

All my values are being returned from the server as 3 decimal places. I need to round to the nearest 10, 2 decimal places, ex. decimal(18,2) from decimal(18,3). The catch is that when it's a 5, it needs to round down.
I need to do this in JavaScript :D
I can not guarantee 3 decimal places will be returned, that is the maximum.
ex. 4.494 -> 4.49
**ex. 4.495 -> 4.49**
ex. 4.496 -> 4.50
It seems you want special rounding only where the last digit is 5, so test for that and round those cases differently:
function myRound(n) {
// If ends in .nn5, round down
if (/\.\d\d5$/.test(''+n)) {
n = Math.floor(n*100)/100;
}
// Apply normal rounding
return n.toFixed(2);
}
console.log(myRound(4.494)); // 4.49
console.log(myRound(4.495)); // 4.49
console.log(myRound(4.496)); // 4.50
Perhaps create your own custom round function? check out Is it ok to overwrite the default Math.round javascript functionality?
Given the solution in the above post, you might modify it slightly like this:
Number.prototype.round = function(precision) {
var numPrecision = (!precision) ? 0 : parseInt(precision, 10);
var numBig = this * Math.pow(10, numPrecision);
var roundedNum;
if (numBig - Math.floor(numBig) == 0.5)
roundedNum = (Math.round(numBig) + 1) / Math.pow(10, numPrecision);
else
roundedNum = Math.round(numBig) / Math.pow(10, numPrecision);
return roundedNum;
};
var n = 2.344;
var x = n.round(2);
alert(x);

How can I scale an array of values while maintaining a minimum value?

So I have an array of values that I need to scale down while maintaining a minimum value for the scaled value.
For example, let's say I have an array of values [1, 1, 3, 5] with a minimum scale factor of .2.
By normalizing the array, we get the array [.1, .1, .3, .5]. However, keeping in mind the minimum scale factor, we'd have values [.2, .2, .3, .5], which adds up to 1.2, not 1.
My thinking was to iterate over the array and first set all values that would be under the minimum to the minimum, keeping a carry variable to determine how much still needs to be redistributed to the other elements in the array that were over the minimum.
With all of the values that were over the minimum, scale their values with respect to the carry variable and then subtract that from their values.
So with the example above, we'd subtract 3/8 * .2 from .3, and 5/8 * .2 from .5 to get [.2, .2, .225, .375].
Is there any other way to do this more efficiently? Or an alternative way to scale the remaining values?
Edit: Sorry, scaling might be the incorrect term, but in the end the values of the array are to be divided in such a way that their values are changed with respect to the total value.
I'll explain the specific implementation so that the question might be more clear:
I have a number of posts, and each of the posts is to be shown for a certain amount of time before fading out, after which the next post is to be shown. I want the delay between posts to be dependent on the number of words within each post, but also constrained to be at least some minimum value.
There is a total amount of time for all of the posts to be shown, and the time is supposed to be split up between all of the posts.
I want the delay between posts to be dependent on the number of words within each post, but also constrained to be at least some minimum value.
There is a total amount of time for all of the posts to be shown, and the time is supposed to be split up between all of the posts.
You cannot guarantee that you'll meet both requirements. If you have 30 posts, each of which must be displayed for at least one second, and only 20 seconds in which to display them, then it's impossible meet both requirements. You'll have to:
Extend the total time; OR
Reduce the minimum time
We have a sample set s = [ 1 1 3 5 ], and we are looking for a function f(x) which takes a single sample and returns the display time.
Requiring Sum(Map(s, f)) = 1.0, (that is, the sum of f(s) for all s[i]) and also that s[i] >= minVal for all s[i], consider first the linear function
f(x) = ax + b
For the minimum
a.xmin + b = minVal
b = minVal - a.xmin
Sum:
total = Sum(f(x) for x in s)
= Sum((a*x + b) for x in s)
= b*len(s) + Sum(a*x for x in s)
= b*len(s) + a * Sum(s)
1 = b*len(s) + a * Sum(s)
a = (b * len(s) - 1.0) / Sum(s)
Substit
1 = b*len(s) + a * Sum(s)
1 = (minVal - a.xmin) * len(s) + a * Sum(s)
1 = minVal * len(s) - xmin * len(s) * a + Sum(s) * a
1 - (minVal * len(s)) = (Sum(s) - xmin*len(s)) * a
a = (1 - (minVal * len(s))) / (Sum(s) - xmin*len(s))
Given a,
b = minVal - a.xmin
Thus in javascript we can have a function factory to give you a scaling function f, given a sample set s:
function makeScalingFun(s, minVal) {
var total = s.reduce(function(a, b) { return a + b; });
var xmin = s.reduce(function(a, b) { return Math.min(a,b); });
// f(x) = ax + b
var a = (1.0 - (minVal * s.length)) / (total - xmin * s.length)
var b = minVal - a * xmin
var f = function(x) {
return a * x + b;
};
return f;
}
And in use:
var scaler = makeScalingFun(s, 0.2);
console.log("Index, Value: Calced Delay");
for(var i = 0; i < s.length; ++i) {
console.log(i + ", " + s[i] + ": " + scaler(s[i]));
}
Result:
Index, Value: Calced Delay
0, 1: 0.2
1, 1: 0.2
2, 3: 0.26666666666666666
3, 5: 0.3333333333333333
If you have further requirements, you could use them to construct a quadratic target function instead of a linear one, etc.
Note that the smallest value always gets given the minVal delay, which isn't perhaps very realistic. Consider modifying to use a constant value for xmin, e.g. 0, so that if you have 3 posts of 450, 451 and 452, you don't just get a comparatively tiny delay for the first just because it's the shortest.
I would take the following approach:
Assume you have the values as in your example: [1 1 3 5]. The sum of the values is 10.
So, divide all values by 10 and correct the values under the minimum. Keep track of the number of corrected values. In this case that's 2. Multiply 2 by .2. That is .4.
Now, 1 - .4 = .6 should be divided over the values 3 and 5. The sum of 3 and 5 is 8. So, divide each uncorrected original value by 8 and multiply it by .6: e.g. 3 / 8 * .6.
That will give you your normalized list of values: [.2 .2 .225 .375].
Obviously you can't scale the terms in the usual sense of the word, as you may end up with times less than your minimum time. Also you will have trouble if the number of posts * minimum time exceeds your total time available.
But assuming not...I would suggest giving each post the minimum time plus extra time proportional to the number of extra words in the longer documents. So, given the values [1 1 3 5] and the requirement that the minimum is 0.2...
1) Subtract the minimum value from each value to give [0 0 2 4]
2) Normalize this to give [0 0 0.333 0.667]
3) Scale by (1 - 4*0.2) (that is 1 - number_of_values*min_time) to give [0 0 0.0667 0.133333]
4) Finally add the minimum to each value to give [0.2 0.2 0.267 0.333]
Now each post gets a base time plus an extra time proportional to its extra length.

Need an explanation of this javascript

I have a question about this script I found and used. It works but I don't get why. The exercise was to make a list with random numbers from -50 to 50. The function below uses Math.floor(Math.random() * (the part i dont understand).
If I put this calculation on google I got as answer 151 and Math.random()*151 does not do from -50 to 50.
Can someone give me a clear explanation about this function below because I am sure that I am missing something.
this script works but I only want a clear explanation how
for (i = 0; i <= 100; i++)
{
Rnumber[i] = randomFromTo(-50,50);
}
function randomFromTo(from, to)
{
return Math.floor(Math.random() * (to - from + 1) + from);
}
to - from + 1 = 50 - (-50) + 1 = 101
Math.random() * 101 = number in range [0,101[
Math.floor([0,101[) = integer in range [0,100]
[0,100] + from = [0,100] + (-50) = integer in range [-50,50]
Which is exactly what is asked for.
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/random
Math.random returns a floating-point, pseudo-random number in the
range [0, 1) that is, from 0 (inclusive) up to but not including 1
(exclusive), which you can then scale to your desired range.
which when multiplied with a number > 1 and floored gives you an integer
Math.random() - get only value between 0 and 1.
Math.floor( number ) get integer down rounded value from number.
You should:
function randomFromTo(from, to)
{
// you can use doubled bitwise NOT operator which also as Math.floor get integer value from number but is much faster.
// ~1 == -2 , ~-2 == 1 and ~1.5 == -2 :)
return ~~( --from + ( Math.random() * ( ++to - from )) )
}

javascript: calculate x% of a number

I am wondering how in javascript if i was given a number (say 10000) and then was given a percentage (say 35.8%)
how would I work out how much that is (eg 3580)
var result = (35.8 / 100) * 10000;
(Thank you jball for this change of order of operations. I didn't consider it).
This is what I would do:
// num is your number
// amount is your percentage
function per(num, amount){
return num*amount/100;
}
...
<html goes here>
...
alert(per(10000, 35.8));
Your percentage divided by 100 (to get the percentage between 0 and 1) times by the number
35.8/100*10000
Best thing is to memorize balance equation in natural way.
Amount / Whole = Percentage / 100
usually You have one variable missing, in this case it is Amount
Amount / 10000 = 35.8 / 100
then you have high school math (proportion) to multiple outer from both sides and inner from both sides.
Amount * 100 = 358 000
Amount = 3580
It works the same in all languages and on paper. JavaScript is no exception.
I use two very useful JS functions:
http://blog.bassta.bg/2013/05/rangetopercent-and-percenttorange/
function rangeToPercent(number, min, max){
return ((number - min) / (max - min));
}
and
function percentToRange(percent, min, max) {
return((max - min) * percent + min);
}
If you want to pass the % as part of your function you should use the following alternative:
<script>
function fpercentStr(quantity, percentString)
{
var percent = new Number(percentString.replace("%", ""));
return fpercent(quantity, percent);
}
function fpercent(quantity, percent)
{
return quantity * percent / 100;
}
document.write("test 1: " + fpercent(10000, 35.873))
document.write("test 2: " + fpercentStr(10000, "35.873%"))
</script>
In order to fully avoid floating point issues, the amount whose percent is being calculated and the percent itself need to be converted to integers. Here's how I resolved this:
function calculatePercent(amount, percent) {
const amountDecimals = getNumberOfDecimals(amount);
const percentDecimals = getNumberOfDecimals(percent);
const amountAsInteger = Math.round(amount + `e${amountDecimals}`);
const percentAsInteger = Math.round(percent + `e${percentDecimals}`);
const precisionCorrection = `e-${amountDecimals + percentDecimals + 2}`; // add 2 to scale by an additional 100 since the percentage supplied is 100x the actual multiple (e.g. 35.8% is passed as 35.8, but as a proper multiple is 0.358)
return Number((amountAsInteger * percentAsInteger) + precisionCorrection);
}
function getNumberOfDecimals(number) {
const decimals = parseFloat(number).toString().split('.')[1];
if (decimals) {
return decimals.length;
}
return 0;
}
calculatePercent(20.05, 10); // 2.005
As you can see, I:
Count the number of decimals in both the amount and the percent
Convert both amount and percent to integers using exponential notation
Calculate the exponential notation needed to determine the proper end value
Calculate the end value
The usage of exponential notation was inspired by Jack Moore's blog post. I'm sure my syntax could be shorter, but I wanted to be as explicit as possible in my usage of variable names and explaining each step.
It may be a bit pedantic / redundant with its numeric casting, but here's a safe function to calculate percentage of a given number:
function getPerc(num, percent) {
return Number(num) - ((Number(percent) / 100) * Number(num));
}
// Usage: getPerc(10000, 25);
var number = 10000;
var result = .358 * number;
Harder Way (learning purpose) :
var number = 150
var percent= 10
var result = 0
for (var index = 0; index < number; index++) {
const calculate = index / number * 100
if (calculate == percent) result += index
}
return result

Categories