Skip to content

Commit

Permalink
Basic BigInt support
Browse files Browse the repository at this point in the history
We should not crash when passed a bigint. This patch will make it so that
the formatter will convert the bigint to a number if possible, else it will
emit an overflow string (#####). A setting has been added so that instead
of the overflow string, a string version of the bigint is emitted instead.
This is similar how date overflows are handled.

I have made an attempt at supporting bigint throughout the library with
some success, but I have abandoned that because it was complicating the
code a lot and I had some worries about performance.

I think partial support would be possible with low cost. But I don't
have the resources to spare for that right now.

Closes #61
  • Loading branch information
borgar committed Feb 26, 2025
1 parent 1087ec5 commit ff29b5d
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 7 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"newline-per-chained-call": "off"
},
"globals": {
"BigInt": true,
"console": true,
"Set": true,
"require": true,
Expand Down
7 changes: 4 additions & 3 deletions lib/formatNumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function formatColor (value, parseData, opts) {
const parts = parseData.partitions;
let part = parts[3];
let color = null;
if (typeof value === 'number' && isFinite(value)) {
if ((typeof value === 'number' || typeof value === 'bigint') && isFinite(value)) {
part = getPart(value, parts);
}
if (part && part.color) {
Expand All @@ -62,11 +62,12 @@ export function formatValue (value, parseData, opts) {
if (value == null) {
return '';
}
if (typeof value !== 'number') {
const n = typeof value === 'bigint';
if (typeof value !== 'number' && !n) {
return runPart(value, text_part, opts, l10n);
}
// guard against non-finite numbers:
if (!isFinite(value)) {
if (!n && !isFinite(value)) {
const loc = l10n || defaultLocale;
if (isNaN(value)) { return loc.nan; }
return (value < 0 ? loc.negative : '') + loc.infinity;
Expand Down
3 changes: 3 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ function prepareFormatterData (pattern, shouldThrow = false) {
* @param {boolean} [options.dateErrorNumber=true]
* Should the formatter switch to a General number format when trying to
* format a date that is out of bounds?
* @param {boolean} [options.bigintErrorNumber=false]
* Should the formatter switch to a plain string number format when trying to
* format a bigint that is out of bounds?
* @param {boolean} [options.dateSpanLarge=true]
* Extends the allowed range of dates from Excel bounds (1900–9999) to
* Google Sheet bounds (0–99999).
Expand Down
4 changes: 3 additions & 1 deletion lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ export const defaultOptions = {
overflow: '######', // dateErrorThrow needs to be off! [prev in locale]
// Should it throw when there is an overflow error?
dateErrorThrows: false,
// Should it emit a number is an overflow error? (Sheets does this)
// Should it emit a number when date has an overflow error? (Sheets does this)
dateErrorNumber: true, // dateErrorThrow needs to be off!
// Should it emit a number when bigint has an is an overflow error?
bigintErrorNumber: false,
// Sheets mode (see #3)
dateSpanLarge: true,
// Simulate the Lotus 1-2-3 leap year bug
Expand Down
16 changes: 15 additions & 1 deletion lib/runPart.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,21 @@ export function runPart (value, part, opts, l10n_) {
let integer = '';
let exp = 0;

let date = value | 0;
let date = 0;
if (typeof value === 'bigint') {
if (value <= Number.MAX_SAFE_INTEGER && value >= Number.MIN_SAFE_INTEGER) {
value = Number(value);
}
else {
return opts.bigintErrorNumber
? String(value)
: opts.overflow;
}
date = value;
}
else {
date = Math.trunc(value);
}
let time = 0;
let year = 0;
let month = 1;
Expand Down
30 changes: 30 additions & 0 deletions test/bigint-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import test from './utils.js';

test('bigint', t => {
t.format('0', Number.MAX_SAFE_INTEGER, String(Number.MAX_SAFE_INTEGER));
t.format('0', 10n, '10');

t.format('General', 10n, '10');
t.format('General', 9007199254740991n, '9.0072E+15');

t.format('0.0', 9007199254740991n, '9007199254740990.0');

t.format('#,##0.0', 9007199254740991n, '9,007,199,254,740,990.0');
t.format('#,##0.0', 9007199254750000n, '######');
t.format('#,##0.0', -9007199254750000n, '######');

t.format('#0-000-00', 9007199254750000n, '######');
t.format('0%', 9007199254750000n, '######');

t.format('#0-000-00', 9007199254750000n, '9007199254750000', { bigintErrorNumber: true });
t.format('0%', 9007199254750000n, '9007199254750000', { bigintErrorNumber: true });

// preferably we should support bigint throughout:
// t.format('#0-000-00', 9007199254750000n, '90071992547-500-00');
// t.format('0%', 9007199254750000n, '900719925475000000%');

t.format('0.000E+00', 999990000, '1.000E+09');
t.format('0.000E+00', 999990000n, '1.000E+09');

t.end();
});
4 changes: 2 additions & 2 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// tests converted from SSF
import test, { Test } from 'tape';
import { format, formatColor, getFormatDateInfo, getFormatInfo } from '../lib/index.js';
import fs from 'fs';
Expand Down Expand Up @@ -84,7 +83,8 @@ Test.prototype.runTable = function runSSFTable (pathToTable) {

function formatMessage (pattern, value, options) {
let message = pattern;
message += '\x1b[36m <=> ' + value + '';
const suffix = (typeof value === 'bigint') ? 'n' : '';
message += '\x1b[36m <=> ' + value + suffix;
const o = JSON.stringify(options);
if (o !== '{}') {
message += '\x1b[2m\x1b[33m [ OPTIONS=' + o + ' ]';
Expand Down

0 comments on commit ff29b5d

Please sign in to comment.