【问题标题】:Java: Generating a random double within a range (inclusive of the min and max to the range)Java:在范围内生成随机双精度(包括范围的最小值和最大值)
【发布时间】:2017-09-07 04:50:27
【问题描述】:

我需要生成介于 -0.10 和 0.25 之间(包括 -0.10 和 0.25)的随机双精度值。我还没有完全理解 java 的随机数生成器,所以我不太确定如何做到这一点。我知道下面的代码会在该范围内生成一个数字,但我几乎 100% 确定它还没有包含在内。如何将其更改为包含范围的最小值和最大值?

public double randDouble() {
    //formula: (Math.random() * (max - min) + min
    //in this case, min = -0.10 and max = 0.25
    return (Math.random() * 0.35) - 0.10;
}

我的问题与@javaguy 所说的不同,因为在该线程上没有人说如何在两端都具有包容性。我已经测试了这段代码,但没有看到 -.10 或 0.25 的输出,所以除非我的测试不够大,否则我看不出它是如何包含两端的。

【问题讨论】:

标签: java


【解决方案1】:

由于您想要介于 -0.10 和 0.25 之间的值,我建议您采用不同的方法来控制随机值的粒度。使用 Math.random() 将返回 0.0(包括)和 1.0(不包括)之间的值,所以使用你的方法你永远不会得到 0.25 的值。而是使用 Random() 类并使用整数来使两端都包含在内。

Random randomValue = new Random();
int randomInt = randomValue.nextInt(3501) - 1000; //Will get integer values between -1000 and 2500
Double rangedValue = Double(randomInt)/10000.0 // Get values between -0.10 and 0.25 

或者,您可以通过增加传递给 randomValue.nextInt() 的值的大小并相应地更改最后一行中除以的值来获得更多小数位。

【讨论】:

  • 一方面,这可能被认为是作弊。另一方面,它可能被认为是对 OP 问题的务实/实用的解决方案。
【解决方案2】:

您获取包含限制的随机 double 值的计划是错误的,因为无法保证您将收到等于任一限制的值。

这是由于double 的巨大精度,这意味着获得任何精确给定随机double 的可能性微乎其微。

你可以让这个随机数发出数十亿次,你可能会得到数千个非常接近、非常接近极限的值,但它们中没有一个恰好等于极限。

因此,您不能有任何依赖于所发出的等于限制的随机双精度数的逻辑,因为该随机数可能永远不会产生。

因此,您的问题的解决方案非常简单:不要再担心包容性与排他性了,因为您所希望的一切都是排他性的。这应该会让你的事情变得更简单。

【讨论】:

  • 我正要回答类似的问题,我认为这是问题的正确答案
【解决方案3】:

您当前的公式包括下限,但不包括上限。要解决此问题,您可以将 Double.MIN_VALUE 添加到上限,创建一个新的最大值。这会稍微改变您的界限,以便您实际上想要排除新的最大值。您的旧最大值包含在 [min, newMax) 中。

public double randDouble() {
    //formula: Math.random() * (max + Double.MIN_VALUE - min) + min
    //in this case, min = -0.10 and max = 0.25
    return (Math.random() * (0.35 + Double.MIN_VALUE)) - 0.10;
}

