【问题标题】:Java: random long number in 0 <= x < n rangeJava:0 <= x < n 范围内的随机长数
【发布时间】:2010-03-30 14:41:21
【问题描述】:

Random 类具有在给定范围内生成随机 int 的方法。例如:

Random r = new Random(); 
int x = r.nextInt(100);

这将生成一个大于或等于 0 且小于 100 的 int 数。我想对 long 数做同样的事情。

long y = magicRandomLongGenerator(100);

Random 类只有 nextLong(),但它不允许设置范围。

【问题讨论】:

  • 相关,可能有用:stackoverflow.com/questions/2290057/…
  • 你有没有考虑过让你的长随机数和你的范围内的模式? (当然,如果范围只有 100,我会生成一个 int 随机数并将其转换为 long。)
  • java.util.Random 只使用 48 位分布(见实现细节),所以它不会有正态分布。
  • 在现代,可以考虑使用 org.apache.commons.lang3.RandomUtils#nextLong。

标签: java random range long-integer


【解决方案1】:

Java 7(或 Android API 级别 21 = 5.0+)开始,您可以直接使用 ThreadLocalRandom.current().nextLong(n)(对于 0 ≤ x ThreadLocalRandom.current().nextLong(m, n)(对于 m ≤ x @Alex的回答。


如果您使用 Java 6(或 Android 4.x),您需要使用外部库(例如 org.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1),请参阅 @mawaldne 的答案),或实现自己的nextLong(n).

根据https://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.htmlnextInt实现为

 public int nextInt(int n) {
     if (n<=0)
                throw new IllegalArgumentException("n must be positive");

     if ((n & -n) == n)  // i.e., n is a power of 2
         return (int)((n * (long)next(31)) >> 31);

     int bits, val;
     do {
         bits = next(31);
         val = bits % n;
     } while(bits - val + (n-1) < 0);
     return val;
 }

所以我们可以修改它来执行nextLong

long nextLong(Random rng, long n) {
   // error checking and 2^x checking removed for simplicity.
   long bits, val;
   do {
      bits = (rng.nextLong() << 1) >>> 1;
      val = bits % n;
   } while (bits-val+(n-1) < 0L);
   return val;
}

【讨论】:

  • 我在使用“2^x 检查”部分时遇到了一些问题。有什么想法吗?
  • @Vilius:2^x 检查只是使生成速度更快,因为直接使用rng.nextLong() % n 将给出统一的值(假设所有位都很好)。如果你愿意,你可以忽略那部分。
  • 如果我想要m &lt;= x &lt;= n,你会如何修改你的解决方案?
  • @BJPeterDeLaCruz:mn之间的随机数可以得到0n-m之间的随机数,然后加上m
【解决方案2】:

ThreadLocalRandom

ThreadLocalRandom 有一个nextLong(long bound) 方法。

long v = ThreadLocalRandom.current().nextLong(100);

如果您需要 0 以外的原点,它也有 nextLong(long origin, long bound)。传递原点(包括)和边界(不包括)。

long v = ThreadLocalRandom.current().nextLong(10,100); // For 2-digit integers, 10-99 inclusive.

SplittableRandom 具有相同的 nextLong 方法,如果您想要可重复的数字序列,则允许您选择种子。

【讨论】:

【解决方案3】:

在范围内生成数字的标准方法(没有实用方法)是只使用带范围的双精度数:

long range = 1234567L;
Random r = new Random()
long number = (long)(r.nextDouble()*range);

会给你一个介于 0(包括)和范围(不包括)之间的长整数。同样,如果您想要 x 和 y 之间的数字:

long x = 1234567L;
long y = 23456789L;
Random r = new Random()
long number = x+((long)(r.nextDouble()*(y-x)));

将为您提供从 1234567(含)到 123456789(不含)的长整数

注意:请检查括号,因为转换为 long 的优先级高于乘法。

【讨论】:

  • 我的第一个想法就是这个。但这似乎有点不雅。而且我担心分布的均匀性(不是我真的需要它,我只是想把它做对)
  • 请不要使用这个。输出根本不统一。
  • 最大的问题是四舍五入会使最低位非常不均匀。此外,bound 必须小于可以编码为双精度的最大整数 2^53。
【解决方案4】:

上述方法效果很好。如果您使用的是 apache commons (org.apache.commons.math.random),请查看 RandomData。它有一个方法:nextLong(long lower, long upper)

http://commons.apache.org/math/userguide/random.html

http://commons.apache.org/math/api-1.1/org/apache/commons/math/random/RandomData.html#nextLong(long,%20long)

【讨论】:

【解决方案5】:

使用“%”运算符

resultingNumber = (r.nextLong() % (maximum - minimum)) + minimum;

通过使用“%”运算符,我们会在除以您的最大值时取余数。这让我们只剩下从 0(含)到除数(不含)的数字。

例如:

public long randLong(long min, long max) {
    return (new java.util.Random().nextLong() % (max - min)) + min;
}

【讨论】:

  • 这很好,但你应该检查if (max == min)
  • 还要检查if (nextLong() &gt;= 0)
  • 仅供参考:这并不总是给出均匀分布,而且对于一些大范围来说真的很糟糕。例如,如果min = 0max = 2 * (MAX_LONG / 3),那么您在[0, MAX_LONG / 3] 中获得值的可能性是在[MAX_LONG / 3, 2 * (MAX_LONG / 3)] 中获得值的两倍。
  • 此代码不起作用。如果nextLong返回负值,则余数为负,值超出范围。
【解决方案6】:

进一步改进 kennytm 的回答:考虑到 Java 8 中的实际实现的子类实现将是:

public class MyRandom extends Random {
  public long nextLong(long bound) {
    if (bound <= 0) {
      throw new IllegalArgumentException("bound must be positive");
    }

    long r = nextLong() & Long.MAX_VALUE;
    long m = bound - 1L;
    if ((bound & m) == 0) { // i.e., bound is a power of 2
      r = (bound * r) >> (Long.SIZE - 1);
    } else {
      for (long u = r; u - (r = u % bound) + m < 0L; u = nextLong() & Long.MAX_VALUE);
    }
    return r;
  }
}

【讨论】:

  • 我知道这是一个旧答案,不太可能使用,但这部分显然是不正确的:if ((bound &amp; m) == 0) { r = (bound * r) &gt;&gt; (Long.SIZE - 1); } 首先,通过单元测试很容易证明这实际上不会产生范围内的数字[0,绑定)。其次,它是不必要的复杂:r = r &amp; m 将实现预期的结果,这基本上是当前 Java 8 实现所做的。编写此答案时,实现可能有所不同,但不可能是所显示的。
【解决方案7】:

如果您想要一个在 [0,m) 范围内的均匀分布的伪随机数,请尝试使用模运算符和绝对值方法结合nextLong() 方法,如下所示:

Math.abs(rand.nextLong()) % m;

rand 是您的 Random 对象。

模运算符将两个数字相除并输出这些数字的余数。例如,3 % 21,因为 3 和 2 的余数是 1。

由于nextLong() 在 [-(2^48),2^48) 范围内(或该范围内的某个位置)生成一个均匀分布的伪随机数,因此您需要取它的绝对值。如果不这样做,nextLong() 方法的模数有 50% 的机会返回负值,该值超出范围 [0,m)。

您最初要求的是一个长度在 [0,100) 范围内的均匀分布的伪随机数。下面的代码就是这样做的:

Math.abs(rand.nextLong()) % 100;

【讨论】:

【解决方案8】:

这个怎么样:

public static long nextLong(@NonNull Random r, long min, long max) {
    if (min > max)
        throw new IllegalArgumentException("min>max");
    if (min == max)
        return min;
    long n = r.nextLong();
    //abs (use instead of Math.abs, which might return min value) :
    n = n == Long.MIN_VALUE ? 0 : n < 0 ? -n : n;
    //limit to range:
    n = n % (max - min);
    return min + n;
}

?

【讨论】:

  • 没问题,除了属于框架的部分(我猜)。
【解决方案9】:

以下方法将返回一个介于 10000000000 到 9999999999 之间的值

long min = 1000000000L
long max = 9999999999L    

public static long getRandomNumber(long min, long max){

    Random random = new Random();         
    return random.nextLong() % (max - min) + max;

}

【讨论】:

  • 当我重置 long min = 1L;最大长 = 10L;生成的随机数超出最大值!
  • 应该是 random.nextLong() % (max - min) + min;
【解决方案10】:

来自 Java 8 API

API 文档 https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long- 获取实际实现可能会更容易 他们正在使用它来生成多头流。而且您的来源可以像问题中一样为“0”。

long nextLong(long origin, long bound) {
  long r = nextLong();
  long n = bound - origin, m = n - 1;
  if ((n & m) == 0L)  // power of two
    r = (r & m) + origin;
  else if (n > 0L) {  // reject over-represented candidates
    for (long u = r >>> 1;            // ensure nonnegative
         u + m - (r = u % n) < 0L;    // rejection check
         u = nextLong() >>> 1) // retry
        ;
    r += origin;
  }
  else {              // range not representable as long
    while (r < origin || r >= bound)
      r = nextLong();
  }
  return r;
}

【讨论】:

    【解决方案11】:

    来自Random上的页面:

    方法 nextLong 由 Random 类实现,好像是:

    public long nextLong() {
       return ((long)next(32) << 32) + next(32);
    }
    

    因为 Random 类使用只有 48 位的种子,所以该算法不会返回所有可能的 long 值。

    因此,如果您想获得 Long,您已经无法获得完整的 64 位范围。

    我建议,如果您的范围接近 2 的幂,您可以像在那个 sn-p 中那样构建 Long,如下所示:

    next(32) + ((long)nextInt(8) << 3)
    

    例如获得 35 位范围。

    【讨论】:

    • 但是文档说“所有 2^64 个可能的长值都是以(大约)相等的概率产生的。”所以显然 nextLong() 方法应该返回所有可能的值。顺便说一句,种子的长度与值的分布有什么关系?
    【解决方案12】:

    使用r.nextDouble()的方法应该使用:

    long number = (long) (rand.nextDouble()*max);
    
    
    long number = x+(((long)r.nextDouble())*(y-x));
    

    【讨论】:

      【解决方案13】:
      public static long randomLong(long min, long max)
      {
          try
          {
              Random  random  = new Random();
              long    result  = min + (long) (random.nextDouble() * (max - min));
              return  result;
          }
          catch (Throwable t) {t.printStackTrace();}
          return 0L;
      }
      

      【讨论】:

      • 你不应该在 hoc 中创建 Random 实例,如果不需要,你不应该捕获 Throwables 或其他异常,你应该使用某种日志框架(即 SLF4J)记录错误而不是使用printStackTrace
      【解决方案14】:

      如果你可以使用 java 流,你可以尝试以下方法:

      Random randomizeTimestamp = new Random();
      Long min = ZonedDateTime.parse("2018-01-01T00:00:00.000Z").toInstant().toEpochMilli();
      Long max = ZonedDateTime.parse("2019-01-01T00:00:00.000Z").toInstant().toEpochMilli();
      randomizeTimestamp.longs(generatedEventListSize, min, max).forEach(timestamp -> {
        System.out.println(timestamp);
      });
      

      这将生成给定范围内的长整数。

      【讨论】:

        【解决方案15】:
        import java.util*;
        
            Random rnd = new Random ();
            long name = Math.abs(rnd.nextLong());
        

        这应该可以工作

        【讨论】:

          【解决方案16】:

          //使用系统时间作为种子值,得到一个好的随机数

             Random random = new Random(System.currentTimeMillis());
                        long x;
                       do{
                          x=random.nextLong();
                       }while(x<0 && x > n); 
          

          //循环直到得到一个大于等于0且小于n的数

          【讨论】:

          • 这可能非常无能。如果n 是 1,或者说是 2,会怎样?循环将执行 许多 次迭代。
          猜你喜欢
          • 2010-10-20
          • 2021-12-02
          • 2011-10-20
          • 1970-01-01
          • 1970-01-01
          • 2015-09-16
          • 1970-01-01
          • 2016-03-24
          • 2011-12-16
          相关资源
          最近更新 更多