Skip to content

Instantly share code, notes, and snippets.

@g4rcez
Created August 6, 2020 18:43
Show Gist options
  • Save g4rcez/2d5426e41213afc970f025a329b4e2be to your computer and use it in GitHub Desktop.
Save g4rcez/2d5426e41213afc970f025a329b4e2be to your computer and use it in GitHub Desktop.
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