MozillaZine


JavaScript Question: Number formatting

Discuss how to use and promote Web standards with the Mozilla Gecko engine.
Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted June 20th, 2022, 2:58 am

Hi all

Question: how do you display a decimal numbers to a specified number of decimal places without rounding?

See after running the calculations what I get back is something like this

X: -2.9513513513513514 km
Y: -4.173513513513513 km
Z: -16.323243243243244 km

Obviously this is not desirable so I’d like to truncate these numbers to 2 decimal places so instead of the output above I get

X: -2.95 km
Y: -4.17 km
Z: -16.32 km

The problem that I’m running into is that I can’t find a way to truncate decimal numbers to a specified number of decimal places without rounding.

.trunc(); doesn’t work as it doesn’t provide a optional parameter to specify at what point you want to start dropping digits, it just drops all digits after the decimal point obviously this is only useful when the number is small enough that I’m displaying in meters instead of kilometres as I don’t need the numbers after the decimal point in that case.

.toFixed(2) doesn’t work either as that rounds meaning the displayed coordinates are wrong

.toPrecision(3) doesn’t work either for the same reason it appears to round thus resulting in incorrect coordinates being displayed

for example

X: -2.747027027027027 km displays as X: -2.75 km

When it should display as X: -2.74 km

I’m aware of the fact that due to the way computers work not all fractional numbers can be accurately represented and this is a problem in all languages not just JavaScript thus I’d like to avoid the whole issue by simply not rounding at all but it seems that what should be a stupidly easy task is actually devilishly tricky, and unfortunately a lot of what I have found online while looking for a solution to this is unnecessarily complicated and I don’t understand it.

in any event here is what I have so far

Code: Select all
var x_coordinate = icons[target]['x'] / 222 / 1000;
var y_coordinate = icons[target]['y'] / 222 / 1000;
var z_coordinate = icons[target]['z'] / 222 / 1000;

if(x_coordinate > 0 && x_coordinate < 1 || x_coordinate < 0 && x_coordinate > -1 || x_coordinate == 0)
{
  var x_coordinate = x_coordinate * 1000;
  table.rows[0].cells[2].innerHTML = Math.trunc(x_coordinate) + " m";
}

else
{
  table.rows[0].cells[2].innerHTML = x_coordinate + " km";
}

if(y_coordinate > 0 && y_coordinate < 1 || y_coordinate < 0 && y_coordinate > -1 || y_coordinate == 0)
{
  var y_coordinate = y_coordinate * 1000;
  table.rows[1].cells[2].innerHTML = Math.trunc(y_coordinate) + " m";
}

else
{
  table.rows[1].cells[2].innerHTML = y_coordinate + " km";
}

if(z_coordinate > 0 && z_coordinate < 1 || z_coordinate < 0 && z_coordinate > -1 || z_coordinate == 0)
{
  var z_coordinate = z_coordinate * 1000;
  table.rows[2].cells[2].innerHTML = Math.trunc(z_coordinate) + " m";
}

else
{
  table.rows[2].cells[2].innerHTML = z_coordinate + " km";
}


For the most part this works apart from the fact that I can’t figures out how to truncate decimal numbers to 2 decimal places without rounding.

morat
 
Posts: 5300
Joined: February 3rd, 2009, 6:29 pm

Post Posted June 20th, 2022, 5:13 am

Truncate (not round off) decimal numbers in javascript
http://stackoverflow.com/questions/4912788

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted June 22nd, 2022, 3:51 am

Hi

I had a read though the post you linked to but to be honest I’m not sure I really understand what they are actually doing, because my knowledge of JavaScript is still pretty basic and math never was my strong suite.

It also doesn’t really help that there is very little in the way of explanation or comments so that when someone like me comes along we can figurer out how to actually use the posted answer take the first answer as an example.

Code: Select all
truncateDecimals = function (number, digits)
{
  var multiplier = Math.pow(10, digits),
  adjustedNum = number * multiplier,
  truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);

  return truncatedNum / multiplier;
};


Presumable I couldn’t just copy and paste that answer I’d need to adjust it for my use but looking at that its not at all clear to me what I’d need to change, to be honest its not even clears to me what exactly this is even doing.

Again I have the same issue with the second answer a bit further down

Code: Select all
function truncateDecimals (num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    return parseFloat(finalResult);
}


from what I can gather it appear as though this one is converting the number to a string and then just trimming the end of it but again I don't really understand what I'm looking at.

Unfortunately this sort of a thing is of no use to me if I don't understand the answer and therefore I'm unable to make use of it.

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted June 22nd, 2022, 10:37 am

Ok so after repeatedly trying and failing to fix this irritating problem, it got me wondering if there might be another way to attack this problem.

so my first thought was to see if there was a way to stop .trunc() from deleting all the values after the decimal point and as it turn out by shifting the decimal point 2 places by multiplying by 100 and then shifting it back after deleting the remaining digits by dividing by 100 like so

Code: Select all
x_coordinate = Math.trunc(x_coordinate * 100) / 100;
y_coordinate = Math.trunc(y_coordinate * 100) / 100;
z_coordinate = Math.trunc(z_coordinate * 100) / 100;


I was able to get close to the effect I'm trying to achieve however then I ran in to another problem upon testing to see if everything was working as expected I found that if I had a number like

11.96054054054054 it was displaying as 11.96

while this approach seems to address the rounding issue I get with .toPrecision(3) which would display 11.96054054054054 as 12.0 which is clearly wrong I'm not seeing the same behaviour that I get when using .toPrecision(3);

aka 11.96054054054054 should display as 11.9 not 11.96

After looking around I found a post on stackoverflow that suggested using string manipulation

Code: Select all
number = number.toString();
number = number.slice(0, (number.indexOf("."))+3);


unfortunately this also fails for the same reason as my attempt using .trunc(); while it gets round the rounding problem the behaviour is not the same as toPrecision(3); therefore numbers like

11.96054054054054 displays as 11.96 instead of 11.9

so how then can I get the same behaviour I see when using .toPrecision(3); without rounding ?

if only toPrecision(3); would allow me to specify an optional parameter to turn off rounding, who knew such a simple and common task could be so bloody difficult.

Why isn't there a simple way to display numbers to a specified number of decimal places without rounding ? when clearly this is a common task at least as far as displaying numbers go anyway.

Talk about frustrating no matter what I try I just can’t get this to work correctly :furious: grrrr

morat
 
Posts: 5300
Joined: February 3rd, 2009, 6:29 pm

Post Posted June 23rd, 2022, 6:43 am

The truncateDecimals function works for me.

Code: Select all
(function () {
  function truncateDecimals(num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;
    return parseFloat(finalResult);
  }
  console.log(truncateDecimals(    1.234            , 2)); //    1.23
  console.log(truncateDecimals(  456.789            , 2)); //  456.78
  console.log(truncateDecimals( -456.789            , 2)); // -456.78
  console.log(truncateDecimals(   11.96054054054054 , 2)); //   11.96
})();

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted June 25th, 2022, 4:29 pm

Perhaps so but unfortunately I’m at my limit for what I currently know how to do as this isn’t something I was ever taught so as I already said I don’t understand what I’m looking at which therefore makes it difficult to actually make use of it.

I know enough that I can do some simple stuff, I understand the basics well enough, but without some additional explanation / help text / comments etc to help me make sense of the answer it’s of little use to me, this is why I don’t post on places like stackoverflow because while they may give some good answers it’s not really the best place for beginners since the folk over there tend to assume a certain level of knowledge which means that often times the answers and aimed at those who are for more proficient at this then I am, at best my proficiency with scripting is about high school level, though I’m getting better I still need a fair amount of help particularly when attempting to tackle something like this that I haven't done before.

jscher2000

User avatar
 
Posts: 11670
Joined: December 19th, 2004, 12:26 am
Location: Silicon Valley, CA USA

Post Posted June 27th, 2022, 10:47 am

I think it's worth looking at what each step of this function does to see whether it sounds sensible for your project.

