Javascript toFixed behaviour with substraction - javascript

I'm manipulating a lot of numbers in my application. For this particular case, here is what I do : I retrieve two lists of numbers, I do an average for each of these list, then I substract the two average. To avoid average like 3.333333333333 I use .toFixed(3) on my results.
Here is what it looks like :
// I found this function somewhere on Stackoverflow
Array.prototype.average = function() {
if(this.length == 0){
return 0;
}
else{
return this.reduce(function (p, c) {
return p + c;
}) / this.length;
}
};
sumHigh = [ 10.965, 10.889, 10.659, 10.69, 10.599 ]
sumLow = [ 4.807, 3.065, 2.668, 2.906, 3.606, 4.074, 4.153 ]
// Ok normal
console.log(sumHigh.average()) // 10.760399999999999
console.log(sumLow.average()) // 3.6112857142857138
// Ok normal
console.log(sumHigh.average().toFixed(3)) // "10.760" Does the ".." has anything to do with my problem ?
console.log(sumLow.average().toFixed(3)) // "3.611"
// So here I have my two average values with no more than 3 numbers after the comma but it is not taken into account when substracting these two numbers...
// Not Ok, why 10.760 - 3.611 = 7.148999999999999 ?
console.log(sumHigh.average().toFixed(3) - sumLow.average().toFixed(3)) // 7.148999999999999
console.log(parseFloat(sumHigh.average().toFixed(3)) - parseFloat(sumLow.average().toFixed(3))) // 7.148999999999999
// Just as an example, this is working
console.log(parseFloat(sumHigh.average().toFixed(3)) + parseFloat(sumLow.average().toFixed(3))) // 14.371
console.log(parseFloat(sumHigh.average()) + parseFloat(sumLow.average())) // 14.371685714285713
Can someone explain this behaviour?
Why substraction is not working while addition is?
Ok I know I can solve my problem with :
console.log((sumHigh.average() - sumLow.average()).toFixed(3)) // "7.149"
But that doesn't explain this behaviour.
Thanks

Related

Weird array output TS/Angular

I'm trying to sort data from API for chart, so I need to filter it based on currency name so I can get their rates.
Algo is working properly but I'm getting weird output in my browser and chart also.
Output from tempRates in browser looks like this: [7.4429, 7.4429, 7.4392, 7.4392] which is the correct output.
But when I expand the same array in the browser console I get this output:
(4)[7.4429, 7.4429, 7.4392, 7.4392]
0: 1
1: 1
2: 1
3: 1
And that goes for all arrays.
But value of last array is (4)[1, 1, 1, 1] and that is the expected output.
Object.keys(data.rates).forEach((key) => {
this.data.labels.push(key); // date
});
Object.values(data.rates).forEach((value) => {
tempCurrency = Object.keys(value)[i];
Object.values(data.rates).forEach((rate) => {
if (tempCurrency === Object.keys(rate)[i]) {
tempRates.push(rate[tempCurrency]);
//tempRates.push(Math.round(Math.random() * 100));
}
});
console.log("log before push", tempRates);
this.data.datasets.push({
label: tempCurrency,
data: tempRates,
});
i++;
if (!(i === Object.keys(data.rates).length)) {
tempRates.length = 0;
}
});
I also have tried with random numbers and the output still has the same problem. All arrays have the value of last array.
Console screen shoot
All I had to do was change the tempRates.length = 0; to tampRates = [].
I just don't know what is the difference between those two solutions. I would be grateful if someone could explain it to me.

Practicing arrays and loops in JS and can't get my int -> binary project to work

I know there's like 8 line solutions to convert integers to binary, but I'm trying my hand at creating a little program that does the same thing with arrays. I'm using 3 arrays, the first stores the original number to be converted and all the values of dividing that number by 2 and then rounding down when there's a remainder, aka [122, 61, 61, 30.5, 30, 15.5, 15, etc. etc.], and the second array will store the binary digits based on if remainder of division is true || false, aka [0,1,0,1,1,1,1]. Not yet written, but at the end I'll take the binaryArray and reverse and toString it to get 1111010, the correct binary for 211.
Obviously I'm doing more than one thing wrong, but I'm getting close as I can see the correct initial results when I console.log the main function. Any help here is appreciated, except I don't need to know how to do this entire concept easier, as I've already studied the easier solutions and am attempting this on purpose for practice.
Run the code snippet to see that the output is close to the solution, but obviously lastNumArray is not being updated and the entire thing is not looping.
let num = 122;
let array = [num];
let binaryArray = [];
let lastNumArray = array[array.length - 1];
function convertToBinary(number) {
let lastNumArray = number;
var result = (number - Math.floor(number)) !== 0;
if (result) {
binaryArray.push('1') &&
array.push(lastNumArray / 2) &&
array.push(Math.floor(array[array.length - 1]))
} else {
binaryArray.push('0') &&
array.push(lastNumArray / 2) && //this pushes 61
array.push(Math.floor(array[array.length - 1]))
}
}
while (array > 1) {
convertToBinary(lastNumArray) &&
lastNumArray.push(array[array.length - 1])
}
console.log(array)
console.log(binaryArray)
console.log(lastNumArray)
Any help or pointers here would be much appreciated. I've tried for loops, do while loops, and more and I'm getting close, but I really want to find out what I'm doing wrong here so I can learn. This is the first little function I've tried to write solo, and it's definitely keeping me humble!
You could try something like that with recursive call
let results = []
let binary = []
function cb(number) {
if (number === 0) return
results.push(number)
results.push(number / 2)
results.push(Math.floor(number / 2))
const last_index = results.length - 1
if (results[last_index] === results[last_index - 1]) {
binary.push("0")
} else {
binary.push("1")
}
cb(results[last_index])
}
cb(122)
console.log(results)
console.log(binary)
console.log(binary.reverse().join(""))

Why does using < and > make no difference to the sort function?

I'm trying to understand how .sort works. And so far, the compare function and returns make zero sense.
Here's an array of names in the form Last Name, First Name.
const people = ['Beck, Glenn', 'Becker, Carl', 'Beckett, Samuel', 'Beddoes, Mick', 'Beecher, Henry', 'Beethoven, Ludwig', 'Begin, Menachem', 'Belloc, Hilaire', 'Bellow, Saul', 'Benchley, Robert', 'Benenson, Peter', 'Ben-Gurion, David', 'Benjamin, Walter', 'Benn, Tony', 'Bennington, Chester', 'Benson, Leana', 'Bent, Silas', 'Bentsen, Lloyd', 'Berger, Ric', 'Bergman, Ingmar', 'Berio, Luciano', 'Berle, Milton', 'Berlin, Irving', 'Berne, Eric', 'Bernhard, Sandra', 'Berra, Yogi', 'Berry, Halle', 'Berry, Wendell', 'Bethea, Erin', 'Bevan, Aneurin', 'Bevel, Ken', 'Biden, Joseph', 'Bierce, Ambrose', 'Biko, Steve', 'Billings, Josh', 'Biondo, Frank', 'Birrell, Augustine', 'Black, Elk', 'Blair, Robert', 'Blair, Tony', 'Blake, William'];
I want to arrange them by last name, low to high.
Here's my code (I'm following this tutorial):
const alpha = people.sort((lastOne, nextOne) => {
const [aFirst, aLast] = lastOne.split(', ');
const [bFirst, bLast] = lastOne.split(', ');
return aLast < bLast ? 1 : -1;
});
This obviously works because it's from the tutorial. But I don't understand why it works.
First, these two lines make zero difference in the output - why is that:
return aLast < bLast ? 1 : -1;
return aLast > bLast ? 1 : -1;
Second, from the MDN pages, it says returning a value less than zero will put the first value before the second value. Returning a value greater than zero will put the second value before the first value.
With those two things, I wrote it this way:
return aLast < bLast ? -1 : 1: If a is smaller than b, put a first, b second (which would be ascending right?). Is this not correct?
But in the tutorial, it's written this way:
return aLast < bLast ? 1 : -1;: Which I read as - If a is smaller than b, return 1, which would put b before a (which would be descending right). But this is not what happens in the result. It still arranges from low to high (Or Beck to Blake).
You are using lastOne for both values (instead of nextOne). So
const [aFirst, aLast] = lastOne.split(', ');
const [bFirst, bLast] = lastOne.split(', ');
should probably be
const [aFirst, aLast] = lastOne.split(', ');
const [bFirst, bLast] = nextOne.split(', ');

Javascript circular calculation involving arrays (example: amortization schedule)

I'm fairly early on in building an app that can be used as an alternative to spreadsheets in a number of scenarios. It offers the user a non-tabular approach to creating and organizing the analysis. The user's instructions are transpiled to and executed by Javascript (using pure functions only). (The interface is between something like Google Blockly and straight coding in a text editor.) In place of cell ranges that you would typically use in a spreadsheet, this product uses Javascript arrays. A problem that I'm facing with this approach is (quasi-)circular calculations such as that found in a simple amortization schedule.
My (unsuccessful) attempts to resolve the issue so far involve:
lazy list evaluation (using https://github.com/dankogai/js-list-lazy)
wrapping all transpiled JS in functions to delay eval so that the user's content doesn't need to be topologically sorted (which it currently is).
Below I'll provide hopefully enough context to illustrate the issue, which can be pretty broadly extrapolated. Take the example of an amortization schedule for a mortgage:
Basically, the BOP ("Beginning of Period") Balance depends on the EOP ("End of Period") Balance from the previous period. And the EOP Balance depends on the BOP Balance from the same period. In a spreadsheet that uses ranges of contiguous cells, this isn't circular because every BOP Balance and EOP Balance is a discrete cell. However, if BOP Balance and EOP Balance (and all of the other time-series-based calcs) are arrays then there is a circular reference when trying to retrieve elements. Spreadsheet screenshots of the example are provided at the end of the post as a supplement.
An attempt to build this analysis in my app generates the following JS (which I've edited and reorganized for clarity). This code, if translated over to a spreadsheet, works just fine (see supplemental screenshots at the end of the post):
// Credit: Apache OpenOffice
function payment (rate, periods, present_value, future_value, type) {
var fv = future_value || 0
var t = type || 0
if (rate === 0) {
return -1 * ((present_value + fv) / periods)
} else {
var term = (1 + rate) ** periods // Transpiling with Babel; otherwise, use Math.pow(1 + rate, periods)
if (t === 1) {
return -1 * ((fv * rate / (term - 1) + present_value * rate / (1 - 1 / term)) / (1 + rate))
} else {
return -1 * (fv * rate / (term - 1) + present_value * rate / (1 - 1 / term))
}
}
}
var loan_principal = 1000000
var annual_interest_rate = 0.06
var interest_rate_monthly = annual_interest_rate / 12
var amortization_period_years = 25
var amortization_period_months = amortization_period_years * 12
var months_range = _.range(1, amortization_period_months + 1, 1) // See: http://underscorejs.org/#range [1, 2, 3, ... 298, 299, 300]
var bop_balance = months_range.map(function (current_element, current_list_position) {
if (current_list_position === 0) {
return loan_principal
} else {
// Along with eop_balance, this causes a graph cycle
return eop_balance[current_list_position - 1]
}
})
var monthly_payment = months_range.map(function (current_element, current_list_position) {
return payment(interest_rate_monthly, amortization_period_months, loan_principal)
})
var principal_payment = months_range.map(function (current_element, current_list_position) {
var current_mthly_pmt = monthly_payment[current_list_position]
var current_int_pmt = interest_payment[current_list_position]
return current_mthly_pmt - current_int_pmt
})
var interest_payment = months_range.map(function (current_element, current_list_position) {
if (current_list_position === 0) {
return loan_principal * interest_rate_monthly * -1
} else {
var previous_bal = eop_balance[current_list_position - 1]
return previous_bal * interest_rate_monthly * -1
}
})
var eop_balance = months_range.map(function (current_element, current_list_position) {
// This causes a graph cycle
var cur_bal = bop_balance[current_list_position]
var cur_prin_pmt = principal_payment[current_list_position]
return cur_bal + cur_prin_pmt
})
This code will not topologically sort because of the cycle between bop_balance and eop_balance. And it won't fully evaluate because of the circular reference.
Any suggestions on how to work around this general scenario? Thank you.
Supplemental Info:
Here are two views of the same spreadsheet representing the analysis:
The reliance on pure functions in the app is to try and minimize confusion for users coming from spreadsheets.
If seeing the actual app would help provide context, please feel free to visit https://www.getpinevale.com. I'm placing this at the end so it doesn't distract from the question.
You shouldn't do a double linked structure. I would suggest use a plain for loop and build up both arrays.
for(int i=0; i<range.length;i++){
if(i==0){
bop_balance[i]= loan_principal;
eop_balance[i]= somecalculatedamount(loan_principal);
}else{
bop_balance[i] = eop_balance[i-1];
eop_balance[i] = somecalculatedamount(bop_balance[i])
}
}
I don't know if my function is correct but the essential point I am trying to make:
don't link the data structures
and use a control structure that is outside of both arrays
Based on the comment below I'd make the suggestion of using reduce.
let range = [1,2,3,4];
let output = range.reduce(function(accumulator, currentValue, currentIndex) {
let balance = {};
if (currentIndex == 0) {
balance.bop = 0;
balance.eop = currentValue;
}else{
balance.bop = accumulator[currentIndex-1].eop;
balance.eop = balance.bop+5;
}
accumulator.push(balance);
return accumulator;
}, []);
console.log(output);

JavaScript Sorting Array of Countries

I am sorting an array of countries, and each have a data-weight attribute which I use in my custom sort function.
At this point, a_weight and b_weight are either 0 or 1 (integer). 'United States of America' is the only element with a data-weight of 1, every other element has a data-weight of 0.
...
return results.sort(sortOnWeight);
}
function sortOnWeight(a,b) {
a_weight = parseInt($(a['element'][0]['attributes']['data-weight']).val(), 10);
b_weight = parseInt($(b['element'][0]['attributes']['data-weight']).val(), 10);
if (a_weight > b_weight){
return -1;
} else if (a_weight < b_weight) {
return 1;
} else {
return 0;
}
}
The array comes back correct EXCEPT for the last item that hits the 'sortOnWeight' function, which is returned out of order.
For example...result returns as:
United States of America
Mozambique
Aruba
Australia
Austria
Belarus
Anybody see a reason why this could be happening?
It looks like you're banking on the browser sorting algorithm being stable. This is not always the case (see this issue for V8 as an example).
EDIT: More information about V8's sorting here. Apparently, arrays with length <= 10 use a stable sort, while length >= 11 uses a faster unstable sort.
You should sort by text if the weights are equal. String comparison can be done with the > and < operators (MDN reference). The comparison is case-sensitive, with uppercase sorting above lowercase ("abc" > "Abc"), so you may need to use toLowerCase/toUpperCase to fudge your results.
function sortOnWeight(a,b) {
var a_weight = parseInt($(a['element'][0]['attributes']['data-weight']).val(), 10);
var b_weight = parseInt($(b['element'][0]['attributes']['data-weight']).val(), 10);
var a_text;
var b_text;
if (a_weight > b_weight){
return -1;
}
else if (a_weight < b_weight) {
return 1;
}
else {
a_text = /* get text from a */
b_text = /* get text from b */
return a_text > b_text;
}
}
From Mozilla documentation sort is considered unstable:
The sort is not necessarily stable. The default sort order is lexicographic (not numeric).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
This is also mentioned in ES documentation:
The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their original order).
http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.11
I just wanted to try it for myself, so created a new 2D array with country names and weights and tried to sort based on weight. It worked to my satisfaction, with US showing up at the top, see the code below which might help you. Of course, this is oversimplified and the links provided by others have a lot more good information and may be necessary depending upon your situation.
<!DOCTYPE html> <html> <body>
<script type = "text/javascript"> var countries = [["Aruba", 0],["Australia", 0],["Austria", 0],["Belarus", 0],["United States of America", 1], ["Mozambique", 0]];
var list = "unsorted :"; for (var i=0;i<countries.length;i++) { list +=countries[i]; list+= " "; }
alert('unsorted array:' + countries);
countries =countries.sort(function(a,b){ return (a[1] > b[1] ? -1 : (a[1] < b[1] ? 1 : 0)); });
list = "sorted :"; for (var i=0;i<countries.length;i++) { list +=countries[i]; list+= " "; }
alert('sorted array:' + countries);
</script>
</body> </html>

Categories

Resources