Как я могу создать случайный BigDecimal в Java?

Этот вопрос: Как создать случайный BigInteger описывает способ достижения той же семантики, что и Random.nextInt(int n) для BigIntegers.

Я хотел бы сделать то же самое для BigDecimal и Random.nextDouble().

Один из ответов на приведенный выше вопрос предлагает создать случайный BigInteger, а затем создать из него BigDouble со случайным масштабом. Очень быстрый эксперимент показывает, что это очень плохая идея :)

Моя интуиция подсказывает, что использование этого метода потребует, чтобы целое число было масштабировано примерно на n-log10(R), где n — количество цифр точности, требуемой в выходных данных, а R — это случайное BigInteger. Это должно обеспечить правильное количество цифр, чтобы (например) 1 -> 10^-64 и 10^64 -> 1.

Значение масштабирования также необходимо выбрать правильно, чтобы результат попадал в диапазон [0,1].

Кто-нибудь делал это раньше, и знают ли они, правильно ли распределяются результаты? Есть ли лучший способ добиться этого?

EDIT: Спасибо @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;
}

person Mike Houston    schedule 04.02.2011    source источник


Ответы (3)


Это, конечно, очень просто... если бы я только знал, чего ты хочешь. Для равномерно распределенного числа в диапазоне [0, 1) и точности N десятичных цифр сгенерируйте универсальный BigInteger меньше 10*N и уменьшите его на 10*N.

person maaartinus    schedule 04.02.2011
comment
Это была часть случайного масштаба исходного ответа, которая была неправильной. Этот метод должен подойти. - person DJClayworth; 04.02.2011
comment
Вы можете создать единый BigInteger меньше 10 ^ N, создав множество целых чисел [0, 10 ^ m) и объединив их. - person Peter Lawrey; 04.02.2011
comment
Правильно, но есть соответствующий вопрос с хорошим ответом, связанным в первом предложении этого вопроса. Ваше предложение также может работать хорошо, особенно с m=9, поэтому можно использовать random.nextInt(). - person maaartinus; 04.02.2011

Я сделал сообщение о создании случайного BigInteger Andy Ответ Тернера о создании случайного 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;
}
person Andy Turner    schedule 17.02.2011
comment
Я не понимаю, что вы имеете в виду под случайным числом, которое вы получаете подряд. Согласно источнику Java для Random, общий контракт next заключается в том, что он возвращает значение int, и если биты аргумента находятся в диапазоне от 1 до 32 (включительно), то такое количество младших битов возвращаемого значения будет... независимо выбранные битовые значения, каждое из которых ... равновероятно равно 0 или 1. Мне кажется, это означает, что нет предела тому, сколько одинаковых значений вы получаете подряд, просто это становится все более маловероятным, как вы бы ожидать. - person Mike Houston; 17.02.2011

Возможно, здесь я упускаю очевидное, но как насчет создания двух случайных BigInteger, одна из которых будет целочисленной, а другая — дробной? Очевидно, что диапазон «дробного» bigint будет определяться точностью, которую вы хотите разрешить, от которой вы не можете избавиться.

Обновление: это может быть дополнительно упрощено для работы только с одним случайным bigint. Если вам нужно случайное число от 0 до n с десятичной точностью k (где k — константа), вы просто генерируете случайное число от 0 до n*10^k и делите его на 10^k.

person biziclop    schedule 04.02.2011
comment
Результат этого действия распределяется неравномерно. Я пробовал это, и результат равномерно распределяется по дробной части, что означает, что 10 ^ -27 так же вероятно, как и число от 0,01 до 0,1, чтобы появиться в результатах. Вероятность появления числа 10^-27 должна быть примерно на 26 порядков меньше, чем число в диапазоне 0,1–0,01. - person Mike Houston; 04.02.2011
comment
@Mike Houston Тогда я упускаю очевидное, потому что до сих пор не понимаю. Вы хотите, чтобы он был равномерно распределен или нет? - person biziclop; 04.02.2011
comment
@ Майк Хьюстон Нет, все еще не понимаю. Если вы возьмете равномерно распределенную переменную длиной не более n цифр и разделите ее на 10 ^ n, она все равно будет распределена равномерно. - person biziclop; 04.02.2011
comment
Да, я хочу, чтобы он был равномерно распределен. Равномерное распределение показателя степени отличается от равномерного распределения абсолютного значения. например для равномерно распределенной случайной величины X p (0 ‹ X ‹ 0,5) = p (0,5 ‹ X ‹ 1,0) = 0,5. р(0 ‹ Х ‹ 0,1) = р(0,5 ‹ Х ‹ 0,6) = 0,1. При равномерном распределении показателя степени это уже не так, поскольку p(0 ‹ X ‹ 0,01) ~= p(0,1 ‹ X ‹ 0,2), т. е. гораздо меньший отрезок числовой прямой имеет ту же вероятность, что и больший срез в другом месте числовой строки. - person Mike Houston; 04.02.2011
comment
@Mike Houston Я прекрасно это понимаю, просто не понимаю, как это связано с моим ответом, который в основном представлял собой равномерно распределенную переменную, деленную на константу. - person biziclop; 04.02.2011
comment
масштаб не делит входное целое число, он говорит, на сколько знаков после запятой нужно сдвинуть точку. То есть новый BigDecimal(1, -3) дает 0,001, а новый BigDecimal(101, -3) дает 0,00101. - person Mike Houston; 04.02.2011
comment
@ Майк Хьюстон Нет, это не так. new BigDecimal( new BigInteger( "101" ), 3 ) == 0.101 - person biziclop; 04.02.2011
comment
Хорошо, это может быть так, но деление одного случайного числа на другое не создает равномерного распределения, что и было первоначальной проблемой. - person Mike Houston; 04.02.2011
comment
@Mike Houston Конечно, нет, поэтому я никогда этого не предлагал. В любом случае, взгляните на другой ответ, который, по сути, говорит то же самое. - person biziclop; 04.02.2011
comment
В таком случае, извините, я так понял, что вы имели в виду создание двух случайных BigInteger. - person Mike Houston; 04.02.2011
comment
@Mike Houston Не беспокойтесь, это действительно была моя вина. Я мог бы включить пример, и было бы ясно, что я имел в виду. - person biziclop; 04.02.2011