Observé que la rand()
función de biblioteca cuando se llama solo una vez dentro de un ciclo, casi siempre produce números positivos.
for (i = 0; i < 100; i++) {
printf("%d\n", rand());
}
Pero cuando agrego dos rand()
llamadas, los números generados ahora tienen más números negativos.
for (i = 0; i < 100; i++) {
printf("%d = %d\n", rand(), (rand() + rand()));
}
¿Alguien puede explicar por qué estoy viendo números negativos en el segundo caso?
PD: inicializo la semilla antes del ciclo como srand(time(NULL))
.
rand()
no puede ser negativo ...RAND_MAX
para tu compilador? Por lo general, puedes encontrarlo enstdlib.h
. (Divertido: comprobaciónman 3 rand
, lleva la descripción de una línea "generador de números aleatorios incorrectos".)abs(rand()+rand())
. ¡Prefiero tener un UB positivo que uno negativo! ;)Respuestas:
rand()
se define para devolver un número entero entre0
yRAND_MAX
.podría desbordarse. Lo que observa es probablemente el resultado de un comportamiento indefinido causado por un desbordamiento de enteros.
fuente
a + b > a
si lo sabenb > 0
. También pueden suponer que si hay una declaración ejecutada más tarde, ela + 5
valor actual es menorINT_MAX - 5
. Por lo tanto, incluso en el procesador / intérprete complementario de 2 sin programa de trampas podría no comportarse como siint
el complemento de 2 fuera sin trampas.El problema es la adición.
rand()
devuelve unint
valor de0...RAND_MAX
. Entonces, si agrega dos de ellos, podrá hacerloRAND_MAX * 2
. Si eso excedeINT_MAX
, el resultado de la suma desborda el rango válido queint
puede contener. El desbordamiento de valores firmados es un comportamiento indefinido y puede hacer que su teclado le hable en lenguas extranjeras.Como no hay ganancia aquí al agregar dos resultados aleatorios, la idea simple es simplemente no hacerlo. Alternativamente, puede emitir cada resultado
unsigned int
antes de la suma si eso puede contener la suma. O use un tipo más grande. Tenga en cuenta quelong
no es necesariamente más ancho queint
, ¡lo mismo se aplica along long
ifint
es de al menos 64 bits!Conclusión: solo evite la adición. No proporciona más "aleatoriedad". Si necesita más bits, puede concatenar los valores
sum = a + b * (RAND_MAX + 1)
, pero eso probablemente también requiera un tipo de datos mayor queint
.Como su razón declarada es evitar un resultado cero: eso no puede evitarse agregando los resultados de dos
rand()
llamadas, ya que ambas pueden ser cero. En cambio, puede simplemente incrementar. SiRAND_MAX == INT_MAX
, esto no se puede hacer enint
. Sin embargo,(unsigned int)rand() + 1
lo hará muy, muy probable. Probable (no definitivamente), porque requiereUINT_MAX > INT_MAX
, lo cual es cierto en todas las implementaciones que conozco (que cubren algunas arquitecturas integradas, DSP y todas las plataformas de escritorio, móviles y servidores de los últimos 30 años).Advertencia:
Aunque ya aparece en los comentarios aquí, tenga en cuenta que agregar dos valores aleatorios no obtiene una distribución uniforme, sino una distribución triangular como lanzar dos dados: para obtener
12
(dos dados) ambos dados tienen que mostrar6
. porque11
ya hay dos posibles variantes:6 + 5
o5 + 6
, etc.Entonces, la adición también es mala desde este aspecto.
También tenga en cuenta que los resultados
rand()
generados no son independientes entre sí, ya que son generados por un generador de números pseudoaleatorios . Tenga en cuenta también que el estándar no especifica la calidad o la distribución uniforme de los valores calculados.fuente
UINT_MAX > INT_MAX != false
está garantizado por el estándar. (Suena probable, pero no estoy seguro si es necesario). Si es así, puede lanzar un solo resultado e incremento (¡en ese orden!).Esta es una respuesta a una aclaración de la pregunta hecha en comentario a esta respuesta ,
El problema era evitar 0. Hay (al menos) dos problemas con la solución propuesta. Una es, como indican las otras respuestas, que
rand()+rand()
puede invocar un comportamiento indefinido. El mejor consejo es nunca invocar un comportamiento indefinido. Otro problema es que no hay garantía de querand()
no produzca 0 dos veces seguidas.Lo siguiente rechaza cero, evita un comportamiento indefinido y, en la gran mayoría de los casos, será más rápido que dos llamadas a
rand()
:fuente
rand() + 1
?Básicamente
rand()
produzca números entre0
yRAND_MAX
, y2 RAND_MAX > INT_MAX
en su caso.Puede modular con el valor máximo de su tipo de datos para evitar el desbordamiento. Por supuesto, esto interrumpirá la distribución de los números aleatorios, pero
rand
es solo una forma de obtener números aleatorios rápidos.fuente
Es posible que pueda intentar un enfoque complicado al asegurarse de que el valor devuelto por la suma de 2 rand () nunca exceda el valor de RAND_MAX. Un posible enfoque podría ser sum = rand () / 2 + rand () / 2; Esto garantizaría que para un compilador de 16 bits con un valor RAND_MAX de 32767, incluso si ambos rands devuelven 32767, incluso entonces (32767/2 = 16383) 16383 + 16383 = 32766, por lo tanto, no daría como resultado una suma negativa.
fuente
rand()
no den cero, por lo que el deseo de evitar cero no es una buena razón para agregar dos valores. Por otro lado, un deseo de tener una distribución no uniforme sería una buena razón para agregar dos valores aleatorios si uno asegura que no se produzca un desbordamiento.Una solución simple (bueno, llámalo "Hack") que nunca produce un resultado cero y nunca se desbordará:
Esto limitará su valor máximo, pero si no le importa, entonces esto debería funcionar bien para usted.
fuente
rand()
siempre devuelve un valor no negativo). Sin embargo, dejaría la optimización al compilador aquí.rand
no será negativo, el cambio será más eficiente que la división por un entero con signo 2. La división por2u
podría funcionar, pero six
esint
posible, puede haber advertencias sobre la conversión implícita de sin signo a firmado./ 2
todos modos (lo he visto incluso para algo así-O0
, es decir, sin optimizaciones solicitadas explícitamente). Es posiblemente la optimización más trivial y más establecida del código C. El punto es que la división está bien definida por el estándar para todo el rango entero, no solo los valores no negativos. Nuevamente: deje las optimizaciones al compilador, escriba el código correcto y claro en primer lugar. Esto es aún más importante para los principiantes.rand()
derecha por uno o se divide por2u
que cuando se divide por 2, incluso cuando se usa-O3
. Uno podría decir razonablemente que tal optimización no es importante, pero decir "dejar tales optimizaciones al compilador" implicaría que los compiladores probablemente las realizarían. ¿Conoces algún compilador que realmente lo haga?Para evitar 0, intente esto:
Necesitas incluir
limits.h
.fuente
rand()
produce 0.1
y2
(todo asumidoRAND_MAX == INT_MAX
). Me olvidé de la- 1
.-1
aquí no tiene ningún valor.rand()%INT_MAX+1;
aún generaría valores en el rango [1 ... INT_MAX].Si bien lo que todos los demás han dicho sobre el probable desbordamiento podría ser la causa de lo negativo, incluso si usa enteros sin signo. El verdadero problema es usar la funcionalidad de hora / fecha como semilla. Si realmente te has familiarizado con esta funcionalidad, sabrás exactamente por qué digo esto. Como lo que realmente hace es dar una distancia (tiempo transcurrido) desde una fecha / hora dada. Si bien el uso de la funcionalidad de fecha / hora como la semilla de un rand (), es una práctica muy común, realmente no es la mejor opción. Debes buscar mejores alternativas, ya que hay muchas teorías sobre el tema y no podría entrar en todas ellas. Agrega a esta ecuación la posibilidad de desbordamiento y este enfoque estuvo condenado desde el principio.
Aquellos que publicaron el rand () + 1 están utilizando la solución que la mayoría usa para garantizar que no obtengan un número negativo. Pero, ese enfoque tampoco es la mejor manera.
Lo mejor que puede hacer es tomarse el tiempo extra para escribir y usar el manejo de excepciones adecuado, y solo agregar al número rand () si y / o cuando termina con un resultado cero. Y, para lidiar con los números negativos correctamente. La funcionalidad rand () no es perfecta y, por lo tanto, debe usarse junto con el manejo de excepciones para garantizar que termine con el resultado deseado.
Merece la pena el tiempo y el esfuerzo extra para investigar, estudiar e implementar adecuadamente la funcionalidad rand (). Solo mis dos centavos. Buena suerte en tus esfuerzos ...
fuente
rand()
no especifica qué semilla usar. El estándar lo especifica para usar un generador pseudoaleatorio, no una relación con ningún momento. Tampoco establece la calidad del generador. El problema actual es claramente el desbordamiento. Tenga en cuenta querand()+1
se utiliza para evitar0
;rand()
no devuelve un valor negativo Lo siento, pero perdiste el punto aquí. No se trata de la calidad del PRNG. .../dev/random
y usar un buen PRNG después (no estoy seguro de la calidad derand()
glibc) o continuar usando el dispositivo, arriesgando su aplicación para bloquear si no hay suficiente entropía disponible. Tratar de obtener su entropía en la aplicación podría ser una vulnerabilidad, ya que posiblemente sea más fácil de atacar. Y ahora se trata de endurecer, no aquí