Java:在一个范围内生成一个随机双精度(包括该范围的最小值和最大值)

时间:2017-04-11 16:58:31

标签: java

我需要生成-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的输出,所以除非我的测试不够大,否则我看不出它是如何包含两端的。

7 个答案:

答案 0 :(得分:4)

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

由于double的巨大精确度,这意味着获得任何精确给定的随机double的可能性是天文数字的。

您可以将该随机数发出数十亿次,并且您可能会获得数千个非常接近,非常接近极限的值,但它们中没有一个可能恰好等于极限。

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

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

答案 1 :(得分:3)

由于您希望-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()的值的大小来更多小数位,并相应地更改最后一行中的值。

答案 2 :(得分:1)

您当前的公式包括下限,但不包括上限。要解决此问题,您可以将Double.MIN_VALUE添加到上限,以创建新的最大值。这会稍微改变您的界限,因此您实际上要排除新的最大值。 [max,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;
}

答案 3 :(得分:0)

当我遇到类似的问题时,在最小值和最大值之间获取一些随机浮点值,并且发现没有可用的解决方案,因此我想发布我的文章。谢谢萨桑为我提供了正确的想法!

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

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;
    }
}

示例: 以最小= -0.10和最大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

答案 4 :(得分:0)

如果你能忍受 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 之间的随机浮点数,包括两端。

这样的测试片段
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 << 48) - 1)
return (int)(seed >>> (48 - bits))

(但实际上也可以找到,比如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 对带有幻数的种子进行异或运算,可以对其进行测试:

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 等)

答案 5 :(得分:-1)

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

编辑:(我不小心阅读文档,所有版本都是从包含 - 到独家)

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);
    }
}

}

答案 6 :(得分:-2)

您使用的公式将为您提供范围下限的随机值包含,但上限的独占,因为:

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