2023-08-24 JS/TS number 格格不入


imo: TypeScript number 类型定义模糊,JavaScript number 精度非主流,ECMAScript bigint 限制超多。JS/TS number 与其他编程语言显得格格不入。

不要妄想 TypeScript 可以拯救 JavaScript number,TypeScript 的 number 类型因为需要兼容 JavaScript,所以也没有 int32 int64 等细分类型,数值的精度范围也继承了 JavaScript 埋的坑。🤷

分享几个我遇到的问题以及解决方案:

1. JS/TS number 全身都是 bug

JS/TS number 因为要同时兼容 int float double 导致它的精度很迷,与强类型工具(如 RDB, protobuf)的数值范围上有差异,非常容易产生 bug。

JS 最大安全整数(能够精确表示的整数)为 9,007,199,254,740,991(253 - 1),远小于 int64 (263 - 1)。所以如果你在 RDB 中使用 int64 类型,在 JS 中就会溢出。

Example: 264-1 is 18446744073709551615 but in JavaScript it evaluates to 18446744073709552000.

使用 long.js 可以弥补这个设计缺陷。

但是 long.js 也有问题,个人建议能不用就别用。

2. JS/TS bigint 限制超多

bigint 虽然是整数,但是在使用上有很多限制,例如:

  1. BigInt 与 Number 转换会损失精度
  2. BigInt 在 JSON.stringify() 会引发 TypeError
  3. BigInt 不能使用 Math 中方法

天呐🤷,这么多限制,还不容不用呢。反正我从来不用 bigint。

如果真的需要处理超大数值,我推荐使用 bignumber.js,我可不想自己写数值运算,bignumber.js 提供了很多开箱即用的方法:

bignumber.js
bignumber.js

3. int32 int64 对应 TS 类型没有统一标准

Prisma scheme 支持 BigInt 类型,对应 TS BigInt 类型

Protocol Buffers 的 int32 int64 类型分别对应 TS number 和 Long

如果你同时使用 Prisma BigInt 和 protobuf int64 的话,你带代码中就会出现大量相互转换的 utils,类似下面这种:

👇这些代码都是我从其他人的代码中 copy 过来的,不是我写的,我也永远不用写这种代码。
export class BigIntUtil {
  static toNumber(b: bigint): number {
    const n = parseInt(b.toString());
    if (!Number.isSafeInteger(n)) {
      throw new Error(
        'Fail to cast bigint to number. Value is out of safe integer range.',
      );
    }
    return n;
  }

  static from(v: string | number | bigint): bigint {
    if (typeof v === 'bigint') {
      return v;
    }
    return BigInt(v);
  }
}
import Long from 'long';

export class LongUtil {
  public static fromBigInt(num: bigint): Long;
  public static fromBigInt(num: bigint | undefined | null): Long | undefined;
  public static fromBigInt(num: bigint | undefined | null): Long | undefined {
    if (num === undefined || num === null) {
      return undefined;
    }
    return Long.fromNumber(Number(num));
  }

  public static toBigInt(num: Long): bigint;
  public static toBigInt(num: Long | undefined | null): bigint | undefined;
  public static toBigInt(num: Long | undefined | null): bigint | undefined {
    if (num === undefined || num === null) {
      return undefined;
    }
    return BigInt(num.toNumber());
  }
}
export class JsonUtil {
  public static stringify(data: Record<string, any>): string {
    return JSON.stringify(data, (k, v) => {
      if (typeof v === 'bigint') {
        return v.toString();
      }
      return v;
    });
  }
}

你有关于 JS/TS number 的故事吗?欢迎与我分享。

refs: