J007-大整数传输为何禁用Long类型

status
Published
slug
J007
type
Post
category
Technology
date
Feb 27, 2025 → Mar 2, 2025
tags
笔记
J计划
summary
关于「禁止服务端在超大整数下使用Long类型作为返回」的解释和分析
💡
博客阅读笔记,原文链接:https://mp.weixin.qq.com/s/ftG0sRBLkKfwXQas7Ee0uQ
阿里巴巴Java开发手册的规约内容:
[强制] 对于需要使用超大整数的场景,服务端一律使用String字符串类型返回,禁止使用Long类型。

使用Long类型返回存在哪些问题?

IEEE754浮点数标准

为目前业界流行的浮点数标准
该标准规定了4种浮点数类型:
  • 单精度
  • 双精度:分配8个字节,共64位
    • 从左至右划分为:
      • 1位符号:最高二进制位,0表示正数,1表示负数
      • 11位指数:阶码位,即指数对应的移码
        • 根据IEEE754规定,偏移量为2^(n-1)-1,因此双精度下偏移量为1023,e的取值范围为[-1022, 1023]
        • 什么是移码(Excess-N Code)?
          • 一种用于表示有符号数的编码方式,通过将数值偏移一个固定的量,使其转换为非负数来表示。
          • 公式:[x]移=x+2^(n-1)(n为x的二进制位数,含符号位)
          • 简化计算/便于比较:移码大真值也大,使用移码后不再需要考虑负号
      • 52位有效数字:尾数位,以原码表示
  • 延伸单精度
  • 延伸双精度

浮点数和十进制之间的转换

  1. 规格化:
    1. 指数位不是全0,且不是全1时,有效数字的最高位前默认增加1,不占用任何比特位
      计算公式如下:
  1. 非规格化
    1. 指数位为全0时,有效数字的最高位前默认为0
      计算公式如下:
  1. 特殊值
    1. 指数全为1,有效数字全为0时,代表无穷大;有效数字不为0时,代表NaN(非数字)

问题解答

  1. JS的Number类型能安全表达的最大整型数值是多少?
根据IEEE754的规定,在浮点数规格化的情况下,双精度浮点数的有效数字为52位,有效数字最高位默认为1,因此JS的Number能保障无精度损失表达的最大整数是2^53
  • 但由于2^53的表示可以是9007199254740992、9007199254740993
  • 因此答案为2^53-1
  1. 在Long取值范围内,2的指数次整数转换为JS的Number类型,不会有精度丢失,但能放心使用么?
规约中指出:在Long取值范围内,任何2的指数次整数都是绝对不会存在精度损失的,对于Long的转换而言精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数。
为了确保程序准确,在整数场景下,JS的Number类型在使用时应严格限制在2^53-1以内,最好还是按照规约直接使用String类型。
精度丢失示例:
原因分析:
  • 计算机中存储的浮点数是一个离散的集合,两个浮点数中间还有无数的自然数无法使用计算机表达
  • 因此当浮点数输出的时候,计算机会在浮点数相关范围内,选择一个最合适的自然数作为输出
  • JS的浮点输出算法中36028797018963970和36028797018963968是相等的
  1. 我们一般都知道十进制数转二进制浮点数有可能会出现精度丢失,精度丢失怎么发生的?
  • 转换结果是无限循环数或无理数
    • 有很多数字在使用二进制表示时,只能近似表示,无法精确表达
    • 例如1/3、1/7
  • 转换结果长度超过有效数字位数,超过部分会舍弃
    • 浮点数加减运算执行步骤如下:
      • 零值检测 -> 对阶操作 -> 尾数求和 -> 结果规格化 -> 结果舍入
    • 对阶和规格化都有可能造成精度损失
      • 对阶:是通过尾数右移(左移会导致高位被移出,误差更大,所以只能是右移),将小指数改成大指数,达到指数阶码对齐的效果,而右移出的位,会作为保护位暂存,在结果舍入中处理,这一步有可能导致精度丢失。
      • 规格化:是为了保障计算结果的尾数最高位是1,视情况有可能会出现右规,即将尾数右移,从而导致精度丢失。
  1. 如果不幸中招,服务端正在使用Long类型作为大整数的返回,有哪些办法解决?
    1. 通过Web的ajax异步接口,以Json串的形式返回给前端
        • 方案一:如果返回的Long型所在的POJO对象在其他地方无使用,将后端的Long型修改为String型
        • 方案二:如果返回给前端的JSON串是将一个POJO对象JSON序列化而来,并且这个POJO对象还在其他地方使用,可以使用如下方法:
          • SerializerFeature.BrowserCompatible 可以自动将数值变成字符串返回,解决精度问题。
        • 方案三:如果上述的两种方式都不合适,需要后端返回一个新的String类型,前端使用新的,并后续上线后下掉老的Long型(推荐使用该方式,因为可以明确使用String型,防止后续误用Long型)。
    2. 使用node的方式,直接通过调用后端接口的方式获取
        • 方案一:使用npm的JS-2-java的java.Long(orderId)方法兼容
        • 方案二:后端接口返回一个新的String类型的订单ID,前端使用新的属性字段

© Shansan 2021 - 2025