【问题标题】:Will I possibly loose any decimal digits (precision) when multiplying Number.MAX_SAFE_INTEGER by Math.random()?将 Number.MAX_SAFE_INTEGER 乘以 Math.random() 时,我可能会丢失任何十进制数字(精度)吗?
【发布时间】:2021-08-30 21:24:40
【问题描述】:

在 JavaScript 中将 Number.MAX_SAFE_INTEGER 乘以 Math.random() 时,我可能会丢失任何十进制数字(精度)吗?

我想我不会,但最好能有一个可信的解释来解释为什么????

已编辑,通俗地说,我们正在处理两个IEEE 754 双精度浮点数,一个是最大整数(用于双精度),另一个是小数小数点后有不少数字。如果(比如说)我先将它们转换为quadruple-precision format,然后相乘,然后再将乘积转换回双精度,结果会有所不同吗?

const max = Number.MAX_SAFE_INTEGER;
const random = Math.random();
console.log(`\
MAX_SAFE_INTEGER: ${max}, \
random: ${random}, \
product: ${max * random}`);

对于更详细的示例,我使用它来生成BigInt random numbers

【问题讨论】:

    标签: javascript math random numbers


    【解决方案1】:

    两个答案都是正确的,但我忍不住在 C# 中运行了这个小实验,其中 double 与 JavaScript 中的 Number 相同(fiddle):

    using System;
    
    public class Program
    {
      public static void Main()
      {
        const double MAX_SAFE_INT = 9007199254740991;
        Decimal maxD = Convert.ToDecimal(MAX_SAFE_INT.ToString());
        var rng = new Random(Environment.TickCount);
        for (var i = 0; i < 1000; i++) 
        { 
            double random = rng.NextDouble();
            double product = MAX_SAFE_INT * random;
          
            // converting via string to workaround the "15 significant digits" limitation for Decimal(Double)
            Decimal randomD = Decimal.Parse(String.Format("{0:F18}", random));
            
            Decimal productD = maxD * randomD;
            double converted = Convert.ToDouble(productD);
            if (Math.Floor(converted) != Math.Floor(product)) 
            {
              Console.WriteLine($"{maxD}, {randomD, 22}, products: decimal {productD, 32}, converted {converted, 20}, original {product, 20}");
            }
        }
      }
    }
    

    就我而言,我仍然可以在0 - 9007199254740991 范围内获得所需的随机数分布。

    这是一个JavaScript playground code,用于检查可能的重复事件。

    【讨论】:

      【解决方案2】:

      如果(比如说)我先将它们转换为四精度格式,然后相乘,然后再将乘积转换回双精度,结果会有所不同吗?

      这可能是在将两个双精度相乘与将四进制转换为双精度之间舍入行为不同的情况下,但主要问题仍然相同。从 2n 到 2n+1 范围内的可表示双精度之间的间距为 2n−52。所以在 252 和 253 之间只能表示整数,在 251 和 252 之间只能表示每个0.5可以表示等。

      如果您想要更精确,可以尝试decimal.js。该库包含在该文档页面中,因此您可以在控制台中尝试这些。

      Number.MAX_SAFE_INTEGER*.9
      8106479329266892
      new Decimal(Number.MAX_SAFE_INTEGER).mul(new Decimal(0.9)).toString()
      "8106479329266891.9"
      

      【讨论】:

      • 谢谢,嘿,很高兴收到你的来信@rjschnorenberg!当然,你是对的 :) 我已经用 C# 验证了这一点,其中double 由相同的binary64 格式表示:dotnetfiddle.net/m6HKuL
      【解决方案3】:

      您的实现应该是安全的——理论上,如果实现Math.random 的引擎使用完全无偏的算法,0 到 MAX_SAFE_INTEGER 之间的所有数字都应该有出现的可能性。

      但规范不能保证绝对无偏的算法 - 选择的数字是随机的,而不是真正的、完全的随机的。 (这样的事情甚至存在吗?这是值得商榷的......)现代版本 V8 和一些其他实现 use an algorithm 具有大约 2 ** 128 的句点,大于 MAX_SAFE_INTEGER (2 ** 53 - 1) - 但其他实现(尤其是较旧的实现)具有更小的周期是完全合理的,导致范围内的某些整数比其他实现更频繁地被选择。

      如果这对您的脚本很重要(我认为这在大多数情况下不太可能),您可能考虑使用 higher-quality random generatior 而不是 Math.random - 但几乎可以肯定不是值得担心。

      【讨论】:

      • 我可以接受重新出现的数字,但简单来说,我很担心我将一个用 IEEE-754 表示的大整数乘以另一个非常小的小数。因此,我认为我可能会失去一些精度。你说不是这样吗?我承认我需要阅读IEEE-754 format,距离我的大学时代已经有一段时间了:)
      • 所有数字将具有 52 位精度,无论大小、小数或非小数,当对数字进行运算时,该精度将延续(并且 然后 不那么重要数字被丢弃),所以你应该没问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-28
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-03
      相关资源
      最近更新 更多