6

I am creating an online calculator using JavaScript.

I have this to work out a calculation:

eval(expression).toPrecision(10);

This produces the right output in almost all cases. E.g.

eval('456456+45646486*45646884').toPrecision(10)
// Output: "2.083619852e+15"

eval('1/0').toPrecision(10)
// Output: "Infinity"

However

eval('4*1').toPrecision(10)
// Output: "4.000000000"

How do I trim the trailing zeros but also keep nice outputs above?

Greg
  • 31,180
  • 18
  • 65
  • 85
  • `numb = (Math.round(numb * 100000000)) / 100000000);` – adeneo May 09 '13 at 21:29
  • Thanks guys but both these suggestions only cover the 2nd and 3rd examples but now the first example produces "2083619852000000" rather than "2.083619852e+15" – Greg May 09 '13 at 21:36
  • Be sure `expression` is generated by *you* and not by a third party. That is, read the input and generate `expression` based on that, since [`eval()` on unsafe code is unsafe](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Don.27t_use_eval.21). – 0b10011 Nov 07 '14 at 21:43

3 Answers3

21

Divide by 1 after using toPrecision. Javascript will trail the zeros, and there's no regexes needed.

Samantha Dove
  • 229
  • 2
  • 5
4

only all-zero decimals

  eval('4*1').toPrecision(10).replace(/\.0+$/,"")

zero ending decimals:

  eval('4.5*1').toPrecision(10).replace(/\.([^0]+)0+$/,".$1")

edit: handle both all zero and ending zero cases

EDIT: if you will ALWAYS use .toPrecision() first, and thus always have a ".", you can trim off any trailing zeros:

  eval('4.5*1').toPrecision(10).replace(/0+$/,"")

EDIT: handling trailing decimals:

eval('4.5*0').toPrecision(10).replace(/\.?0+$/,"")
dandavis
  • 16,370
  • 5
  • 40
  • 36
  • Almost, but `eval('4+0.0001').toPrecision(10).replace(/\.0+$/,"")` gives "4.000100000" rather than "4.0001" – Greg May 09 '13 at 21:38
  • gotcha, added two other options for that. let me know if something more complex is required. – dandavis May 09 '13 at 21:41
  • Regex to the rescue? That last edit I think nailed it! I'll give it a more thorough test and accept if its good to go! Many thanks. – Greg May 09 '13 at 21:43
  • Is there anyway of improving it slightly for whole numbers? i.e. "2" instead of "2."? – Greg May 09 '13 at 21:46
  • @greg: added to answer. i think that last one is THE one, have a good feeling about it... – dandavis May 09 '13 at 22:16
  • So long as I don't use numbers beginning with 0's like `04.5+10` I think this should be alright... – Greg May 10 '13 at 07:52
  • All of these result in trailing `0`s for certain input. Precision of 2 for `/\.0+$/,""` will return `0.040` for `.04*1`. Precision of 2 for `/\.([^0]+)0+$/,".$1"` will return `0.040` for `.04*1`. Precision of 2 for `/0+$/,""` will return `4.` for `4*1`. Precision of 2 for `/\.?0+$/,""` will return `4` for `40*1`. – 0b10011 Nov 07 '14 at 21:39
  • (I specified that those examples messed up when the precision was 2. The input can be altered to result in the same issue with a precision of 10.) [`eval('4000000000*1').toPrecision(10).replace(/\.?0+$/,"")` becomes `4` instead of `4000000000`.](http://jsfiddle.net/bfrohs/06uw0fo4/) And I never said `/\.?0+$/,""` would mess up `0.04*1`. I said `/\.0+$/,""` and `/\.([^0]+)0+$/,".$1"` would. Look at the Stack Snippet in my answer to see the numbers your answer's methods mess up on. – 0b10011 Nov 07 '14 at 22:21
  • we were only interested in toPrecision(10), it's not a universal solution, which would be a lot more complex... – dandavis Nov 07 '14 at 23:53
4

The following replace() call will replace all trailing 0s and a trailing ., if present:

eval(expression).toPrecision(10).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1")

How does it work?

/(?:\.0+|(\.\d+?)0+)$/ looks for \.0+ or (\.\d+?)0+ at the end of the string ($). The ?: prevents \.0+|(\.\d+?)0+ from being "captured".

\.0+ will match . followed by any number of 0s.

(\.\d+?)0+ will match . followed by at least one digit followed by at least one 0. The ? makes sure the 0+ matches as many 0s as possible. The parentheses "capture" the non-0s.

The second parameter on replace() is the new string to replace what was matched. The $1 is a variable of sorts and tells replace() to replace the matched value with the first captured value (because 1 was after the $). In this case, the first captured value was whatever \.\d+? matched.

So, in the end:

  1. . followed by any number of 0s will be discarded
  2. . followed by non-0s followed by 0s will have the 0s discarded

Examples

For comparison of methods for precisions of 1, 2, and 3 for .40*1, 4*1, and 40*1, see below. First (bold) item in each group is this method. Red items are those that don't match expectations.

var tests = [
  '.04*1',
  '.40*1',
  '4*1',
  '40*1'
];
var results = {};
for (var i = 0; i < tests.length; i += 1) {
  results[i] = {};
  for (var p = 1; p < 3; p += 1) {
    results[i][p] = {};
    results[i][p][0] = {
      'output': eval(tests[i]).toPrecision(p).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1"),
      'regex': '/(?:\.0+|(\.\d+?)0+)$/, "$1"'
    };
    results[i][p][1] = {
      'output': eval(tests[i]).toPrecision(p).replace(/\.0+$/, ""),
      'regex': '/\.0+$/, ""'
    };
    results[i][p][2] = {
      'output': eval(tests[i]).toPrecision(p).replace(/\.([^0]+)0+$/, ".$1"),
      'regex': '/\.([^0]+)0+$/, ".$1"'
    };
    results[i][p][3] = {
      'output': eval(tests[i]).toPrecision(p).replace(/0+$/, ""),
      'regex': '/0+$/, ""'
    };
    results[i][p][4] = {
      'output': eval(tests[i]).toPrecision(p).replace(/\.?0+$/, ""),
      'regex': '/\.?0+$/, ""'
    };
  }
}
for (var i in results) {
  $("#result").append("<h1>" + tests[i] + "</h1>");
  for (var p in results[i]) {
    var expected = null;
    for (var t in results[i][p]) {
      var div = $("<div></div>");
      if (t == 0) {
        expected = results[i][p][t].output;
        div.addClass("expected");
      } else if (results[i][p][t].output !== expected) {
        div.addClass("invalid");
      }
      div.append("P" + p + ": " + results[i][p][t].output);
      div.append(" <small>" + results[i][p][t].regex + "</small>");
      $("#result").append(div);
    }
    $("#result").append("<br>");
  }
}
body { font-family: monospace; }
.expected { font-weight: bold; }
.invalid { color: red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="result"></div>
Community
  • 1
  • 1
0b10011
  • 18,397
  • 4
  • 65
  • 86
  • simply using $2 instead of $1 would simplify and run faster than non-capturing groups. – dandavis Nov 07 '14 at 22:18
  • @dandavis "Simplfy" is subjective. When dealing with complex regex, I find only capturing groups you're actually going to use greatly simplifies things in the long run. And that isn't what I've [found with some experimenting](http://jsperf.com/capturing-vs-non-capturing-math). Yes, with Chrome 37, it's faster to use capturing groups, but Firefox 33 seems to perform just as well regardless of the method used. And, a couple of times I ran the test, Firefox performed *better* with non-capturing groups. – 0b10011 Nov 07 '14 at 22:39