获取JavaScript数字的精确十进制表示

4

在 JavaScript 中,每个有限数字都有一个确切的实际值。例如:

const x = Number.MAX_VALUE

这里,x 的精确值为 21024 - 2971 =

179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368

我们可以通过使用 x 进行算术运算来证明这一点:

console.log(x % 10000) // 8368

但是我该如何获得所有这些小数位数呢?

我希望解决方案也适用于非整数,例如const y = Number.EPSILON 精确到了2-52 =

0.0000000000000002220446049250313080847263336181640625


1
在 JavaScript 中,超过 2^52 的任何数都无法表示而不会失去精度。 - Felix Kling
1
@FelixKling - 不是这样的。浮点数可以精确表示的值是不会失去精度的。而且OP正在询问那些可以精确表示的值。 - Oliver Charlesworth
@OliverCharlesworth:我明白你的意思。 - Felix Kling
2
如果需要的话,toString(2)会给你二进制表示;实际上,对于任何 x(除了 10),toString(x) 都会返回一个没有用科学计数法书写的结果。我不认为有内置的方法可以获得该表示。 - Felix Kling
啊,这可能已经足够好了!将 '0b' + x.toString(2) 传递给一个大十进制库,如 Decimal.js,然后从那里输出可能会解决问题。 - qntm
2个回答

2
我发现最有效的方法是:
  • 将浮点数写入ArrayBuffer,然后将其作为一个64位无符号BigInt读回来
  • 使用位运算提取符号、指数和尾数
  • 通过乘以10的一个大幂次方计算所需的结果
  • 使用字符串操作在格式化的BigInt中插入正确位置的小数点。
例如:
const SIGN_BITS = 1n
const EXPONENT_BITS = 11n
const MANTISSA_BITS = 52n
const BIAS = 1023n

export const stringify = value => {
  if (typeof value !== 'number') {
    throw Error('Not a number')
  }

  if (!Number.isFinite(value)) {
    return String(value)
  }

  const dataView = new DataView(new ArrayBuffer(8))
  dataView.setFloat64(0, value)
  const bigUint64 = dataView.getBigUint64(0)

  const mantissaBits = (bigUint64 >> 0n) & ((1n << MANTISSA_BITS) - 1n)
  const exponentBits = (bigUint64 >> MANTISSA_BITS) & ((1n << EXPONENT_BITS) - 1n)
  const signBits = (bigUint64 >> (MANTISSA_BITS + EXPONENT_BITS)) & ((1n << SIGN_BITS) - 1n)

  const sign = signBits === 0b0n ? '' : '-'

  const isSubnormal = exponentBits === 0b0n

  // So as to keep this in integers, multiply the fraction by 2 ** 52 while subtracting
  // that same power from the exponent
  const m = ((isSubnormal ? 0n : 1n) << MANTISSA_BITS) + mantissaBits
  const e = (isSubnormal ? 1n : exponentBits) - BIAS - MANTISSA_BITS

  if (e >= 0n) {
    // Pure integers, no problem
    return sign + String(m << e)
  }

  // Multiply by a large enough power of 10 that all possible decimal digits are preserved
  // when we then divide by the power of 2
  const power10 = 10n ** -e
  const f = (m * power10) >> -e
  const pre = f / power10
  const post = f % power10

  if (post === 0n) {
    return sign + String(pre)
  }

  return sign + String(pre) + '.' + String(post).padStart(Number(-e), '0').replace(/0+$/, '')
}

console.log(stringify(Number.MAX_VALUE))

这会输出:

179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368

类似的:
console.log(stringify(Number.EPSILON))

outputs:

0.0000000000000002220446049250313080847263336181640625


-2

你误解了浮点数。

由于没有比2^52更高的精度,因此您最多可以获得约14位数字。

最大数字是一个由三部分组成的数字。

  • 符号,正或负,为1位。因此,您会得到0和-0
  • 分数52位,给出约14位数字的精度。
  • 指数11位,是带符号的值,是将分数值乘以的数量。2 ^ exponent

有关更多信息,请参见维基Double-precision_floating-point_format


浮点数并不是不精确的。浮点数算术有其局限性,因为结果会四舍五入到最接近的可用浮点数,但这不是我所询问的内容。 - qntm
似乎OP对浮点数非常了解。浮点数值是精确的,OP正在询问如何获得这些值的精确十进制表示。 - Oliver Charlesworth
@OliverCharlesworth 1.7976931348623157e+308是double类型的最大值,保留到第291位小数。在64位中,无法表示超过2^64个状态。数字 Number.MAX_VALUE - 1 是无意义的,即 Number.MAX_VALUE - 1 === Number.MAX_VALUENumber.MAX_VALUE - 100000000 === Number.MAX_VALUE 均为真。要改变这种情况,你最多可以尝试1e300。Double类型的精度不能超过2^52,请阅读我提供的维基百科了解更多信息。 - Blindman67
@qntm MAX_VALUE不是一个精确的值,你可能会这样读取它,但它并不代表这个意思。 - Blindman67
哪个参数?浮点数值对应于哪个数学表达式?还是说该表达式定义了一个唯一的实数?前者在你引用的维基百科文章中有提到,后者仅仅是数学的表现方式 :) - Oliver Charlesworth
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接