在我们日常开发中,经常会遇到将一些具体数字提升转换为最高单位的做法。如阅读量多万时则以「万」为单位,并保留 2 位小数,过亿则以「亿」为单位等;还有文件大小超过1MB
后则以「MB」为单位,超过 1GB 后则「GB」为单位等。
就是当数据比较大,达到该领域的某常用单位时,我们需要将数据转为最高量级的单位。一方面是缩短数据的长度,再一个是对用户更加的直观。当数据太长时,用户就会对最后位置的数字不太敏感,更加关注当前数字处在一个什么样的量级。
我们以阅读量的数字转换为例,这个工具方法我们主要实现的要点有:
- 若数字在 1 万以内,则原样返回;
- 小数点后有 0 结尾时,省去该位置,如我们保留 1 位小数时得到的数字是「12.0 万」时,最终会展示为「12 万」;
- 若数字较大时,会一直提升到设定的最大量级,如过万则以万为单位,若过亿则以亿为单位;
分享下当数字达到某量级时转为该对应单位的工具方法:
/**
* 将数字提升到最高的量级
* @param num 要转换的数字
* @param config 配置
* @returns 返回转换后的数字
*/
const covertNumToMaxSize = (
num: number,
config?: {
decimal?: number; // 要保留几位小数,默认保留1位
strict?: boolean; // 是否严格保留小数位数,默认会舍弃小数点后面的0
unit?: boolean; // 是否需要单位,有的场景可能只是比大小,不需要单位,默认有单位
thousandth?: boolean; // 是否要千分位,只在万级以下的数字才会生效,默认true
sizes?: [number, string][]; // 要转换的基数和单位
}
) => {
if (typeof num !== "number") {
// 万一接口在数据为0时返回null或undefined等类型
return 0;
}
const decimal = config?.decimal ?? 1;
const strict = config?.strict ?? false;
const unit = config?.unit ?? true;
const thousandth = config?.thousandth ?? true;
const sizes = config?.sizes || [
[0, ""],
[1e4, "万"],
[1e8, "亿"],
[1e12, "兆"],
];
let i = 0;
// 判断数字处于哪个量级
for (; i < sizes.length - 1; i++) {
if (num < sizes[i + 1][0]) {
break;
}
}
if (sizes[i][0]) {
num /= sizes[i][0];
}
let result: number | string = num.toFixed(decimal);
if (!strict) {
// 非严格要求小数位数,则舍弃小数最后的0
result = Number(result);
}
if (thousandth) {
const [first, last] = String(result).split(".");
if (first.length > 3 || (last?.length || 0) > 3) {
// 将字符串添加千分位
result = String(result).replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, "$1,");
}
}
if (unit && sizes[i][1]) {
result += sizes[i][1];
}
return result;
};
我们在实现了数据的量级提升功能后,还加了一些配置的字段,方便灵活地返回我们需要的数据:
- 可以配置最多保留几位小数,默认保留 1 位;
- 是否保留小数点后的后置 0,默认是 false,会舍弃;
- 是否需要对应量级的单位,默认携带,可以配置为只返回数据;
- 是否需要转为千分位,默认转换;
- 可以自定义量级的基数和单位;
我们来测试几个数据,下面的测试用例全部通过:
import covertNumToMaxSize from "./index";
describe("test covertNumToMaxSize", () => {
test("should get zero when param is not number", () => {
expect(covertNumToMaxSize(null as any)).toBe(0);
expect(covertNumToMaxSize("234" as any)).toBe(0);
});
test("should return original num when not greater", () => {
expect(covertNumToMaxSize(123)).toBe(123);
});
test("should one decimal", () => {
expect(covertNumToMaxSize(1234567)).toBe("123.5万");
});
test("should give up last decimal zero", () => {
expect(covertNumToMaxSize(10345)).toBe("1万");
});
test("should get max size", () => {
expect(covertNumToMaxSize(1367892445)).toBe("13.7亿");
});
test("should get two decimal when last num is not zero", () => {
expect(covertNumToMaxSize(1234567, { decimal: 2 })).toBe("123.46万");
});
test("should get one decimal when last num is zero although config decimal is 2", () => {
expect(covertNumToMaxSize(1234017, { decimal: 2 })).toBe("123.4万");
});
test("should get two decimal although last num is zero when strict is true", () => {
expect(covertNumToMaxSize(1234017, { decimal: 2, strict: true })).toBe("123.40万");
});
test("should get thousandth", () => {
// 千分位
expect(covertNumToMaxSize(1234)).toBe("1,234");
});
test("should not get thousandth when config.thousandth is false", () => {
// 千分位
expect(covertNumToMaxSize(1234, { thousandth: false })).toBe(1234);
});
test("should not get unit when config.unit is false", () => {
expect(covertNumToMaxSize(1234017, { unit: false })).toBe(123.4);
});
test("diy sizes", () => {
expect(
covertNumToMaxSize(1234017, {
sizes: [
[0, ""],
[1024, "KB"],
[1024 * 1024, "MB"],
],
})
).toBe("1.2MB");
});
});
我们在测试了几种不同长度的数据后,均能满足我们的要求。
在文件大小的场景中转换单位时,原理是一样的。我们可以自定义配置中的sizes
,只不过基数变成了1024
,单位也从「万亿」变成了「MB」、「GB」等。