Created
August 6, 2020 18:43
-
-
Save g4rcez/2d5426e41213afc970f025a329b4e2be to your computer and use it in GitHub Desktop.
This file contains hidden or 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
type Locale = { | |
decimal: "."; | |
thousands: ","; | |
grouping: [3]; | |
currency: ["$", ""]; | |
minus: "-"; | |
numerals?: string; | |
percent?: string; | |
nan?: string; | |
}; | |
const re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; | |
const map = Array.prototype.map; | |
const prefixes = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"]; | |
function formatDecimal(x: any, p?: number) { | |
if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; | |
var i; | |
const coefficient = x.slice(0, i); | |
return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +x.slice(i + 1)]; | |
} | |
const exponent = (x: any) => { | |
return (x = formatDecimal(Math.abs(x))), x ? x[1] : NaN; | |
}; | |
const formatGroup = (grouping: any, thousands: any) => (value: any, width: number) => { | |
const t = []; | |
let i = value.length; | |
let j = 0; | |
let g = grouping[0]; | |
let length = 0; | |
while (i > 0 && g > 0) { | |
if (length + g + 1 > width) g = Math.max(1, width - length); | |
t.push(value.substring((i -= g), i + g)); | |
if ((length += g + 1) > width) break; | |
g = grouping[(j = (j + 1) % grouping.length)]; | |
} | |
return t.reverse().join(thousands); | |
}; | |
const identity = (x: number) => x; | |
const formatNumerals = (numerals: string[]) => (value: string) => value.replace(/[0-9]/g, (i) => numerals[+i]); | |
const formatRounded = (x: number, p: number) => { | |
var d = formatDecimal(x, p); | |
if (!d) { | |
return x + ""; | |
} | |
const coefficient = d[0]; | |
const exponent = d[1]; | |
return exponent < 0 | |
? "0." + new Array(-exponent).join("0") + coefficient | |
: coefficient.length > exponent + 1 | |
? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) | |
: coefficient + new Array(exponent - coefficient.length + 2).join("0"); | |
}; | |
let prefixExponent: any; | |
function formatPrefixAuto(x: number, p: number) { | |
let d = formatDecimal(x, p); | |
if (!d) { | |
return x + ""; | |
} | |
let coefficient = d[0], | |
exponent = d[1], | |
i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, | |
n = coefficient.length; | |
return i === n | |
? coefficient | |
: i > n | |
? coefficient + new Array(i - n + 1).join("0") | |
: i > 0 | |
? coefficient.slice(0, i) + "." + coefficient.slice(i) | |
: "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))![0]; | |
} | |
const formatTypes = { | |
"%": (x: number, p: number) => (x * 100).toFixed(p), | |
b: (x: number) => Math.round(x).toString(2), | |
c: (x: number) => x + "", | |
d: (x: number) => Math.round(x).toString(10), | |
e: (x: number, p: number) => x.toExponential(p), | |
f: (x: number, p: number) => x.toFixed(p), | |
g: (x: number, p: number) => x.toPrecision(p), | |
o: (x: number) => Math.round(x).toString(8), | |
p: (x: number, p: number) => formatRounded(x * 100, p), | |
r: formatRounded, | |
s: formatPrefixAuto, | |
X: (x: number) => Math.round(x).toString(16).toUpperCase(), | |
x: (x: number) => Math.round(x).toString(16), | |
}; | |
class FormatSpecifier { | |
public fill: string; | |
public align: string; | |
public sign: string; | |
public symbol: string; | |
public zero: boolean; | |
public width?: number; | |
public comma: boolean; | |
public precision?: number; | |
public trim: boolean; | |
public type: string; | |
constructor(specifier: any) { | |
this.fill = specifier.fill === undefined ? " " : specifier.fill + ""; | |
this.align = specifier.align === undefined ? ">" : specifier.align + ""; | |
this.sign = specifier.sign === undefined ? "-" : specifier.sign + ""; | |
this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + ""; | |
this.zero = !!specifier.zero; | |
this.width = specifier.width === undefined ? undefined : +specifier.width; | |
this.comma = !!specifier.comma; | |
this.precision = specifier.precision === undefined ? undefined : +specifier.precision; | |
this.trim = !!specifier.trim; | |
this.type = specifier.type === undefined ? "" : specifier.type + ""; | |
} | |
} | |
function formatTrim(s: string) { | |
out: for (var n = s.length, i = 1, i0 = -1, i1: number; i < n; ++i) { | |
switch (s[i]) { | |
case ".": | |
i0 = i1 = i; | |
break; | |
case "0": | |
if (i0 === 0) i0 = i; | |
i1 = i; | |
break; | |
default: | |
if (!+s[i]) break out; | |
if (i0 > 0) i0 = 0; | |
break; | |
} | |
} | |
return i0 > 0 ? s.slice(0, i0) + s.slice(i1! + 1) : s; | |
} | |
function formatSpecifier(specifier: any) { | |
if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); | |
var match; | |
//@ts-ignore | |
return new FormatSpecifier({ | |
fill: match[1], | |
align: match[2], | |
sign: match[3], | |
symbol: match[4], | |
zero: match[5], | |
width: match[6], | |
comma: match[7], | |
precision: match[8] && match[8].slice(1), | |
trim: match[9], | |
type: match[10], | |
} as any); | |
} | |
const fmtLocale = (locale: Locale) => { | |
var group = | |
locale.grouping === undefined || locale.thousands === undefined | |
? identity | |
: formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), | |
currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", | |
currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", | |
decimal = locale.decimal === undefined ? "." : locale.decimal + "", | |
numerals = locale.numerals === undefined ? identity : formatNumerals(map.call(locale.numerals, String) as string[]), | |
percent = locale.percent === undefined ? "%" : locale.percent + "", | |
minus = locale.minus === undefined ? "-" : locale.minus + "", | |
nan = locale.nan === undefined ? "NaN" : locale.nan + ""; | |
function newFormat(str: any) { | |
const specifier = formatSpecifier(str); | |
let fill = specifier.fill; | |
let align = specifier.align; | |
let sign = specifier.sign; | |
let symbol = specifier.symbol; | |
let zero = specifier.zero; | |
let width = specifier.width; | |
let comma = specifier.comma; | |
let precision = specifier.precision; | |
let trim = specifier.trim; | |
let type = specifier.type; | |
if (type === "n") (comma = true), (type = "g"); | |
else if (!(formatTypes as any)[type]) precision === undefined && (precision = 12), (trim = true), (type = "g"); | |
if (zero || (fill === "0" && align === "=")) (zero = true), (fill = "0"), (align = "="); | |
let prefix = | |
symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "", | |
suffix = symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : ""; | |
let formatType = (formatTypes as any)[type], | |
maybeSuffix = /[defgprs%]/.test(type); | |
precision = | |
precision === undefined | |
? 6 | |
: /[gprs]/.test(type) | |
? Math.max(1, Math.min(21, precision)) | |
: Math.max(0, Math.min(20, precision)); | |
function format(value: any) { | |
let valuePrefix = prefix; | |
let valueSuffix = suffix; | |
let i; | |
let n; | |
let c; | |
if (type === "c") { | |
valueSuffix = formatType(value) + valueSuffix; | |
value = ""; | |
} else { | |
value = +value; | |
let valueNegative = value < 0 || 1 / value < 0; | |
value = isNaN(value) ? nan : formatType(Math.abs(value), precision); | |
if (trim) value = formatTrim(value); | |
if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; | |
valuePrefix = | |
(valueNegative ? (sign === "(" ? sign : minus) : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; | |
valueSuffix = | |
(type === "s" ? prefixes[8 + prefixExponent / 3] : "") + | |
valueSuffix + | |
(valueNegative && sign === "(" ? ")" : ""); | |
if (maybeSuffix) { | |
(i = -1), (n = value.length); | |
while (++i < n) { | |
if (((c = value.charCodeAt(i)), 48 > c || c > 57)) { | |
valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; | |
value = value.slice(0, i); | |
break; | |
} | |
} | |
} | |
} | |
if (comma && !zero) value = group(value, Infinity); | |
let length = valuePrefix.length + value.length + valueSuffix.length; | |
let padding = length < width! ? new Array(width! - length + 1).join(fill) : ""; | |
if (comma && zero) | |
(value = group(padding + value, padding.length ? width! - valueSuffix.length : Infinity)), (padding = ""); | |
switch (align) { | |
case "<": | |
value = valuePrefix + value + valueSuffix + padding; | |
break; | |
case "=": | |
value = valuePrefix + padding + value + valueSuffix; | |
break; | |
case "^": | |
value = | |
padding.slice(0, (length = padding.length >> 1)) + | |
valuePrefix + | |
value + | |
valueSuffix + | |
padding.slice(length); | |
break; | |
default: | |
value = padding + valuePrefix + value + valueSuffix; | |
break; | |
} | |
return numerals(value as never); | |
} | |
format.toString = () => specifier + ""; | |
return format; | |
} | |
function formatPrefix(specifier: FormatSpecifier, value: number) { | |
let f = newFormat(((specifier = formatSpecifier(specifier)), (specifier.type = "f"), specifier)), | |
e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, | |
k = Math.pow(10, -e), | |
prefix = prefixes[8 + e / 3]; | |
return (value: number) => f(k * value) + prefix; | |
} | |
return { | |
format: newFormat, | |
formatPrefix: formatPrefix, | |
}; | |
}; | |
const localeFormatter = fmtLocale({ | |
decimal: ".", | |
thousands: ",", | |
grouping: [3], | |
currency: ["$", ""], | |
minus: "-", | |
}); | |
export const siFormat = (n: number) => localeFormatter.format("~s")(n); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment