long.js: a 64-bit integer class for JavaScript
A Long class for representing a 64-bit two's-complement integer value.
At a glance
- What is it?
- long.js gives JavaScript a Long class that holds a 64-bit two's-complement integer as two signed 32-bit halves, derived from the Closure Library and extended with unsigned support. It is the right tool when you must interoperate with a binary protocol or a 64-bit identifier, and the wrong tool when native BigInt would do.
- Who is it for?
- long.js suits a developer working with a binary protocol, a database that stores 64-bit identifiers, or a wire format that splits an integer into low and high 32-bit words, and who wants the arithmetic to behave exactly like the Closure implementation it came from.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 47 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why the native Number type cannot hold the value
The background section of the README is a short lesson in why this library exists. As of ECMA-262 5th Edition, all the positive and negative integers whose magnitude is no greater than 2 to the power of 53 are representable in the Number type, which is a double precision 64-bit format as specified in the IEEE Standard for Binary Floating-Point Arithmetic. The maximum safe integer in JavaScript is therefore 2 to the power of 53 minus 1.
The failure is not subtle. The README gives the example that 2 to the power of 64 minus 1 is 18446744073709551615, but in JavaScript it evaluates to 18446744073709552000. The digits are wrong, silently.
There is a second constraint. Bitwise operators in JavaScript deal only with integers in the range from minus 2 to the power of 31 through 2 to the power of 31 minus 1, inclusive, or in the range 0 through 2 to the power of 32 minus 1, inclusive. These operators accept any value of the Number type but first convert each such value to one of 2 to the power of 32 integer values.
So there are two separate problems: representing a 64-bit magnitude, and performing bitwise operations on it. In some use cases, the README says, it is required to be able to reliably work with and perform bitwise operations on the full 64 bits. That is where long.js comes into play.
The audience is therefore narrow and clear: people writing code that has to agree with something else about what a 64-bit integer means.
Two signed 32-bit halves instead of one big number
The class is built from the Closure Library, originally for stand-alone use and extended with unsigned support.
The internal representation is the interesting design decision. A Long holds two fields, low and high, each described as 32 bits as a signed value, plus an unsigned boolean flag recording whether the value should be interpreted as signed or unsigned. The constructor takes exactly that:
import Long from "long";
var value = new Long(0xFFFFFFFF, 0x7FFFFFFF);
console.log(value.toString());The constructor is documented as constructing a 64-bit two's-complement integer given its low and high 32-bit values as signed integers, and the README points at the from* functions as more convenient ways of constructing Longs.
The split into two halves is what makes the library interoperable. When you read a protocol that sends a 64-bit field as two 32-bit words, you do not have to convert, you just assign the words. And the getters come in signed and unsigned pairs, getLowBits and getLowBitsUnsigned, getHighBits and getHighBitsUnsigned, so you can extract either half in whichever interpretation you need.
The class also exposes constants for the boundary cases: ZERO, ONE and NEG_ONE for signed values, UZERO and UONE for unsigned, plus MAX_VALUE, MIN_VALUE and MAX_UNSIGNED_VALUE. The presence of separate unsigned constants and an unsigned flag is the extension over the original Closure class, and it is what lets the same code handle a database that treats an id as unsigned.
Constructing from the type you happen to have
The utility functions cover the ways a 64-bit value arrives in practice, and each one has a documented edge case.
Long.fromBits takes the low and high bits and returns the Long that comes by concatenating them, each assumed to use 32 bits. fromBytes creates a Long from its byte representation, with fromBytesLE and fromBytesBE as explicit little and big endian variants and an le flag on the general form. fromInt returns a Long for a 32-bit integer, and fromNumber returns one for a finite number, with the documented caveat that otherwise zero is returned rather than an error. fromBigInt takes a bigint. fromString parses a string in a given radix, with an overload that takes the radix in place of the unsigned flag. fromValue converts a LongLike, where LongLike is any value or object that either is or can be converted to a Long, using the appropriate from* function for its type.
Two of those deserve a second look. fromNumber returning zero for a non-finite input means a NaN or Infinity quietly becomes 0, which is the sort of default that hides a bug rather than surfacing it. And fromString with a radix is how you read an identifier that arrives as text, which is common for IDs in JSON payloads.
The instance methods follow the same discipline. Arithmetic returns new Longs, so add, divide and the rest are immutable. Comparison comes in several shapes: compare and comp return 0, 1 or minus 1, while equals and eq, greaterThan and gt, and greaterThanOrEqual, gte and ge return booleans. The bitwise side has and, and the shift and rotate operations that a 64-bit protocol needs.
Installing it and the ESM and CommonJS trap
The package is published on npm as long, and installation is the usual command:
$> npm install longThe package exports an ECMAScript module with a UMD fallback, and the manifest makes that explicit with a conditional exports map. The import condition points at ./index.js with types from ./index.d.ts, while the require condition points at ./umd/index.js with types from ./umd/index.d.ts:
"exports": {
".": {
"import": {
"types": "./index.d.ts",
"default": "./index.js"
},
"require": {
"types": "./umd/index.d.ts",
"default": "./umd/index.js"
}
}
},That map is the reason for the warning in the README: mixing ESM and CommonJS is not recommended as it yields different classes, albeit with the same functionality. Two class identities means an instanceof check fails across the boundary, and a Long created by one build is not the same object as a Long created by the other even though the arithmetic agrees. In a monorepo where one package is ESM and another is CommonJS, this bites.
For browser use without a bundler, the README lists CDN URLs. From npm through jsDelivr there is an ESM path at /npm/long@VERSION/index.js and a UMD path at /npm/long@VERSION/umd/index.js, and unpkg offers the same two. From GitHub through jsDelivr it is /gh/dcodeIO/long.js@TAG/index.js. The instruction is to replace TAG and VERSION with a specific version, or omit it, which the README says is not recommended in production. Pinning a version from a CDN is the same discipline as pinning an npm dependency, and the README is right to say so.
What the repository contains beyond the class
The top level entries are small and tell you how the package is built. There is index.js and index.d.ts for the ESM build, a umd/ directory for the UMD build, types.d.ts, a scripts/ directory, tests/, a .prettierrc.js, and wasm.wat.
The wasm.wat file is the one that raises a question the README does not answer. A WebAssembly text format file at the root of the repository implies a WebAssembly build or a WebAssembly component of the implementation, but the README documents none, and the published files list in package.json contains only the JavaScript, the type declarations, the umd bundle, the licence and the readme. So the WebAssembly artefact is not part of the published package, and the documentation gives no hint what it is for. If a WebAssembly fast path matters to you, you will have to read the repository rather than the README.
The build scripts are straightforward. The build script runs node scripts/build.js, and the dev dependencies explain the toolchain: esm2umd at 0.3.1 to produce the UMD build from the ESM source, prettier 3.5.0 for the formatting scripts, and typescript 5.7.3 for the type tests.
The test script is worth noting because it does not test only runtime behaviour. It runs npm run test:unit, which is node tests, and then npm run test:typescript, which compiles the type tests under four separate configurations: tsconfig.esnext.json, tsconfig.nodenext.json, tsconfig.commonjs.json and tsconfig.global.json. Those four configurations exist to prove the typings work under ES modules, under Node's module resolution, under CommonJS and in a global script context, which is exactly the compatibility surface the exports map exposes.
Apache 2.0, an old tag, and when BigInt is the better answer
The licence field on the repository says Apache-2.0, with the LICENSE file at the top of the tree. That is permissive, with the usual obligations to keep the licence text and the notices when you redistribute.
On maintenance, the repository is not archived and the last push was on 2026-09-06. The release list shows v5.3.2 on 2025-04-17, v5.3.1 on 2025-02-17 and v5.3.0 on 2025-02-10. So there are commits from September 2026 and no tag since April 2025. Anyone installing from npm gets the published 5.3.2 unless they track main, and the package.json in the tree carries the placeholder version 0.0.0, which the publish workflow replaces at release time.
The real alternative to this library is the native BigInt type, and the difference is worth stating precisely. BigInt gives you arbitrary precision integers with correct arithmetic and bitwise operators over the full 64 bits, with no dependency at all. What it does not give you is a 32-bit split representation, an unsigned flag on a single value, or the small-value API surface of this class, where fromBytes, fromBits and the signed and unsigned getters are the point of the library.
So the decision rule is narrow. If you need arbitrary precision maths, use BigInt and forget about this package. If you need a value that matches a binary protocol, a database column, or a third-party implementation that stores a 64-bit integer as two 32-bit words, and you want that behaviour to be identical to the Closure original, long.js is the right tool and BigInt is not a substitute.
Editorial conclusion
long.js suits a developer working with a binary protocol, a database that stores 64-bit identifiers, or a wire format that splits an integer into low and high 32-bit words, and who wants the arithmetic to behave exactly like the Closure implementation it came from. It does not suit a project that simply needs big integers, since native BigInt does that without a dependency, and it does not suit a codebase that mixes ESM and CommonJS carelessly, because the two builds yield different classes. Before adopting, install long from npm, construct one value with Long.fromString and compare its toString output against the same value as a BigInt, and pin a version rather than tracking latest, since the newest tagged release is v5.3.2 from 2025-04-17.
Frequently asked questions
What is long.js and why does it exist?
long.js is a Long class for representing a 64-bit two's-complement integer, derived from the Closure Library for stand-alone use and extended with unsigned support. It exists because JavaScript numbers cannot represent integers beyond 2 to the power of 53 minus 1 and because bitwise operators only work on 32-bit ranges.
How do I install long.js?
Install it from npm with npm install long. The package exports an ECMAScript module with a UMD fallback, and the manifest uses a conditional exports map so both import and require resolve correctly.
How do I create a Long from a string or bytes?
Long.fromString parses a string in a given radix, fromBytes creates one from a byte representation with fromBytesLE and fromBytesBE for explicit endianness, fromBits takes the low and high 32-bit words directly, and fromValue converts a LongLike, which is a Long, number, bigint or string.
Why does mixing ESM and CommonJS cause problems with long.js?
The README warns that mixing ESM and CommonJS is not recommended because it yields different classes with the same functionality. The two builds produce separate class identities, so instanceof checks fail across the boundary even though the arithmetic agrees.
When should I use long.js instead of the native BigInt type?
Use long.js when a 64-bit value has to match a binary protocol or a storage format that splits it into two 32-bit words, or when you need its unsigned flag and signed and unsigned getters. For arbitrary precision arithmetic with no external format to match, native BigInt needs no dependency at all.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dcodeio-long-js)Was this useful?