我该如何在Java中创建一个随机的BigDecimal?

6
这个问题:如何生成一个大整数随机数描述了一种实现与Random.nextInt(int n)具有相同语义的方法,适用于BigIntegers。
我想对于BigDecimal和Random.nextDouble()实现相同的功能。
上述问题的一个答案建议创建一个随机BigInteger,然后使用随机比例创建一个BigDouble。快速实验表明这是一个非常糟糕的想法 :)
我直觉上认为使用此方法需要将整数按n-log10(R)的比例缩放,其中n是所需输出精度的数字位数,而R是随机BigInteger。这应该允许正确数量的数字存在,以便(例如)1-> 10^-64和10^64-> 1。
缩放值还需要正确选择,以使结果落在[0,1]范围内。
是否有人以前做过这个,并且他们知道结果是否正确分布? 是否有更好的方法来实现这一点?
编辑:感谢@biziclop纠正了我的比例参数的理解。上面的方法不必要,恒定的比例因子可以产生期望的效果。
供以后参考,我的(显然有效的)代码是:
private static BigDecimal newRandomBigDecimal(Random r, int precision) {
    BigInteger n = BigInteger.TEN.pow(precision);
    return new BigDecimal(newRandomBigInteger(n, r), precision);
}

private static BigInteger newRandomBigInteger(BigInteger n, Random rnd) {
    BigInteger r;
    do {
        r = new BigInteger(n.bitLength(), rnd);
    } while (r.compareTo(n) >= 0);

    return r;
}
3个回答

2
我曾经发过一篇关于生成随机BigInteger的帖子,链接如下:Andy Turner提供的生成随机BigInteger的答案。但是我不直接使用它来生成随机BigDecimal,我的问题在于要使用独立的Random实例来生成数字中的每个数字。我注意到的一个问题是,使用Random时,有一定数量的特定数字连续出现的次数是有限的。此外,该生成器试图保持生成值的均匀分布。我的解决方案依赖于存储Random实例数组或集合,并调用这些实例。我认为这是一个很好的方法,我正在尝试找出更多信息,如果有人对这种方法有任何指针或批评,请告诉我。
/**
 *
 * @param a_Random
 * @param decimalPlaces
 * @param lowerLimit
 * @param upperLimit
 * @return a pseudo randomly constructed BigDecimal in the range from
 * lowerLimit to upperLimit inclusive and that has up to decimalPlaces
 * number of decimal places
 */
public static BigDecimal getRandom(
        Generic_Number a_Generic_Number,
        int decimalPlaces,
        BigDecimal lowerLimit,
        BigDecimal upperLimit) {
    BigDecimal result;
    BigDecimal range = upperLimit.subtract(lowerLimit);
    BigDecimal[] rangeDivideAndRemainder =
            range.divideAndRemainder(BigDecimal.ONE);
    BigInteger rangeInt = rangeDivideAndRemainder[0].toBigIntegerExact();
    BigInteger intComponent_BigInteger = Generic_BigInteger.getRandom(
            a_Generic_Number,
            rangeInt);
    BigDecimal intComponent_BigDecimal =
            new BigDecimal(intComponent_BigInteger);
    BigDecimal fractionalComponent;
    if (intComponent_BigInteger.compareTo(rangeInt) == 0) {
        BigInteger rangeRemainder =
                rangeDivideAndRemainder[1].toBigIntegerExact();
        BigInteger fractionalComponent_BigInteger =
                Generic_BigInteger.getRandom(a_Generic_Number, rangeRemainder);
        String fractionalComponent_String = "0.";
        fractionalComponent_String += fractionalComponent_BigInteger.toString();
        fractionalComponent = new BigDecimal(fractionalComponent_String);
    } else {
        fractionalComponent = getRandom(
                a_Generic_Number, decimalPlaces);
    }
    result = intComponent_BigDecimal.add(fractionalComponent);
    result.add(lowerLimit);
    return result;
}

/**
 * Provided for convenience.
 * @param a_Generic_BigDecimal
 * @param decimalPlaces
 * @return a random BigDecimal between 0 and 1 inclusive which can have up
 * to decimalPlaces number of decimal places
 */
public static BigDecimal getRandom(
        Generic_Number a_Generic_Number,
        int decimalPlaces) {
    //Generic_BigDecimal a_Generic_BigDecimal = new Generic_BigDecimal();
    Random[] random = a_Generic_Number.get_RandomArrayMinLength(
            decimalPlaces);
    //System.out.println("Got Random[] size " + random.length);
    String value = "0.";
    int digit;
    int ten_int = 10;
    for (int i = 0; i < decimalPlaces; i++) {
        digit = random[i].nextInt(ten_int);
        value += digit;
    }
    int length = value.length();
    // Tidy values ending with zero's
    while (value.endsWith("0")) {
        length--;
        value = value.substring(0, length);
    }
    if (value.endsWith(".")) {
        value = "0";
    }
    BigDecimal result = new BigDecimal(value);
    //result.stripTrailingZeros();
    return result;
}

我不明白你所说的“使用Random时,某个特定数字连续出现的次数是有限制的”是什么意思。根据Java中Random的源代码,“next方法的一般契约是返回一个int值,如果参数bits在1到32之间(包括1和32),则返回值的那些低位将是...独立选择的位值,每个位值都有相等的可能性是0或1。”这似乎意味着你连续获得多少个相同的值并没有限制,只是越来越不可能,这也是你所期望的。 - Kothar

2

如果我知道你想要什么,那么这肯定非常容易。要在[0,1)范围内生成一个均匀分布的数字,精度为N位小数,并生成一个小于10*N的均匀BigInteger,然后将其缩小10*N


原回答中错误的是“随机比例”部分。这种方法应该没问题。 - DJClayworth
你可以通过创建许多整数并将它们组合起来,来创建一个小于10^N的统一BigInteger。这些整数应该在[0, 10^m)范围内。 - Peter Lawrey
没错,但是这个问题的第一句话中有一个相关的问题,并且有一个很好的答案链接。您的建议也可能很有效,特别是当m=9时可以使用random.nextInt()。 - maaartinus

1

我可能会错过显而易见的地方,但是如何创建两个随机的BigInteger,一个是整数部分,另一个是小数部分呢?显然,“小数”大整数的范围将由您想要允许的精度决定,您无法避免固定下来。

更新:这可以进一步简化为仅使用一个随机大整数即可。如果您想要在0和n之间生成具有k位小数精度(其中k是常数)的随机数字,则只需生成介于0和n*10^k之间的随机数字,并将其除以10^k。


这样做的结果并不是均匀分布的。我尝试过,结果在小数部分上是均匀分布的,这意味着10^-27出现在结果中的可能性与0.01到0.1之间的数字一样大。10^-27应该比0.1-0.01范围内的数字少出现26个数量级左右。 - Kothar
@Mike Houston 我完全理解,但我不明白它如何与我的答案相关,我的答案基本上是一个均匀分布的变量除以一个常数。 - biziclop
@Mike Houston 不是这样的。new BigDecimal(new BigInteger("101"), 3) == 0.101 - biziclop
当然不会,这就是为什么我从未建议过这样的事情。无论如何,请看看另一个答案,它本质上是相同的。 - biziclop
@Mike Houston 没关系,这真的是我的错。我本可以包含一个例子,这样大家就能清楚明白我的意思了。 - biziclop
显示剩余6条评论

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