-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add formatting implementation, with a unittest for thousands separators
- Loading branch information
Showing
2 changed files
with
114 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); | ||
}); |