npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

to-plain

v1.0.1

Published

Convert number to string without scientific notation

Readme

toPlain

npm GitHub License: MIT

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-plain

Usage

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)
    • bigint
    • string (decimal, exponential, or radix-prefixed — see below)
    • the wrapper objects Number, BigInt, String (unwrapped via valueOf())

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 zeros

Strings

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)

toPlain is 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 string that 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