Skip to content

Formatting numbers without exponential notation (for bignumbers, also)? #676

@balagge

Description

@balagge

I have a scenario where I need a string representation of a number without exponential notation (i.e. always in "normal" decimal notation). No rounding should occur, and the actual (highest available) precision value is required. Also, no unnecessary trailing zeros after the decimal point, please :)

Now this is crazily difficult in plain javascript, as far as I know there is no simple solution for that. See this mess:

http://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript

I checked math.format, but, unfortunately, it copies the behavior of javascript Number.prototype.toFixed() , in the sense that specifying notation:"fixed"will always require an actual precision setting (if omitted, defaulted to zero), which is unfortunate, because it causes rounding and/or adding unnecessary 0's after the last significant digit after the decimal point. So, at the end of the day, it returns a different or non-canonical string representation of the input number. By the way, toFixed() does not deliver its promise even in the sense that it returns exponential notation sometimes (???!!). So again, toFixed() is pretty useless, and, by copying its behavior, math.format() also seems pretty useless.

Then I realized that I can solve the problem by using notation:"auto"(which is the default) because it allows the additional option exponential and I can set exponential:{lower:0, upper:Infinity}. This does the job nicely! No rounding, no unnecessary zeroes added!

However, when I use the same function for decimals (bignumbers), a run-time exception occurs.

I checked the source and I found this (lib/utils/bignumber/formatter.js, lines 118-126):

var oldConfig = {
  toExpNeg: value.constructor.toExpNeg,
  toExpPos: value.constructor.toExpPos
};

value.constructor.config({
  toExpNeg: Math.round(Math.log(lower) / Math.LN10),
  toExpPos: Math.round(Math.log(upper) / Math.LN10)
});

This is where the exception occurs, because the value.constructor.toExpNeg and value.constructor.toExpPos do not accept values -Infinitiy and Infinity, respectively (where -Infinity = Math.log(0), and Infinity = Math.log(Infinity).

However, the lines above are not needed at all, since oldConfig is not referenced in the source at all, and setting the toExpNeg and toExpPos properties is not required on the constructor (as far as I see), since the following lines (134-141) check the limits for exponential notation, and it is not decimal.js that decides whether to use exponential or fixed notation.

So I tried removing the above lines, and everything works as expected! Try:

var math = require('mathjs');

var numbers = [
1.1234567890123456789e+30, 
1.1234567890123456789e-30, 
-1.1234567890123456789e+30,
-1.1234567890123456789e-30, 
math.bignumber("0.12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 
math.bignumber("12345678901234567890123456789012345678901234567890123456789012345678901234567890")]

var i;
for (i=0;i<numbers.length;i++) {
    console.log(math.format(numbers[i],{exponential:{lower:0,upper:Infinity}}));
}

with the above lines this produces an exception.

Error: [DecimalError] Invalid argument: toExpNeg: -Infinity

without them, it produces the expected results:

1123456789012345700000000000000
0.0000000000000000000000000000011234567890123457
-1123456789012345700000000000000
-0.0000000000000000000000000000011234567890123457
0.1234567890123456789012345678901234567890123456789012345678901235
12345678901234567890123456789012345678901234567890123456789012350000000000000000

Note: loss of some significant digits is normal, both for the javascript numbers (float) and for the decimals (precision is set to 64 significant digits).

So maybe removing lines 118 through 126 from formatter.js could be a solution? But I kind of hesitate, because of a comment in the source (line 117):

 // adjust the configuration of the BigNumber constructor (yeah, this is quite tricky...)

So maybe I am missing a point here?

Update: checked for complexes as well, the setting {exponential:{lower:0,upper:Infinity}} works fine, also!

Update2: decimal.js does correct the messy behavior of javascript toFixed(), it drops both the (stupid) defaulting of precision to 0, and the (even crazier) exponential notation in the return value. See

http://mikemcl.github.io/decimal.js/#toFixed

Where it reads

" Unlike Number.prototype.toFixed, which returns exponential notation if a number is greater or equal to 10^21, this method will always return normal notation.

If dp is omitted, the return value will be unrounded and in normal notation. This is unlike Number.prototype.toFixed, which returns the value to zero decimal places[...]"

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions