to-plain
v1.0.1
Published
Convert number to string without scientific notation
Maintainers
Readme
toPlain
Convert a number to a string — and never, under any circumstance, degrade into scientific notation.
toPlain takes a number, bigint, or numeric string and hands you back an honest, plain, decimal string. No e. No e+22. No 1.23e-7. Just digits, a decimal point, and — if you earned it — a minus sign.
Why this exists
Because JavaScript decided, on your behalf, that you don't want to see your own number.
Feed Number.prototype.toString() a value that crosses one of the spec's arbitrary "that's too many digits" thresholds and it silently vandalizes your output:
(0.000000123).toString() // "1.23e-7" — WHAT. No.
(1e21).toString() // "1e+21" — I asked for a number, not a punchline.
String(0.123e-10) // "1.23e-11" — the decimal point *moved* and got *replaced by an e*.The behavior is baked into the ECMAScript specification: toString switches to exponential form whenever the exponent n of the value (written as m × 10ⁿ, 1 ≤ m < 10) is n ≥ 21 or n ≤ -7. That is the rule. There is no "please don't" flag. There is no opt-out. toLocaleString might work in your browser and produce garbage in the next one. So you can either write this conversion by hand every time, or use a function that does exactly one thing and does it without betraying you.
This library exists so you never have to think about that threshold again. It is a middle finger, in code form, aimed squarely at "1e+21".
| Your number | String(x) / `${x}` | toPlain(x) |
| ---------------------------- | -------------------------- | ------------------------------------- |
| 0.123e-10 | "1.23e-11" | "0.0000000000123" |
| 123.123e20 | "1.23123e+22" | "12312300000000000000000" |
| 1e21 | "1e+21" | "1000000000000000000000" |
| 1e-7 | "1e-7" | "0.0000001" |
| Number.MAX_VALUE | "1.7976931348623157e+308" | "17976931348623157…" (all 309 digits) |
| Number.MIN_VALUE | "5e-324" | "0.000…0005" (all 324 decimal places) |
Installation
# npm
npm install to-plain
# yarn
yarn add to-plain
# pnpm
pnpm add to-plain
# bun
bun add to-plainUsage
import toPlain from "to-plain";
toPlain(0.123e-10); // "0.0000000000123"
toPlain(1e21); // "1000000000000000000000"
toPlain(-123.123e20); // "-12312300000000000000000"
toPlain("123.123e-1"); // "12.3123"
toPlain(0xDeadBeef); // "3735928559"
toPlain(256n); // "256"Works in CommonJS too:
const toPlain = require("to-plain");API
declare function toPlain(num: number | bigint | string | Number | BigInt | String): string;Parameters
num— the value to stringify. Accepts:number(integers, decimals, and the exponential garbage JS loves so much)bigintstring(decimal, exponential, or radix-prefixed — see below)- the wrapper objects
Number,BigInt,String(unwrapped viavalueOf())
Returns
A plain decimal string. Never exponential. "NaN" when the input cannot be interpreted as a number.
Behavior reference
Every rule below is intentional and covered by tests. No surprises, no thresholds, no silent e.
Numbers
toPlain(0); // "0"
toPlain(123); // "123"
toPlain(0.0012); // "0.0012"
toPlain(0.123e-10); // "0.0000000000123"
toPlain(1.123e-10); // "0.0000000001123"
toPlain(123.123e20); // "12312300000000000000000"
toPlain(-0.123e-10); // "-0.0000000000123"
toPlain(123e14); // "12300000000000000"
toPlain(0.123e-4); // "0.0000123"Exponential inputs are expanded in both directions — huge and tiny:
toPlain(Number.MAX_VALUE); // "17976931348623157" + 292 more zeros
toPlain(Number.MIN_VALUE); // "0.000…0005" with 323 leading zerosStrings
toPlain also accepts strings, and is equally allergic to e. Whitespace is trimmed first.
toPlain("123.123e-1"); // "12.3123"
toPlain("123.123e+4"); // "1231230"
toPlain("123.123e4"); // "1231230"
toPlain("123.123e0"); // "123.123"
toPlain("123.123E-1"); // "12.3123" (uppercase `E` is fine)
toPlain(" 123.123 "); // "123.123" (whitespace is trimmed)
toPlain("31415926535897932.384626433832795e-1"); // "3141592653589793.2384626433832795"A leading + is never kept:
toPlain("+123.123e+4"); // "1231230"
toPlain("+123.123e0"); // "123.123"
toPlain("+0.123e-1"); // "0.0123"BigInt
toPlain(256n); // "256"
toPlain(-123n); // "-123"
toPlain(0xDeadBeefn); // "3735928559"
toPlain(0o135471371453451n); // "6432986912553"
toPlain(0b110011010110101110000011010101010001010n); // "441136032394"Watch out for this one.
BigInt(float)truncates the double, so it exposes the exact integer a float actually stores — not the tidy number you wrote:toPlain(123.123e20); // "12312300000000000000000" (rounded double, decimal string) toPlain(BigInt(123.123e20)); // "12312300000000000131072" (the *real* value in the double)
toPlainis simply being honest here. The float was lying; the BigInt is not.
Wrapper objects
Boxed primitives are unwrapped through valueOf():
toPlain(new Number(123.123e20)); // "12312300000000000000000"
toPlain(new String("123.123e20")); // "12312300000000000000000"
toPlain(new Object(BigInt(123.123e20))); // "12312300000000000131072"Special & invalid values
toPlain(NaN); // "NaN"
toPlain(Infinity); // "Infinity"
toPlain(-Infinity); // "-Infinity"
toPlain("NaN"); // "NaN"
toPlain("Infinity"); // "Infinity"
toPlain(""); // "NaN" (empty string is not a number)Normalization — applied to every result
| Rule | Example | Result |
| ------------------------------------------- | -------------------------------- | ---------------------- |
| -0 becomes "0" | toPlain(-0) / toPlain("-0") | "0" |
| leading + is stripped | toPlain("+123.123e+2") | "12312.3" |
| leading integer zeros are stripped | toPlain("000123.456") | "123.456" |
| trailing fractional zeros are stripped | toPlain("123.456000") | "123.456" |
| a bare leading . becomes 0. | toPlain(".123123e30") | "123123000000000000000000000000" |
| a bare trailing . is dropped | toPlain("123123.e-30") | "0.000000000000000000000000123123" |
| an all-zero fraction is dropped | toPlain("000…123123000…00.000")| "12312300000000000" |
For example:
toPlain("-.123123e-30"); // "-0.000000000000000000000000000000123123"
toPlain(".123123e30"); // "123123000000000000000000000000"
toPlain("00000000000012312300000000000.000000000000"); // "12312300000000000"
toPlain("000000000000000.00000000012312300000000000"); // "0.000000000123123"Other radixes
Hex, octal, and binary literals are accepted — as numbers, bigints, or strings — and converted to plain decimal:
toPlain(0xDeadBeef); // "3735928559"
toPlain("0xDeadBeef"); // "3735928559"
toPlain("-0xDeadBeef"); // "-3735928559"
toPlain(0o135471371453451); // "6432986912553"
toPlain("-0o135471371453451"); // "-6432986912553"
toPlain(0b110011010110101110000011010101010001010); // "441136032394"
toPlain("-0b110011010110101110000011010101010001010"); // "-441136032394"Prefixes are case-insensitive (0X, 0B, 0O all work).
What counts as "valid" input?
toPlain accepts a value when it is:
- a finite
number(including exponential literals like-123.45e-56), - a non-empty
stringthat coerces to a finite number ("1.0e-8","0xDeadBeef"), or - a
bigint.
"", NaN, and Infinity are not valid finite numbers; they fall through to the special-value handling described above.
License
MIT © lunate
