Skip to content

Commit

Permalink
Add formatting implementation, with a unittest for thousands separators
Browse files Browse the repository at this point in the history
  • Loading branch information
dsagal committed Jul 6, 2020
1 parent b8c4ec8 commit 437c24b
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
64 changes: 64 additions & 0 deletions lib/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {toFixed} from './index';

interface FormatOptions {
minDecimals: number;
maxDecimals: number;
percent?: boolean; // Format as percentage
grouping?: string; // String for thousand separators
accounting?: boolean; // Use parentheses for negative numbers
exponential?: boolean; // Use exponential notation
currency?: string; // Symbol for currency prefix
}

type FormatFunc = (num: number) => string;

// TODO need separate tests for addGrouping, and core createFormatFunc; should find assorted test
// cases for it.
// Add benchmarks comparing to Intl and numeraljs.
export function createFormatFunc(options: FormatOptions): FormatFunc {
const {minDecimals, maxDecimals, grouping, currency} = options;
if (options.exponential) {
return (num: number) => num.toExponential(maxDecimals);
}
let func: FormatFunc;
if (grouping) {
func = (num: number) => addGrouping(toFixed(num, minDecimals, maxDecimals), grouping);
} else {
func = (num: number) => toFixed(num, minDecimals, maxDecimals);
}
if (options.accounting) {
const func1 = func;
func = (num: number) => num < 0 ? `(${func1(-num)})` : func1(num);
}
if (options.percent) {
const func1 = func;
func = (num: number) => func1(num * 100) + '%';
} else if (options.currency) {
const func1 = func;
if (options.accounting) {
func = (num: number) => currency + func1(num);
} else {
func = (num: number) => num < 0 ? '-' + currency + func1(-num) : currency + func1(num);
}
}
return func;
}

/**
* Add thousands separators to the input number, which should be in the format /-?\d*(.\d*)?/
* (e.g. as returned by toFixed).
*/
export function addGrouping(input: string, sep: string = ','): string {
let decimalDot = input.indexOf('.');
let start = input[0] === '-' ? 1 : 0;
let end = decimalDot < 0 ? input.length : decimalDot;
let digits = end - start;
let firstGroup = (digits + 2) % 3 + 1; // Mod 3, but returning a value between 1 and 3.
let p = start + firstGroup;
let res = input.slice(0, p);
for ( ; p < end; p += 3) {
res += sep + input.slice(p, p + 3);
}
res += input.slice(p);
return res;
}
50 changes: 50 additions & 0 deletions test/test-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {assert} from 'chai';
import {toFixed} from '../lib/index';
import {addGrouping} from '../lib/format';

describe('format', () => {
describe('addGrouping', function() {
it('should add thousand separators correctly', function() {
assert.equal(addGrouping(''), '');
assert.equal(addGrouping('0'), '0');
assert.equal(addGrouping('10'), '10');
assert.equal(addGrouping('100'), '100');
assert.equal(addGrouping('1000'), '1,000');
assert.equal(addGrouping('1234'), '1,234');
assert.equal(addGrouping('12345'), '12,345');
assert.equal(addGrouping('123456'), '123,456');
assert.equal(addGrouping('1234567'), '1,234,567');

// Same as above, with a minus sign.
assert.equal(addGrouping('-0'), '-0');
assert.equal(addGrouping('-10'), '-10');
assert.equal(addGrouping('-100'), '-100');
assert.equal(addGrouping('-1000'), '-1,000');
assert.equal(addGrouping('-1234'), '-1,234');
assert.equal(addGrouping('-12345'), '-12,345');
assert.equal(addGrouping('-123456'), '-123,456');
assert.equal(addGrouping('-1234567'), '-1,234,567');

// Same as above, with decimals, assorted minus signs.
assert.equal(addGrouping('-0.1234567'), '-0.1234567');
assert.equal(addGrouping('.1234567'), '.1234567');
assert.equal(addGrouping('-.1234567'), '-.1234567');
assert.equal(addGrouping('-10.1234567'), '-10.1234567');
assert.equal(addGrouping('100.1234567'), '100.1234567');
assert.equal(addGrouping('-1000.1234567'), '-1,000.1234567');
assert.equal(addGrouping('1234.1234567'), '1,234.1234567');
assert.equal(addGrouping('-12345.1234567'), '-12,345.1234567');
assert.equal(addGrouping('123456.1234567'), '123,456.1234567');
assert.equal(addGrouping('-1234567.1234567'), '-1,234,567.1234567');

// Very large numbers
assert.equal(addGrouping(toFixed(6.0015e20, 0, 20)), '600,150,000,000,000,000,000');
});

it('should support custom separators', function() {
assert.equal(addGrouping(toFixed(6.0015e20, 0, 20), "'"), "600'150'000'000'000'000'000");
assert.equal(addGrouping(toFixed(6.0015e20, 0, 20), " "), "600 150 000 000 000 000 000");
assert.equal(addGrouping('-1234567.1234567', ' '), '-1 234 567.1234567');
});
});
});

0 comments on commit 437c24b

Please sign in to comment.