Code: Select all
(function () {
  function truncateDecimals(num, digits) {

The function takes the full number and the number of digits you want to the right of the decimal place.

The function then combines a number of individual variable assignments into a single var statement. Let's separate them into individual lines for simplicity of understanding:

Code: Select all
    var numS = num.toString();

The variable numS holds the number provided by the calling code converted to a string. This is preparatory to using string parsing methods on the number. No rounding should be caused by toString().

Code: Select all
    var decPos = numS.indexOf('.');

The variable decPos holds the numeric index/position of the decimal point. This will be used in other methods. Note that this function is not regionally aware: it assumes that the decimal separator is the period and doesn't account for countries where the comma is used instead.

Code: Select all
    var substrLength = decPos == -1 ? numS.length : 1 + decPos + digits;

The variable substrLength stores the updated length of the "number" that should result from the operation. This code is weird because it uses the ternary operator, which is similar to an inline if statement in other languages. The key to understanding it is the part before the question mark:

var substrLength = decPos == -1 ? numS.length : 1 + decPos + digits;

You can think of it as being equivalent to this:

Code: Select all
// EXAMPLE CODE NOT PART OF ORIGINAL
if (decPos == -1){ // num was a whole number, there is no decimal, length is same as original
    var substrLength = numS.length;
} else { // there is a decimal point, length needs to account for digits
    var substrLength = 1 + decPos + digits;
}

Assuming there is a decimal point, it is the position of the decimal point, plus 1 because the index starts at zero, plus digits, which is the number provided by the calling code. Question: what if digits exceed the actual number of digits available -- what should happen in that case?

Code: Select all
    var trimmedResult = numS.substr(0, substrLength);

The variable trimmedResult is the original number but truncated after substrLength. No rounding should be caused by substr().

Code: Select all
    var finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

The variable finalResults uses the ternary operator again. If trimmedResult is not a number, it is set to zero, otherwise, it is set to trimmedResult.

Code: Select all
    return parseFloat(finalResult);
  }

And finally, finalResult is converted back to a floating point number and returned to the calling code.

At least, that's what it looks like to me.

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted July 14th, 2022, 8:48 am

Thanks that makes things a little clearer, though I’m still not quite sure how I would actually use this because I’m using 3 different variables for the number one for each of the 3 axis

So my question is how would I feed those numbers to the function when the function only has a single variable for number?

Something else I just noticed looking over the test data the morat posted is that the truncateDecimals function is returning a number to 2 decimal places not 3 significant digits though the difference might be subtle the difference is important.

For example according to the test data the morat posted the truncateDecimals function will return a number like 11.96054054054054 as 11.96

However a number like 11.96054054054054 will display as 11.9 when displayed to 3 significant digits

So the truncateDecimals function seems to be giving the wrong output for my purpose because what I’m trying to do is replicate the behaviour of .toPrecision(3) aka display a number to 3 significant digits however I want to do so without rounding which is the road block I’ve run in to because .toPrecision(3) doesn’t allow you to specify whither you want to round up, round down or not round at all, and unfortunately my knowledge of JavaScript is insufficient and I have no idea how to do what I need to.

In any event I’ve put together a striped down quick and dirty demo page which should hopefully make things a bit clearer

Code: Select all
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>demo</title>

<style>

table#demo
{
margin:0;
padding:0;
border-collapse: collapse;
font-family: Arial;
margin-top:16px
}

table#demo tr td
{
padding:0 2px;
}

table#demo tr td:nth-of-type(1)
{
text-align: center;
}

table#demo tr td:nth-of-type(2)
{
transform: translate(0px, -2px);
}

table#demo tr td:nth-of-type(3)
{
text-align: right;
}

</style>

</head>

<body>

<table>

  <tr data-name='icon1'>
    <td>icon 1</td>
  </tr>

  <tr data-name='icon2'>
    <td>icon 2</td>
  </tr>

  <tr data-name='icon3'>
    <td>icon 3</td>
  </tr>

  <tr data-name='icon4'>
    <td>icon 4</td>
  </tr>

  <tr data-name='icon5'>
    <td>icon 5</td>
  </tr>

  <tr data-name='icon6'>
    <td>icon 6</td>
  </tr>

  <tr data-name='icon7'>
    <td>icon 7</td>
  </tr>

  <tr data-name='icon8'>
    <td>icon 8</td>
  </tr>

  <tr data-name='icon9'>
    <td>icon 9</td>
  </tr>

  <tr data-name='icon10'>
    <td>icon 10</td>
  </tr>

  <tr data-name='icon11'>
    <td>icon 11</td>
  </tr>

  <tr data-name='icon12'>
    <td>icon 12</td>
  </tr>

  <tr data-name='icon13'>
    <td>icon 13</td>
  </tr>

  <tr data-name='icon14'>
    <td>icon 14</td>
  </tr>

  <tr data-name='icon15'>
    <td>icon 15</td>
  </tr>

  <tr data-name='icon16'>
    <td>icon 16</td>
  </tr>

</table>

<table id="demo">
  <tr>
    <td>X</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>

  <tr>
    <td>Y</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>

  <tr>
    <td>Z</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>
</table>

<script>

var icons =
{
icon1:  {x: -609840,   y: 1879080,  z: -1617000},
icon2:  {x: 3552000,   y: 333000,   z: 4151400},
icon3:  {x: 722400,    y: -1187760, z: 1300320},
icon4:  {x: -1603560,  y: 506520,   z: -1195320},
icon5:  {x: -7022400,  y: 435960,   z: -1504440},
icon6:  {x: 2009280,   y: -1346520, z: 2655240},
icon7:  {x: -5394600,  y: -1110000, z: -3529800},
icon8:  {x: -2348640,  y: 1706040,  z: 1743000},
icon9:  {x: -3662400,  y: 2055480,  z: 1601040},
icon10: {x: -655200,   y: -926520,  z: -3623760},
icon11: {x: 5328000,   y: -88800,   z: 1931400},
icon12: {x: 3108000,   y: 88800,    z: -1776000},
icon13: {x: 0,         y: 0,        z: 10080000},
icon14: {x: 10080000,  y: 0,        z: 0},
icon15: {x: 0,         y: 0,        z: -10080000},
icon16: {x: -10080000, y: 0,        z: 0},
};

var table = document.querySelector('table#demo');

function hoveron(event)
{
  if(event.target.closest('table').id !== "demo")
  {
    var target = event.target.closest('tr').getAttribute('data-name');
 
    var x_coordinate = icons[target]['x'] / 222 / 1000;
    var y_coordinate = icons[target]['y'] / 222 / 1000;
    var z_coordinate = icons[target]['z'] / 222 / 1000;

    if(x_coordinate > 0 && x_coordinate < 1 || x_coordinate < 0 && x_coordinate > -1 || x_coordinate == 0)
    {
      var x_coordinate = x_coordinate * 1000;
      table.rows[0].cells[2].innerHTML = Math.trunc(x_coordinate) + " m";
    }

    else
    {
      table.rows[0].cells[2].innerHTML = x_coordinate + " km";
    }

    if(y_coordinate > 0 && y_coordinate < 1 || y_coordinate < 0 && y_coordinate > -1 || y_coordinate == 0)
    {
      var y_coordinate = y_coordinate * 1000;
      table.rows[1].cells[2].innerHTML = Math.trunc(y_coordinate) + " m";
    }

    else
    {
      table.rows[1].cells[2].innerHTML = y_coordinate + " km";
    }

    if(z_coordinate > 0 && z_coordinate < 1 || z_coordinate < 0 && z_coordinate > -1 || z_coordinate == 0)
    {
      var z_coordinate = z_coordinate * 1000;
      table.rows[2].cells[2].innerHTML = Math.trunc(z_coordinate) + " m";
    }

    else
    {
      table.rows[2].cells[2].innerHTML = z_coordinate + " km";
    }
  }
}

var cells = document.querySelectorAll("td");
for (i = 0; i < cells.length; i++)
{
  cells[i].addEventListener("mouseover", hoveron);
}

</script>

</body>
</html>


Now this output I would expect from each of the 16 items

icon 1 X: -2.74km Y: 8.46km Z: -7.28km
icon 2 X: 16.0km Y: 1.50km Z: 18.7km
icon 3 X: 3.25km Y: -5.35km Z: 5.85km
icon 4 X: -7.22km Y: 2.28km Z: -5.38km
icon 5 X: -31.6km Y: 1.96km Z: -6.77km
icon 6 X: 9.05km Y: -6.06km Z: 11.9km
icon 7 X: -24.3km Y: -5.00km Z: -15.9km
icon 8 X: -10.5km Y: 7.68km Z: 7.85km
icon 9 X: -16.4km Y: 9.25km Z: 7.21km
icon 10 X: -2.95km Y: -4.17km Z: -16.3km
icon 11 X: 24.0km Y: -400m Z: 8.70km
icon 12 X: 14.0km Y: 400m Z: -8.00km
icon 13 X: 0m Y: 0m Z: 45.4km
icon 14 X: 45.4km Y: 0m Z: 0m
icon 15 X: 0m Y: 0m Z: -45.4km
icon 16 X: -45.4km Y: 0m Z: 0m


If everything worked as it should now while things seem to work correctly if the number is less than 1km the issue is getting the numbers to display correctly when the number is 1km or grater, because i have no idea how to replicate the behaviour of .toPrecision(3) minus the rounding.

jscher2000

User avatar
 
Posts: 11670
Joined: December 19th, 2004, 12:26 am
Location: Silicon Valley, CA USA

Post Posted July 18th, 2022, 3:02 pm

> how would I feed those numbers to the function when the function only has a single variable for number?

You could call it for each value that needs to be processed, like

x = truncateDecimals(x, 2); y = truncateDecimals(y, 2); z = truncateDecimals(z, 2);

> So the truncateDecimals function seems to be giving the wrong output for my purpose because what I’m trying to do is replicate the behaviour of .toPrecision(3) aka display a number to 3 significant digits

"Significant digits" is giving me flashbacks to high school calculus. Have you searched for a function for that? I don't want to go back there. Don't make me go back.

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted July 20th, 2022, 3:10 pm

"Significant digits" is giving me flashbacks to high school calculus. Have you searched for a function for that? I don't want to go back there. Don't make me go back.


yeah but I can't say I have found anything that would answer my question aka how to replicate the behaviour of the JavaScript .toPrecision method minus the rounding part.

why it is that when .toPrecision and .toFixed where implemented they decided to make them round I don't know it seems like a fairly stupid thing to do especially when you consider that it would in fact be fairly easy to round before outputting if you really wanted to round, and what makes it even more stupid is the fact that after making this boneheaded decision they provide no way to turn the rounding part off and what's even more stupid is the fact that more often then then not toPrecision and .toFixed get the rounding wrong due to the fact that not all fractional number can be accurately represented due to the way computers work.

so all in all it would of in fact been more sensible and more accurate for .toPrecision and .toFixed to not round by default.

sadly math isn't my strong suite either the first time I tried doing higher math after leaving high school I failed, although I did manage to pass my higher math a few years later when I went back to college to do the web design course, I just wish they had actually covered JavaScript because its kind of a prerequisite if you want to work as a wed developer, additionally had it actually been covered maybe I wouldn't be having such a hard time with this now oh well guess I'll just have to teach my self the same way I learned to do most things by doing and by asking lots of questions when I can't figure out how to do what I'm trying to do.

You could call it for each value that needs to be processed, like

x = truncateDecimals(x, 2); y = truncateDecimals(y, 2); z = truncateDecimals(z, 2);


I see so

Code: Select all
table.rows[0].cells[2].innerHTML = x_coordinate + " km";
table.rows[1].cells[2].innerHTML = y_coordinate + " km";
table.rows[2].cells[2].innerHTML = z_coordinate + " km";


would become

Code: Select all
function truncateDecimals(number, decimal_places)
{
  //do stuff
}

var x_coordinate = truncateDecimals(x_coordinate, 2);
var y_coordinate = truncateDecimals(y_coordinate, 2);
var z_coordinate = truncateDecimals(z_coordinate, 2);

table.rows[0].cells[2].innerHTML = x_coordinate + " km";
table.rows[1].cells[2].innerHTML = y_coordinate + " km";
table.rows[2].cells[2].innerHTML = z_coordinate + " km";


is that right ?

jscher2000

User avatar
 
Posts: 11670
Joined: December 19th, 2004, 12:26 am
Location: Silicon Valley, CA USA

Post Posted July 21st, 2022, 10:02 am

Bethrezen wrote:
You could call it for each value that needs to be processed, like

x = truncateDecimals(x, 2); y = truncateDecimals(y, 2); z = truncateDecimals(z, 2);


I see so

Code: Select all
table.rows[0].cells[2].innerHTML = x_coordinate + " km";
table.rows[1].cells[2].innerHTML = y_coordinate + " km";
table.rows[2].cells[2].innerHTML = z_coordinate + " km";


would become

Code: Select all
function truncateDecimals(number, decimal_places)
{
  //do stuff
}

var x_coordinate = truncateDecimals(x_coordinate, 2);
var y_coordinate = truncateDecimals(y_coordinate, 2);
var z_coordinate = truncateDecimals(z_coordinate, 2);

table.rows[0].cells[2].innerHTML = x_coordinate + " km";
table.rows[1].cells[2].innerHTML = y_coordinate + " km";
table.rows[2].cells[2].innerHTML = z_coordinate + " km";


is that right ?

You probably should not redefine x_coordinate if it's already a var. So you can remove var from those 3 lines. You also can streamline the code by using the function in the assignment:

Code: Select all
table.rows[0].cells[2].innerHTML = truncateDecimals(x_coordinate, 2) + " km";
table.rows[1].cells[2].innerHTML = truncateDecimals(y_coordinate, 2) + " km";
table.rows[2].cells[2].innerHTML = truncateDecimals(z_coordinate, 2) + " km";

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted August 4th, 2022, 5:03 pm

You probably should not redefine x_coordinate if it's already a var. So you can remove var from those 3 lines. You also can streamline the code by using the function in the assignment:

Code: Select all
table.rows[0].cells[2].innerHTML = truncateDecimals(x_coordinate, 2) + " km";
table.rows[1].cells[2].innerHTML = truncateDecimals(y_coordinate, 2) + " km";
table.rows[2].cells[2].innerHTML = truncateDecimals(z_coordinate, 2) + " km";


I see, well that helps clear up how to use the function unfortunately it doesn’t really help with modifying said function to get the proper output.

Anyway here is an updated version of the demo page which incorporates the changes so far

Code: Select all
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>demo</title>

<style>

table#demo
{
margin:0;
padding:0;
border-collapse: collapse;
font-family: Arial;
margin-top:16px
}

table#demo tr td
{
padding:0 2px;
}

table#demo tr td:nth-of-type(1)
{
text-align: center;
}

table#demo tr td:nth-of-type(2)
{
transform: translate(0px, -2px);
}

table#demo tr td:nth-of-type(3)
{
text-align: right;
}

</style>

</head>

<body>

<table>

  <tr data-name='icon1'>
    <td>icon 1</td>
  </tr>

  <tr data-name='icon2'>
    <td>icon 2</td>
  </tr>

  <tr data-name='icon3'>
    <td>icon 3</td>
  </tr>

  <tr data-name='icon4'>
    <td>icon 4</td>
  </tr>

  <tr data-name='icon5'>
    <td>icon 5</td>
  </tr>

  <tr data-name='icon6'>
    <td>icon 6</td>
  </tr>

  <tr data-name='icon7'>
    <td>icon 7</td>
  </tr>

  <tr data-name='icon8'>
    <td>icon 8</td>
  </tr>

  <tr data-name='icon9'>
    <td>icon 9</td>
  </tr>

  <tr data-name='icon10'>
    <td>icon 10</td>
  </tr>

  <tr data-name='icon11'>
    <td>icon 11</td>
  </tr>

  <tr data-name='icon12'>
    <td>icon 12</td>
  </tr>

  <tr data-name='icon13'>
    <td>icon 13</td>
  </tr>

  <tr data-name='icon14'>
    <td>icon 14</td>
  </tr>

  <tr data-name='icon15'>
    <td>icon 15</td>
  </tr>

  <tr data-name='icon16'>
    <td>icon 16</td>
  </tr>

</table>

<table id="demo">
  <tr>
    <td>X</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>

  <tr>
    <td>Y</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>

  <tr>
    <td>Z</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>
</table>

<script>

var icons =
{
icon1:  {x: -609840,   y: 1879080,  z: -1617000},
icon2:  {x: 3552000,   y: 333000,   z: 4151400},
icon3:  {x: 722400,    y: -1187760, z: 1300320},
icon4:  {x: -1603560,  y: 506520,   z: -1195320},
icon5:  {x: -7022400,  y: 435960,   z: -1504440},
icon6:  {x: 2009280,   y: -1346520, z: 2655240},
icon7:  {x: -5394600,  y: -1110000, z: -3529800},
icon8:  {x: -2348640,  y: 1706040,  z: 1743000},
icon9:  {x: -3662400,  y: 2055480,  z: 1601040},
icon10: {x: -655200,   y: -926520,  z: -3623760},
icon11: {x: 5328000,   y: -88800,   z: 1931400},
icon12: {x: 3108000,   y: 88800,    z: -1776000},
icon13: {x: 0,         y: 0,        z: 10080000},
icon14: {x: 10080000,  y: 0,        z: 0},
icon15: {x: 0,         y: 0,        z: -10080000},
icon16: {x: -10080000, y: 0,        z: 0},
};

var table = document.querySelector('table#demo');

function hoveron(event)
{
  if(event.target.closest('table').id !== "demo")
  {
    var target = event.target.closest('tr').getAttribute('data-name');
 
    var x_coordinate = icons[target]['x'] / 222 / 1000;
    var y_coordinate = icons[target]['y'] / 222 / 1000;
    var z_coordinate = icons[target]['z'] / 222 / 1000;

    function truncateDecimals(number, decimal_places)
    {
      var numS = number.toString(),
      decPos = numS.indexOf('.'),
      substrLength = decPos == -1 ? numS.length : 1 + decPos + decimal_places,
      trimmedResult = numS.substr(0, substrLength),
      finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

      return parseFloat(finalResult);
    }

    if(x_coordinate > 0 && x_coordinate < 1
       || x_coordinate < 0 && x_coordinate > -1
       || x_coordinate == 0)
    {
      table.rows[0].cells[2].innerHTML = x_coordinate * 1000 + " m";
    }

    else
    {
      table.rows[0].cells[2].innerHTML = truncateDecimals(x_coordinate, 2) + " km";
    }

    if(y_coordinate > 0 && y_coordinate < 1
       || y_coordinate < 0 && y_coordinate > -1
       || y_coordinate == 0)
    {
      table.rows[1].cells[2].innerHTML = y_coordinate * 1000 + " m";
    }

    else
    {
      table.rows[1].cells[2].innerHTML = truncateDecimals(y_coordinate, 2) + " km";
    }

    if(z_coordinate > 0 && z_coordinate < 1
       || z_coordinate < 0 && z_coordinate > -1
       || z_coordinate == 0)
    {
      table.rows[2].cells[2].innerHTML = z_coordinate * 1000 + " m";
    }

    else
    {
      table.rows[2].cells[2].innerHTML = truncateDecimals(z_coordinate, 2) + " km";
    }
  }
}

var cells = document.querySelectorAll("td");
for (i = 0; i < cells.length; i++)
{
  cells[i].addEventListener("mouseover", hoveron);
}

</script>

</body>
</html>


As you can see upon running the truncateDecimals, there are 2 things wrong as already mentioned previously the truncateDecimals is formatting to 2 decimal places not 3 significant digits, so I’m getting output like

X: -10.57 km when I should get X: -10.5 km

I also noticed a second issue it’s dropping zeros so I’m getting output like

Y: -5 km when I should get Y: -5.00 km

Which of course leads me right back to my original problem trying to replicate the functionality of .toPrecision() so I get the proper output, since .toPrecision() has optional parameter to disable rounding.

I suppose there is one other way I could go about dealing with this irritating problem I could do it manually like so

Code: Select all
var coordinates =
{
  //stations
  shipyard:                {x: "-2.74 km", y: "8.46 km",  z: "-7.28 km"},
  wharf:                   {x: "16.0 km",  y: "1.50 km",  z: "18.7 km"},
  equipment_dock:          {x: "3.25 km",  y: "-5.35 km", z: "5.85 km"},
  trading_station:         {x: "-7.22 km", y: "2.28 km",  z: "-5.38 km"},
  solar_power_plant_alpha: {x: "-31.6 km", y: "1.96 km",  z: "-6.77 km"},
  solar_power_plant_beta:  {x: "9.05 km",  y: "-6.06 km", z: "11.9 km"},
  rimes_fact_alpha:        {x: "-24.3 km", y: "-5.00 km", z:"-15.9 km"},
};


although I was really hoping to avoid having to do this because it's really slow and time consuming and kinds defeats the whole purpose of the exercise

Bethrezen
 
Posts: 431
Joined: September 13th, 2003, 11:56 am

Post Posted August 11th, 2022, 4:31 pm

Ok so after much searching I was unable to find anything that would tell me how to truncate a number to a specified number of significant digits without rounding so I had to invent my own so after a lot of tinkering and trying out a bunch of different things I think I’ve come up with a solution that should work correctly, so for the benefit of anyone else that might be looking to truncate a number to a given number of significant digits without rounding here is my solution.

Code: Select all
function truncateDecimals(number, significant_digits)
{
  var string = String(number);
  var decimal_position = string.indexOf('.');

  if(number > 0 && decimal_position > 0
     || number < 0 && decimal_position == -1
     || number < 0 && decimal_position - 1 == significant_digits)
  {
    var string_length = significant_digits + 1;
  }

  if(number < 0 && decimal_position > 0 && decimal_position - 1 < significant_digits)
  {
    var string_length = significant_digits + 2;
  }

  if(number > 0 && decimal_position == -1
     || number > 0 && decimal_position == significant_digits)
  {
    var string_length = significant_digits;
  }

  if(decimal_position == -1)
  {
    var pad_zeros = "0".repeat(significant_digits - string.replace("-","").length);
    var result = string + "." + pad_zeros;
  }

  else if(decimal_position > 0 && string.length < string_length)
  {
    var pad_zeros = "0".repeat(string_length - string.length);
    var result = string + pad_zeros;
  }

  else
  {
    var result = string.substring(0, string_length);
  }

  return result;
}


to see a quick demo of this in action, just copy the code below to a new text document and then rename it from .txt to .html then open it up in your browser, and then hover where it says icon.

if you don't see the file extension then you will need to unchecked hide extensions for known file types.

Code: Select all
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>demo</title>

<style>

table#demo
{
margin:0;
padding:0;
border-collapse: collapse;
font-family: Arial;
margin-top:16px;
display:block
}

table#demo tr td
{
padding:0 2px;
}

table#demo tr td:nth-of-type(1)
{
text-align: center;
}

table#demo tr td:nth-of-type(2)
{
transform: translate(0px, -2px);
}

table#demo tr td:nth-of-type(3)
{
text-align: right;
}

table
{
display:inline-block;
vertical-align: top;
}

</style>

</head>

<body>

<table>

  <tr data-name='icon1'>
    <td>icon 1</td>
  </tr>

  <tr data-name='icon2'>
    <td>icon 2</td>
  </tr>

  <tr data-name='icon3'>
    <td>icon 3</td>
  </tr>

  <tr data-name='icon4'>
    <td>icon 4</td>
  </tr>

  <tr data-name='icon5'>
    <td>icon 5</td>
  </tr>

  <tr data-name='icon6'>
    <td>icon 6</td>
  </tr>

  <tr data-name='icon7'>
    <td>icon 7</td>
  </tr>

  <tr data-name='icon8'>
    <td>icon 8</td>
  </tr>

  <tr data-name='icon9'>
    <td>icon 9</td>
  </tr>

  <tr data-name='icon10'>
    <td>icon 10</td>
  </tr>

</table>

<table>

  <tr data-name='icon11'>
    <td>icon 11</td>
  </tr>

  <tr data-name='icon12'>
    <td>icon 12</td>
  </tr>

  <tr data-name='icon13'>
    <td>icon 13</td>
  </tr>

  <tr data-name='icon14'>
    <td>icon 14</td>
  </tr>

  <tr data-name='icon15'>
    <td>icon 15</td>
  </tr>

  <tr data-name='icon16'>
    <td>icon 16</td>
  </tr>

  <tr data-name='icon17'>
    <td>icon 17</td>
  </tr>

  <tr data-name='icon18'>
    <td>icon 18</td>
  </tr>

  <tr data-name='icon19'>
    <td>icon 19</td>
  </tr>

  <tr data-name='icon20'>
    <td>icon 20</td>
  </tr>

</table>

<table>

  <tr data-name='icon21'>
    <td>icon 21</td>
  </tr>

  <tr data-name='icon22'>
    <td>icon 22</td>
  </tr>

  <tr data-name='icon23'>
    <td>icon 23</td>
  </tr>

  <tr data-name='icon24'>
    <td>icon 24</td>
  </tr>

  <tr data-name='icon25'>
    <td>icon 25</td>
  </tr>

  <tr data-name='icon26'>
    <td>icon 26</td>
  </tr>

  <tr data-name='icon27'>
    <td>icon 27</td>
  </tr>

</table>

<table id="demo">
  <tr>
    <td>X</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>

  <tr>
    <td>Y</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>

  <tr>
    <td>Z</td>
    <td>:</td>
    <td>0.00 km</td>
  </tr>
</table>

<script>

var icons =
{
icon1:  {x: -609840,   y: 1879080,  z: -1617000},
icon2:  {x: 3552000,   y: 333000,   z: 4151400},
icon3:  {x: 722400,    y: -1187760, z: 1300320},
icon4:  {x: -1603560,  y: 506520,   z: -1195320},
icon5:  {x: -7022400,  y: 435960,   z: -1504440},
icon6:  {x: 2009280,   y: -1346520, z: 2655240},
icon7:  {x: -5394600,  y: -1110000, z: -3529800},
icon8:  {x: -2348640,  y: 1706040,  z: 1743000},
icon9:  {x: -3662400,  y: 2055480,  z: 1601040},
icon10: {x: -655200,   y: -926520,  z: -3623760},
icon11: {x: 5328000,   y: -88800,   z: 1931400},
icon12: {x: 3108000,   y: 88800,    z: -1776000},
icon13: {x: -1603560, y: -61320,   z: -1195320},
icon14: {x: -1159200, y: 1637160,  z: 22248240},
icon15: {x: -3071040, y: -493920,  z: 3858960},
icon16: {x: -8783880, y: 192360,   z: 3163440},
icon17: {x: -7970760, y: -1031520, z: -281400},
icon18: {x: -3981600, y: -1792560, z: -336840},
icon19: {x: -5961480, y: 990360 ,  z: -6830880},
icon20: {x: -2100000, y: -614880,  z: -2161320},
icon21: {x: 3108000,  y: 0,        z: 3774000},
icon22: {x: -3928680, y: 451920,   z: -1792560},
icon23: {x: -496440,  y: -787920,  z: -5553240},
icon24: {x: 0,         y: 0,        z: 10080000},
icon25: {x: 10080000,  y: 0,        z: 0},
icon26: {x: 0,         y: 0,        z: -10080000},
icon27: {x: -10080000, y: 0,        z: 0},
};

var table = document.querySelector('table#demo');

function hoveron(event)
{
  if(event.target.closest('table').id !== "demo")
  {
    var target = event.target.closest('tr').getAttribute('data-name');
 
    var x_coordinate = icons[target]['x'] / 222 / 1000;
    var y_coordinate = icons[target]['y'] / 222 / 1000;
    var z_coordinate = icons[target]['z'] / 222 / 1000;

    function truncateDecimals(number, significant_digits)
    {
      var string = String(number);
      var decimal_position = string.indexOf('.');

      if(number > 0 && decimal_position > 0
         || number < 0 && decimal_position == -1
         || number < 0 && decimal_position - 1 == significant_digits)
      {
        var string_length = significant_digits + 1;
      }

      if(number < 0 && decimal_position > 0 && decimal_position - 1 < significant_digits)
      {
        var string_length = significant_digits + 2;
      }

      if(number > 0 && decimal_position == -1
         || number > 0 && decimal_position == significant_digits)
      {
        var string_length = significant_digits;
      }

      if(decimal_position == -1)
      {
        var pad_zeros = "0".repeat(significant_digits - string.replace("-","").length);
        var result = string + "." + pad_zeros;
      }

      else if(decimal_position > 0 && string.length < string_length)
      {
        var pad_zeros = "0".repeat(string_length - string.length);
        var result = string + pad_zeros;
      }

      else
      {
        var result = string.substring(0, string_length);
      }

      return result;
    }

    if(x_coordinate < 1 && x_coordinate > -1)
    {
      table.rows[0].cells[2].innerHTML = Math.trunc(x_coordinate * 1000) + " m";
    }

    else
    {
      table.rows[0].cells[2].innerHTML = truncateDecimals(x_coordinate, 3) + " km";
    }

    if(y_coordinate < 1 && y_coordinate > -1)
    {
      table.rows[1].cells[2].innerHTML = Math.trunc(y_coordinate * 1000) + " m";
    }

    else
    {
      table.rows[1].cells[2].innerHTML = truncateDecimals(y_coordinate, 3) + " km";
    }

    if(z_coordinate < 1 && z_coordinate > -1)
    {
      table.rows[2].cells[2].innerHTML = Math.trunc(z_coordinate * 1000) + " m";
    }

    else
    {
      table.rows[2].cells[2].innerHTML = truncateDecimals(z_coordinate, 3) + " km";
    }
  }
}

var cells = document.querySelectorAll("td");
for (i = 0; i < cells.length; i++)
{
  cells[i].addEventListener("mouseover", hoveron);
}

</script>

</body>
</html>


Now I’ll admit this isn’t the shortest or most elegant solution and sure it could probably be improved but so far as I can tell the logic is sound, it works correctly with both positive and negative numbers it also correctly pads the appropriate number of zeros regardless of whether it’s a whole number or a floating point number that is to short to meet the specified level of precision.

I tested this up to 10 significant digits and everything seemed to work fine.

EDIT: I refined the original function removing unnecessary code, I also fixed a minor oversight which cause the original function to do the wrong thing and thus give incorrect output.

Return to Web Development / Standards Evangelism


Who is online

Users browsing this forum: No registered users and 2 guests