🙊 Change floating-point to 2 decimal places?

Before I go down this rabbit hole further. If you could share your wisdom/experience.

I’m assuming with certain large floating-point numbers is a known problem and CAN NOT be converted to 2 decimal places. As these large numbers appear to be an error, it is the way things work with my current findings as we discuss below.

StackOverflow

Tearing through page after page related to big floating-point numbers devs are trying to find ways of converting. I’d be another duplicate that is why we are discussing on discourse.threejs.org have you come across these big floating-point numbers? What did you do to convert to 2 decimal places?

Dev(s) speak of many ways in trying to do the same thing and yet these numbers refuse to change = typeof number: 0.9299999999999999

Reference: Round to at most 2 decimal places (only if necessary) = none of these worked for my situation

:speak_no_evil: What is really strange is when I convert 0.93 from a string back into 0.93 the computer wants to convert to 0.9299999999999999

ry += Number(((parseFloat(rotateY, 10)).toFixed(2)));

ry += Math.round((rotateY * 100 ))/100;

The 2 pieces of code that fit into line 10 are trying to do the same thing, this is more commonly what you’ll see devs doing because you need to have an understanding that toFixed(2) will produce a string.

ry += Math.round((rotateY * 100 ))/100;

Devs do go on to discuss using:

Input

01 var trigger = setInterval(function() {
02   cnt++;
03
04   var rotateY = THREE.Math.degToRad(18);
05
06   if (cnt < 4) {
07     var works = (ry + parseFloat(rotateY, 10)).toFixed(2);
08     console.log('WORKING AS STRING:', works);
09     console.log(typeof works);
10     ry += Math.round((rotateY * 100 ).toFixed(2))/100;
11     console.log('FIXED AS NUMBER:', ry);
12     console.log(typeof ry);
13   }
14   if (cnt === 11) {
15     clearInterval(trigger);
16   }
17
18   mesh.rotation.y = ry;
19
20   render();
21
22 }, 1000);

Output

THREE.WebGLRenderer 92

WORKING AS STRING: 0.31
string
FIXED AS NUMBER: 0.31
number
WORKING AS STRING: 0.62
string
FIXED AS NUMBER: 0.62
number
WORKING AS STRING: 0.93
string
FIXED AS NUMBER: 0.9299999999999999
number

In depth talk on Mantissa/Significand:
Everything you never wanted to know about JavaScript numbers

Bartek Szopka: Only says these numbers exist, no fix, quite interesting in knowing what is happening under the hood

Reminds me of a problem I was banging my head against earlier this year:

(0.1 + 0.2 === 0.3) is false, so I had to use 2 comparisons as a dirty fix: x > 2.9 && x < 3.1

2 Likes

Awesome for this tip, yes I could check if that number kicks in to manually load a rounded floating point.
The ah-ha moment just kicked in! Is it ideal no, for now the condition will get the job done!

I know of the culprit problem as well (0.1 + 0.2 === 0.3) is false
Wondering if we could create a compiled list of these Javascript Gotcha(s) for the community!

Thanks for sharing @marquizzo!

Just in addition to the reply from @marquizzo.
When I was answering this SO question, I bumped into something similar. So I solved it with the conception of “tolerance”:

THREE.Vector3.prototype.equals = function(v, tolerance) {
  if (tolerance === undefined) {
    return ((v.x === this.x) && (v.y === this.y) && (v.z === this.z));
  } else {
    return ((Math.abs(v.x - this.x) < tolerance) && (Math.abs(v.y - this.y) < tolerance) && (Math.abs(v.z - this.z) < tolerance));
  }
}
2 Likes

Another great response thanks @prisoner849!

When calculating with floating points, inaccuracies are inevitable which makes comparison operations via === not robust. Using a “tolerance” or “epsilon” value like mentioned by @prisoner849 is the right approach. But there is one obvious problem: What value should you use for tolerance? You often see hard coded values like Number.EPSILON or something like 0.00001. The problem is that these values might be inappropriate for data sets with very large or very small values. Because of this, you normally scale the tolerance value based on all input data you are going to process. For instance the QuickHull implementation of three.js which computes the convex hull for a given set of points uses this approach.

2 Likes

Another great response thanks @Mugen87!
Have some more playing around to do

It seems like Math.round() is a better solution, but it is not! In some cases it will NOT round correctly. Also, toFixed() will NOT round correctly in some cases.

To correct the rounding problem with the previous Math.round() and toFixed(), you can define a custom JavaScript round function that performs a “nearly equal” test to determine whether a fractional value is sufficiently close to a midpoint value to be subject to midpoint rounding. The following function return the value of the given number rounded to the nearest integer accurately.

Number.prototype.roundTo = function(decimal) {
  return +(Math.round(this + "e+" + decimal)  + "e-" + decimal);
}

var num = 9.7654;
console.log( num.roundTo(2)); //output 9.77
1 Like