6

This question: How to generate a random BigInteger describes a way to achieve the same semantics as Random.nextInt(int n) for BigIntegers.

I would like to do the same for BigDecimal and Random.nextDouble().

One answer in the above question suggests creating a random BigInteger and then creating a BigDouble from it with a random scale. A very quick experiment shows this to be a very bad idea :)

My intuition is that using this method would require the integer to be scaled by something like n-log10(R), where n is the number of digits of precision required in the output and R is the random BigInteger. This should allow the correct number of digits to be present so that (for example) 1 -> 10^-64 and 10^64 -> 1.

The scaling value also needs to be chosen correctly for the result to fall in the range [0,1].

Has anyone done this before, and do they know if the results are correctly distributed? Is there a better way to achieve this?

EDIT: Thanks to @biziclop for correcting my understanding of the scale argument. The above isn't necessary, a constant scale factor has the desired effect.

For later reference, my (apparently working code) is:

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;
}
Community
  • 1
  • 1
Kothar
  • 6,579
  • 3
  • 33
  • 42

3 Answers3

2

It's surely very easy... if I only knew what you want. For a uniformly distributed number in range [0, 1) and precision N decimal digits generate a uniform BigInteger less than 10*N and scale it down by 10*N.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • It was the "random scale" part of the original answer that was wrong. This method should be fine. – DJClayworth Feb 04 '11 at 16:41
  • You can create a uniform BigInteger less than 10^N buy creating many integers which are [0, 10^m) and combining them. – Peter Lawrey Feb 04 '11 at 16:48
  • Right, but there's a corresponding question with a nice answer linked in the first sentence of this question. Your proposal may work well, too, especially with m=9 so random.nextInt() may be used. – maaartinus Feb 04 '11 at 16:58
2

I made a post about generating a random BigInteger Andy Turner's answer about generating a random BigInteger. I don't use this directly for generating a random BigDecimal. Essentially my concern is to use independent instances of Random to generate each digit in a number. One problem I noticed is that with Random there are only so many values of and particular number that you get in a row. Also the generation tries to maintain something of an even distribution of generated values. My solution depends on something storing an array or collection of Random instances and calling these. I think this is a good way of going about it and I am trying to find out, so am interested if anyone has any pointers or criticism of this approach.

/**
 *
 * @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;
}
Community
  • 1
  • 1
  • I don't understand what you mean by "with Random there are only so many values of [a] particular number that you get in a row". According to the Java source for Random, "The general contract of next is that it returns an int value and if the argument bits is between 1 and 32 (inclusive), then that many low-order bits of the returned value will be ... independently chosen bit values, each of which is ... equally likely to be 0 or 1." This seems to imply to me that there is no limit to how many identical values you get in a row, it's just increasingly unlikely, as you would expect. – Kothar Feb 17 '11 at 16:22
1

I might be missing the obvious here but how about creating two random BigIntegers, one being the integer part, and the other the fractional? Obviously the range of the "fractional" bigint would be dictated by the precision you want to allow, which you can't get away from pinning down.

Update: This can be further simplified to work with just one random bigint. If you want a random number between 0 and n with k decimal precision (where k is a constant), you just generate a random number between 0 and n*10^k and divide it by 10^k.

biziclop
  • 48,926
  • 12
  • 77
  • 104
  • The result of doing this is not uniformly distributed. I tried this, and the result is uniformly distributed across the fractional part, which means that 10^-27 is just as likely as a number between 0.01 and 0.1 to appear in the results. 10^-27 should be 26 or so orders of magnitude less likely to appear than a number in the range 0.1-0.01 – Kothar Feb 04 '11 at 16:25
  • @Mike Houston I am missing the obvious then, because I still don't get it. Do you want it to be uniformly distributed or not? – biziclop Feb 04 '11 at 16:28
  • @Mike Houston Nope, still don't get it. If you take an evenly distributed variable that is at most n digits long and you divide it by 10^n, it still is evenly distributed. – biziclop Feb 04 '11 at 16:31
  • Yes, I want it to be uniformly distributed. Uniform distribution of the exponent is not the same as uniform distribution of the absolute value. e.g. for a uniformly distributed random variable X, p(0 < X < 0.5) = p(0.5 < X < 1.0) = 0.5. p(0 < X < 0.1) = p(0.5 < X < 0.6) = 0.1. With uniform distribution of the exponent, this doesn't hold any more since p(0 < X < 0.01) ~= p(0.1 < X < 0.2), i.e., a much smaller slice of the number line has the same probability as a larger slice elsewhere in the number line. – Kothar Feb 04 '11 at 16:37
  • @Mike Houston I understand that perfectly, I just don't see how it relates to my answer, which was basically a uniformly distributed variable divided by a constant. – biziclop Feb 04 '11 at 16:39
  • scale doesn't divide the input integer, it says how many decimal places to shift the point. I.e new BigDecimal(1, -3) yields 0.001 and new BigDecimal(101, -3) yeilds 0.00101. – Kothar Feb 04 '11 at 16:41
  • @Mike Houston No, it isn't. `new BigDecimal( new BigInteger( "101" ), 3 ) == 0.101` – biziclop Feb 04 '11 at 16:46
  • OK, that may be the case, but dividing one random integer by another does not create a uniform distribution, which was the original problem. – Kothar Feb 04 '11 at 16:52
  • @Mike Houston Of course it doesn't, that's why I never suggested such a thing. Anyway, have a look at the other answer then, which essentially says the same. – biziclop Feb 04 '11 at 17:04
  • In that case, I am sorry, that is what I understood you meant by creating two random BigIntegers. – Kothar Feb 04 '11 at 17:10
  • @Mike Houston No worries, it was my fault really. I could've included an example and it would've been clear what I meant. – biziclop Feb 04 '11 at 17:25