2011-02-04 22 views
5

Esta pregunta: How to generate a random BigInteger describe una forma de lograr la misma semántica que Random.nextInt (int n) para BigIntegers.¿Cómo puedo crear un BigDecimal aleatorio en Java?

Me gustaría hacer lo mismo para BigDecimal y Random.nextDouble().

Una respuesta en la pregunta anterior sugiere crear un BigInteger aleatorio y luego crear un BigDouble a partir de él con una escala aleatoria. Un experimento muy rápido muestra que esta es una muy mala idea :)

Mi intuición es que usar este método requeriría que el entero fuera escalado por algo como n-log10(R), donde n es el número de dígitos de precisión requeridos en el salida y R es el BigInteger aleatorio. Esto debería permitir que el número correcto de dígitos esté presente para que (por ejemplo) 1 -> 10^-64 y 10^64 -> 1.

El valor de escala también debe elegirse correctamente para que el resultado caiga. en el rango [0,1].

¿Alguien ha hecho esto antes, y saben si los resultados están distribuidos correctamente? ¿Hay una mejor manera de lograr esto?

EDIT: Gracias a @biziclop por corregir mi comprensión del argumento de la escala. Lo anterior no es necesario, un factor de escala constante tiene el efecto deseado.

Para tener una referencia más tarde, mi (código aparentemente de trabajo) es:

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

Respuesta

3

Seguro que es muy fácil ... si supiera lo que quiere. Para un número uniformemente distribuido en el rango [0, 1) y precisión N dígitos decimales generan un BigInteger uniforme menos de 10 * N y lo reducen en 10 * N.

+0

Era la parte de "escala aleatoria" de la respuesta original incorrecta. Este método debería estar bien. – DJClayworth

+0

Puede crear un BigInteger uniforme de menos de 10^N buy creando muchos enteros que son [0, 10^m) y combinándolos. –

+0

Correcto, pero hay una pregunta correspondiente con una buena respuesta vinculada en la primera oración de esta pregunta. Su propuesta también puede funcionar bien, especialmente con m = 9, por lo que se puede usar random.nextInt(). – maaartinus

1

puede ser que falte lo obvio aquí, pero ¿qué hay de la creación de dos aleatorios BigInteger s, uno de ellos la parte entera, y el otro el fraccional ? Obviamente, el alcance del bigint "fraccionario" estaría dictado por la precisión que desea permitir, de la que no puede alejarse.

Actualización: Esto se puede simplificar aún más para que funcione con solo un bigint aleatorio. Si desea un número aleatorio entre 0 y n con k precisión decimal (donde k es una constante), solo genere un número aleatorio entre 0 y n * 10^k y divídalo entre 10^k.

+0

El resultado de hacer esto no se distribuye uniformemente. Intenté esto, y el resultado se distribuye uniformemente en la parte fraccionaria, lo que significa que 10^-27 es tan probable como un número entre 0.01 y 0.1 para aparecer en los resultados. 10^-27 debería tener 26 órdenes de magnitud menos probable de aparecer que un número en el rango 0.1-0.01 –

+0

@Mike Houston Me falta entonces lo obvio, porque todavía no lo entiendo.¿Quieres que se distribuya uniformemente o no? – biziclop

+0

@Mike Houston No, todavía no lo entiendo. Si toma una variable distribuida uniformemente que tiene un máximo de n dígitos y la divide por 10^n, aún se distribuye de manera uniforme. – biziclop

2

Hice una publicación sobre la generación de BigInteger al azar Andy Turner's answer about generating a random BigInteger. No uso esto directamente para generar un BigDecimal aleatorio. Esencialmente mi preocupación es usar instancias independientes de Aleatorio para generar cada dígito en un número. Un problema que noté es que con Random hay solo muchos valores y un número particular que obtienes en una fila. Además, la generación intenta mantener una distribución uniforme de los valores generados. Mi solución depende de algo que almacene una matriz o colección de instancias aleatorias y llamarlas. Creo que esta es una buena manera de resolverlo y estoy tratando de averiguarlo, por lo que estoy interesado si alguien tiene alguna sugerencia o crítica sobre este enfoque.

/** 
* 
* @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; 
} 
+0

No entiendo a qué te refieres con "con Random hay solo muchos valores de [un] número particular que obtienes en una fila". De acuerdo con la fuente de Java para Random, "El contrato general de next es que devuelve un valor int y si los bits del argumento están entre 1 y 32 (inclusive), entonces muchos bits de orden bajo del valor devuelto serán ... valores de bits elegidos independientemente, cada uno de los cuales es ... igualmente probable que sea 0 o 1. " Esto parece implicar para mí que no hay límite para la cantidad de valores idénticos que obtienes seguidamente, cada vez es más improbable, como era de esperar. –

Cuestiones relacionadas