Redondeando los flotadores para que sumen precisamente a 1.

Tengo un poco de código codicioso que debe más o menos generar aleatoriamente un montón de porcentajes, almacenados como flotantes decimales. Es decir, decide que el material uno representa el 13.307 por ciento del total, luego lo almacena en un dictamen como 0.13307.

El problema es que nunca puedo conseguir que esos números sumen exactamente uno. No estoy completamente seguro de cuál es el problema, honestamente. Podría ser algo que ver con la naturaleza de las carrozas.

Aquí está el código ofensivo, en toda su gloria complicada:

while not sum(atmosphere.values())>=1: #Choose a material randomly themat=random.choice(list(materials.values())) #If the randomly chosen material is gaseous at our predicted temperature... if themat.vapor  0: #Choose a random constituent themat=random.choice(list(atmosphere.keys())) #If that constituent has a higher fraction value than the amount we'd need to reduce the total to 1... if atmosphere[themat]>(sum(atmosphere.values())-1): #Subtract that much from it. atmosphere[themat]-=difference #Then break the loop, since we're done and otherwise we'd go on removing bits of the atmosphere forever. break else: #Otherwise, halve its percentage and reduce difference by the amount we reduced the atmosphere oldperc=atmosphere[themat] atmosphere[themat]=oldperc/2 difference-=oldperc/2 #Then, finally, we correct any overcorrections the previous block made. difference=(sum(atmosphere.values())-1) if difference < 0: #Choose a random mat themat=random.choice(list(atmosphere.keys())) #Then add enough to it that the total is 1. atmosphere[themat]+=difference 

Lo siento si me he perdido algo obvio, o no estoy proporcionando un poco de información importante, pero estoy cansado en este momento, y he estado tratando de resolver esto durante días.

Desde tu código parece que estás generando aleatoriamente atmósferas planetarias, probablemente para algún tipo de juego o algo. En cualquier caso, su aleatoriedad me convence de que no tiene que ser demasiado preciso.

Así que te sugiero que no utilices flotadores, solo usa int s y subes hasta 100. Luego obtendrás tu sum exacta. Para cualquier matemática que desee utilizarlos en sólo el elenco.

¿No es esta una opción?

Si insistes en usar flotadores, sigue leyendo …

El problema que tienes usando flotadores es el siguiente:

Un punto flotante (en este caso un doble) se representa así:

introduzca la descripción de la imagen aquí

que corresponde a un double de valor:

introduzca la descripción de la imagen aquí

Asi que,

su número es (1+M) * 2**(E) (donde E = e-offset )

1+M está siempre en el rango 1-2.

Entonces, tenemos números espaciados igualmente entre cada par de poder de dos (positivo y negativo), y el espaciado entre los números se duplica con cada aumento en el exponente, E

Piense en esto, significa que hay un espaciado constante de números representables entre cada uno de estos números [(1,2),(2,4),(4,8), etc] . Esto también se aplica a los poderes negativos de dos, por lo que:

 0.5 - 1 0.25 - 0.5 0.125 - 0.25 0.0625 - 0.125 etc. 

Y en cada rango, hay la misma cantidad de números. Esto significa que si toma un número en el rango (0.25,0.5) y lo agrega a un número en el rango (0.5,1) , entonces tiene un 50% de probabilidad de que el número no pueda ser representado exactamente.

Si sums dos números de punto flotante para los cuales los exponentes difieren en D , entonces las posibilidades de que la sum sea exactamente representable son 2 -D .

Si luego quiere representar el rango 0-1 , deberá tener mucho cuidado con los flotadores que usa (es decir, obligar a que los últimos N bits de la fracción sean cero, donde N es una función de E ).

Si recorres esta ruta, terminarás con muchos más flotadores en la parte superior del rango que en la parte inferior.

La alternativa es decidir qué tan cerca de cero desea poder obtener. Digamos que quieres bajar a 0.0001.

0.0001 = (1 + M) * 2 E

log 2 (0.0001) = -13.28771 …

Entonces usaremos -14 como nuestro mínimo exponente.

Y luego, para obtener hasta 1, dejamos el exponente como -1.

Así que ahora tenemos 13 rangos, cada uno con el doble de valores que el inferior, que podemos sumr sin tener que preocuparnos por la precisión.

Sin embargo, esto también significa que el rango superior tiene 213 valores más que podemos usar. Esto obviamente no está bien.

Entonces, después de seleccionar un flotador, redondéelo al valor permisible más cercano. En este caso, por redondeo solo quiero establecer los últimos 13 bits a cero, y solo ponerlo todo en una función, y aplicarlo a sus números inmediatamente después. los rand de rand

Algo como esto:

 from ctypes import * def roundf(x,bitsToRound): i = cast(pointer(c_float(x)), POINTER(c_int32)).contents.value bits = bin(i) bits = bits[:-bitsToRound] + "0"*bitsToRound i = int(bits,2) y = cast(pointer(c_int32(i)), POINTER(c_float)).contents.value return y 

(imagenes de wikipedia)

Si quieres encontrar dos valores que sumen 1.0

Entiendo que desea elegir dos números de punto flotante entre 0.0 y 1.0 para que se sumen a 1.0.

Hacer esto:

  • Elige la L más grande de las dos. Tiene que estar entre 0.5 y 1.0.
  • define el número más pequeño S como 1.0 – L.

Luego, en punto flotante, S + L es exactamente 1.0.


Si, por algún motivo, obtiene el número S más pequeño primero en su algoritmo, calcule L = 1.0 – S y luego S0 = 1.0 – L. Entonces L y S0 se sumn exactamente a 1.0. Considere S0 la versión “redondeada” de S.

Si te refieres a varios valores X 1 , X 2 ,…, X N

Esta es una solución alternativa si agrega números N, cada uno entre 0.0 y 1.0, y espera que las operaciones X 1 + X 2 + … y 1.0 – X 1 … se comporten como lo hacen en matemáticas.

Cada vez que obtenga un nuevo número X i , haga: X i ← 1.0 – (1.0 – X i ). Solo use este nuevo valor de X i desde ese punto en adelante. Esta asignación redondeará ligeramente X i para que se comporte bien en todas las sums cuyos resultados intermedios estén entre 0.0 y 1.0.

EDITAR: después de hacer lo anterior para los valores X 1 ,…, X N-1 , calcule X N como 1 – X 1 -… – X N-1 . Este cálculo de punto flotante será exacto (a pesar de involucrarlo), por lo que tendrá X 1 + … + X N = 1 exactamente.

Dado que los flotadores se almacenan en la máquina en una representación binaria, siempre hay números que no se pueden representar con precisión. Si necesita evitar esta limitación, debe usar alguna biblioteca matemática, que usa tipos de datos definidos de forma personalizada.

Al final, resultó que la solución más sencilla era cambiar el problema. Redondeando la sum a 5 dígitos de precisión con round(x,5) cada vez que se verificó dio resultados adecuados.

Los flotadores están representados por poderes de dos. De los documentos de python: “Desafortunadamente, la mayoría de las fracciones decimales no se pueden representar exactamente como fracciones binarias”

http://docs.python.org/2/tutorial/floatingpoint.html

EDITAR: Tal vez, en lugar de intentar realmente llegar a 1.000000000000000000000000, debería determinar un nivel de error aceptable al cortar cualquier cosa después del tercer decimal. Puede estar relativamente seguro de que el valor agregado a 1. Usando este concepto, podría aceptar cualquier respuesta mayor que 0.999 y menor que 1.001.

Es posible que esto no sea perfecto, pero puede ser una buena solución para superar su problema.