Andrey Frolov

Posted on

# Why rounding is more important than you think

About the author and these notes

In the last three years, I've worked in fintech companies, and I often see that JavaScript developers attempt to use just numbers or strings (i.e. https://github.com/MikeMcl/bignumber.js) to operate with monetary values. I think this is the wrong way, and strings or numbers are not well suited for financial applications. But someone might probably ask why? In a series of articles, I'm gonna try to describe why.

Introduction

Rounding is a very simple but deep question. What does js bring us when we talk about math operations? Yes, we have a Math object, that brings us methods to round values.

``````Math.round(5.5)
// => 6

Math.trunc(5.5)
// => 5

Math.floor(5.5)
// => 5

Math.ceil(5.5)
// => 6

``````

But what's the problem? Well, when we talk about rounding, we talk about transforming some quantity from a greater precision to a lesser precision.

For example, you have a function that ends up with a dollar value like \$7.112312, so, the rounding value with cents will be \$7.11, which is less precise.

So, when we round, we want our algorithm to minimize the effect of lost values (for example, our cents).

You can say: "It doesn't matter at all, it's just 1 cent. Are you kidding me?"; Well, for those who aren't interested in details, just check https://github.com/frolovdev/easymoney.

For others, who interested, not really, give me a sec

How Much Impact Can Rounding Have?

Suppose you are an investor - a really big investor, in other words, a hedge fund manager and you decide to invest your \$200 buying some stocks. In our model, stock price depends only on one principle: The more people there are who want to buy a stock, the more value (price) the stock has and vice versa.

Moving on, we'll pretend that the price of the stock fluctuates every second with random values between -0.05 and +0.05 values (you buy and sell).

Let's consider a little bit of code. You don't want to think about rounding and decide to chop everything off after the third decimal place. In math, this is called truncating or rounding toward zero, but Math.trunc in js doesn't give us the opportunity to decide how many decimals we want, so I created a handmade implementation of it with 3 decimals.

``````
function truncate(n) {
return parseInt(n * 1000, 10) / 1000;
}

``````

Just shifting the decimal point by three places to the right when multiplying by 1000, we get an integer part for the result. And shift the decimal point three places left when dividing by 1000.

Next step, bring in our two experimental values, one to keep track of the actual value of your stocks after the simulation is complete, and the other for the value of your stocks after you’ve truncated to three decimal places at each step.

``````let actualValue = 200;
let truncatedValue = 200;
``````

Now, set up our model and take 100000 seconds. That's about 1.5 days. For each second, generate a random value between -0.05 and 0.05 and then update the actual value and the truncated value.

``````
function truncate(n) {
return parseInt(n * 1000, 10) / 1000;
}

let actualValue = 200;
let truncatedValue = 200;

// just generate random value between -0.05 and +0.05
function generateNumber() {
return Number((Math.random() * (0.05 + 0.05) - 0.05).toFixed(10));
}

//  make 1000000 operations in for loop
for (let i = 0; i < 1000000; i++) {
// rand fluctuation
const randFluctuation = generateNumber();

// real value after buy or sell
actualValue = actualValue + randFluctuation;

// truncated value
truncatedValue = truncate(truncatedValue + randFluctuation);
}

// log resulted values
console.log(actualValue);
console.log(truncatedValue);

``````

Here's my example (you can run the simulation in your browser and get your own results).

Working with real values, you earn 13 dollars, but with truncation you are bankrupt!

Let's re-run the simulation but with math rounding.

``````
// round implementation

(function() {

function decimalAdjust(type, value, exp) {
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;

if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}

value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));

value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
}

if (!Math.round10) {
Math.round10 = function(value, exp) {
return decimalAdjust('round', value, exp);
};
}

if (!Math.floor10) {
Math.floor10 = function(value, exp) {
return decimalAdjust('floor', value, exp);
};
}

if (!Math.ceil10) {
Math.ceil10 = function(value, exp) {
return decimalAdjust('ceil', value, exp);
};
}
})();

let actualValue = 200;
let truncatedValue = 200;

function generateNumber() {
return Number((Math.random() * (0.05 + 0.05) - 0.05).toFixed(10));
}

for (let i = 0; i < 1000000; i++) {
const randFluctuation = generateNumber();
actualValue = actualValue + randFluctuation;

truncatedValue = Math.round10(truncatedValue + randFluctuation, -4);
}

console.log(actualValue);
console.log(truncatedValue);

``````

Now things are getting better, but we lose cents. And it also might be a problem. We describe how to deal with this problem in the subsequent chapters.

You can also read about Vancouver Stock Exchange which truncated the overall index value to three decimal places instead of rounding. Rounding errors have even resulted in the loss of life: http://www-users.math.umn.edu/~arnold/disasters/patriot.html

The bottom line is rounding is important, and developers have the responsibility to know what the common issues are and how to deal with them. In the next chapter, we try to deep dive into different rounding algorithms and figure out the differences between them.

Feel free to ask questions, to express any opinion and discuss from your point of view. Share, subscribe and make code, not war. ❤️