【讨论】:

    【解决方案4】:

    当我遇到类似的问题时,我在最小值和最大值之间获得了一些随机浮点值,但没有找到可以使用的解决方案,我想发布我的。感谢 Sasang 为我提供了正确的想法!

    最小值和最大值包括在内,但如果范围或精度很大,则不太可能发生。如果您对该函数有不同的用例,您也可以将精度用作参数。

    public class MyRandom {
    
        private static Random randomValue = new Random();
    
        public static double randomFloat(double min, double max) {
            double precision = 1000000D;
            double number = randomValue.nextInt((int) ((max - min) * precision + 1)) + min * precision;
            return number / precision;
        }
    }
    

    示例: 使用 min = -0.10 和 max 0.25 和精度 1000000 调用生成的输出如下:

    -0.116965
    0.067249
    0.246948
    -0.180695
    -0.033533
    0.08214
    -0.053864
    0.216388
    -0.158086
    0.05963
    0.168015
    0.119533
    

    【讨论】:

      【解决方案5】:

      如果你可以忍受float 和大约 800 万步,你可以简单地丢弃几乎一半的数字(比一半的数字少一个):

      private static Random rnd=new Random();
      public static float next0to1() {
        float f;
        do {
          f=rnd.nextFloat();
        } while(f>0.5);
        return f*2;
      }
      

      此函数将生成 0 到 1 之间的随机浮点数,包括两端。
      一个测试sn-p之类的

      long start=System.currentTimeMillis();
      int tries=0;
      while(next0to1()!=0)
        tries++;
      System.out.println(tries);
      while(next0to1()!=1)
        tries++;
      System.out.println(tries);
      System.out.println(System.currentTimeMillis()-start);
      

      或更长的,带有您的实际数字和一些额外的检查

      long start=System.currentTimeMillis();
      int tries=0;
      float min=-0.1f;
      float max=0.25f;
      tries=0;
      float current;
      do {
        tries++;
        current=min+next0to1()*(max-min);
        if(current<min)
          throw new RuntimeException(current+"<"+min);
        if(current>max)
          throw new RuntimeException(current+">"+max);
      } while(current!=min);
      System.out.println(tries);
      do {
        tries++;
        current=min+next0to1()*(max-min);
        if(current<min)
          throw new RuntimeException(current+"<"+min);
        if(current>max)
          throw new RuntimeException(current+">"+max);
      } while(current!=max);
      System.out.println(tries);
      System.out.println(System.currentTimeMillis()-start);
      

      通常会显示几千万次尝试生成 0 和 1,对于我使用 5 年的笔记本电脑来说,在不到一秒的时间内完成。

      旁注:通常尝试超过 800 万次是正常的:虽然 nextFloat() 生成 24 位,通过丢弃几乎一半的数字减少到 ~23,生成器本身在 48 位上工作。

      你可以用Random 做的最好的仍然是nextInt(),如Sasang 的回答所示。可用范围为2^30:

      static double next0to1() {
        return rnd.nextInt(0x40000001)/(double)0x40000000;
      }
      

      这需要更多的时间(分钟)并尝试(数十亿,tries 最好更改为long)来生成 0 和 1。


      Random.nextDouble(),或“破解”随机数

      nextDouble() 需要比生成器单步生成的精度更高的精度,而是将 26 位和 27 位数字组合在一起:

      public double nextDouble() {
        return (((long)next(26) << 27) + next(27))
          / (double)(1L << 53);
      }
      

      next() 被描述为

      种子 = (种子 * 0x5DEECE66DL + 0xBL) & ((1L 返回(int)(种子>>>(48位))

      (但实际上也可以找到,比如https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/Random.java#L183

      这意味着为了生成 0,a=next(26) 和连续的b=next(27) 都必须返回 0,所以

      seeda=00000000 00000000 00000000 00xxxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
      seedb=00000000 00000000 00000000 000xxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
      

      从更新中:

      seedb = (seeda * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
      

      它可以在一瞬间被暴力破解(400 万种可能性)seeda 必须是什么:

      long mask = ((long) ((1 << 27) - 1)) << 21;
      System.out.println(Long.toBinaryString(mask));
      for (long i = 0; i < 1 << 22; i++)
        if (((i * 0x5DEECE66DL + 0xBL) & mask) == 0)
          System.out.println(i);
      

      循环在低 22 位运行(我们知道其余部分为零),mask11111111 11111111 11111111 11100000 00000000 00000000,用于检查下一个种子的相关 27 位是否为零。

      所以seeda=0

      接下来的问题是是否存在之前的seedx来生成seeda,所以

      seeda = (seedx * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
      

      只是这一次我们不知道这些位,所以暴力破解将无济于事。但是这种方程是一个实际的东西,叫做congruence relation,是可以解的。

      0 "=" seedx * 0x5DEECE66DL + 0xBL (mod 2^48)
      

      WolframAlpha 需要 Ax "=" B (mod C) 的形式,并且是十进制的,所以输入是

      25214903917x          <- 0x5DEECE66D
      281474976710656       <- 2^48
      -11                   <- 0xB, went to the other side
      

      一种可能的解决方案是 107048004364969。学习/知道Random XOR-s 与幻数的种子,可以测试:

      double low=-0.1,
             high=0.25;
      Random rnd=new Random(0x5DEECE66DL ^ 107048004364969L);
      System.out.println(low+(high-low)*rnd.nextDouble()==low);
      

      将导致true。所以是的,Random.nextDouble() 可以生成精确的 0。

      下一部分是0.5:

      seeda=10000000 00000000 00000000 00xxxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
      seedb=00000000 00000000 00000000 000xxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
      

      seeda 的第 48 位设置为 1。
      暴力循环来了:

      long mask = ((long) ((1 << 27) - 1)) << 21;
      System.out.println(Long.toBinaryString(mask));
      for (long i = 0; i < 1 << 22; i++)
        if ((((i + 0x800000000000L) * 0x5DEECE66DL + 0xBL) & mask) == 0)
          System.out.println(i);
      

      糟糕,没有解决方案。 5DEECE66D 有它的最低位设置(它是一个奇数),所以当我们在 0x80 中正好有一个位设置为 1 时......,乘法后它将保持为 1 - 当然,如果我们尝试移动它,这也适用右边一位)。
      TL;DR:Random.nextDouble()(因此Math.random())永远不会生成精确的 0.5。(或 0.25、0.125 等)

      【讨论】:

        【解决方案6】:

        您也可以使用 Streams,但它是 [fromInclusive, toExclusive)。 检查以下示例,一个使用 Streams,另一个使用 Random:

        已编辑:(我没有仔细阅读文档,所有版本都是从Inclusive-toExclusive)

        public class MainClass {
        public static void main(String[] args) {
            generateWithStreams(-0.10, 0.25);
            generateWithoutStreams(-0.10, 0.25);
        }
        
        private static void generateWithStreams(double fromInclusive, double toExclusive) {
            new Random()
                    .doubles(fromInclusive, toExclusive)
                    // limit the stream to 100 doubles
                    .limit(100)
                    .forEach(System.out::println);
        }
        
        private static void generateWithoutStreams(double fromInclusive, double toExcusive) {
            Random random = new Random();
        
            // generating 100 doubles
            for (int index = 0; index < 100; index++) {
                System.out.println(random.nextDouble() * (toExcusive - fromInclusive) + fromInclusive);
            }
        }
        

        }

        【讨论】:

          【解决方案7】:

          您使用的公式将为您提供随机值 inclusive 范围的下限,但 exclusive 的上限,因为:

          Math.random() 返回一个伪随机双精度大于或等于 0.0 且小于 1.0

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-07-04
            • 2020-07-18
            • 1970-01-01
            • 2012-04-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-22
            相关资源
            最近更新 更多