¿Es posible forzar el exponente o el significado de un flotador para que coincida con otro flotante (Python)?

Esta es una pregunta interesante que intenté resolver el otro día. ¿Es posible forzar el significado o el exponente de un float para que sea igual que otro float en Python?

La pregunta surge porque estaba intentando volver a escalar algunos datos para que el mínimo y el máximo coincidieran con otro conjunto de datos. Sin embargo, mis datos reescalados estaban ligeramente desactivados (después de aproximadamente 6 decimales) y fueron suficientes para causar problemas en el futuro.

Para dar una idea, tengo f1 y f2 ( type(f1) == type(f2) == numpy.ndarray ). Quiero np.max(f1) == np.max(f2) and np.min(f1) == np.min(f2) . Para lograr esto, yo hago:

 import numpy as np f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0 f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1) 

El resultado (solo como un ejemplo) sería:

 np.max(f1) # 5.0230593 np.max(f2) # 5.0230602 but I need 5.0230593 

Mi pensamiento inicial es que forzar el exponente del float sería la solución correcta. No pude encontrar mucho al respecto, así que hice una solución para mi necesidad:

 exp = 0 mm = np.max(f1) # find where the decimal is while int(10**exp*mm) == 0 exp += 1 # add 4 digits of precision exp += 4 scale = 10**exp f2 = np.round(f2*scale)/scale f1 = np.round(f1*scale)/scale 

ahora np.max(f2) == np.max(f1)

Sin embargo, ¿hay una mejor manera? ¿Hice algo mal? ¿Es posible remodelar un float para que sea similar a otro float (exponente u otro medio)?

EDITAR: como se sugirió, ahora estoy usando:

 scale = 10**(-np.floor(np.log10(np.max(f1))) + 4) 

Si bien mi solución anterior funcionará (para mi aplicación), me interesa saber si hay una solución que de alguna manera pueda obligar al float a tener el mismo exponente y / o significación para que los números se vuelvan idénticos.

TL; DR

Utilizar

 f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1) 

y asegúrese de usar precisión doble, compare números de punto flotante observando diferencias absolutas o relativas, evite redondear para ajustar (o comparar) números de punto flotante y no establezca los componentes subyacentes de los números de punto flotante manualmente.

Detalles

Este no es un error muy fácil de reproducir, como has descubierto. Sin embargo, trabajar con números flotantes está sujeto a error. Por ejemplo, sumndo 1 000 000 000 + 0 . 000 000 000 1 1 000 000 000 + 0 . 000 000 000 1 da 1 000 000 000 . 000 000 000 1 1 000 000 000 . 000 000 000 1 , pero se trata de demasiadas cifras significativas incluso para la doble precisión (que admite alrededor de 15 cifras significativas ), por lo que el decimal final se elimina. Además, algunos números “cortos” no se pueden representar exactamente, como se señala en la respuesta de @Kevin . Ver, por ejemplo, aquí , para más. (Busque algo como “error de redondeo de truncamiento de punto flotante” para obtener aún más).

Aquí hay un ejemplo que demuestra un problema:

 import numpy as np numpy.set_printoptions(precision=16) dtype=np.float32 f1 = np.linspace(-1000, 0.001, 3, dtype=dtype) f2 = np.linspace(0, 1, 3, dtype=dtype) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0 f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1) print (f1) print (f2) 

salida

 [ -1.0000000000000000e+03 -4.9999951171875000e+02 1.0000000474974513e-03] [ -1.0000000000000000e+03 -4.9999951171875000e+02 9.7656250000000000e-04] 

Siguiendo el comentario de @Mark Dickinson, he usado un punto flotante de 32 bits. Esto es consistente con el error que informó, un error relativo de alrededor de 10 ^ -7, alrededor de la séptima cifra significativa

 In: (5.0230602 - 5.0230593) / 5.0230593 Out: 1.791736760621852e-07 

Ir a dtype=np.float64 mejora las cosas pero aún no es perfecto. El progtwig de arriba da entonces

 [ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03] [ -1.0000000000000000e+03 -4.9999950000000001e+02 9.9999999997635314e-04] 

Esto no es perfecto, pero generalmente es lo suficientemente cerca. Cuando se comparan números de punto flotante, casi nunca se quiere usar una igualdad estricta debido a la posibilidad de pequeños errores como se indicó anteriormente. En su lugar, reste un número de otro y verifique que la diferencia absoluta sea menor que cierta tolerancia y / o observe el error relativo. Ver, por ejemplo, numpy.isclose .

Volviendo a su problema, parece que debería ser posible hacerlo mejor. Después de todo, f2 tiene el rango de 0 a 1, por lo que debería poder replicar el máximo en f1 . El problema viene en la línea.

 f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1) 

porque cuando un elemento de f2 es 1, se le está haciendo mucho más que simplemente multiplicar 1 por el máximo de f1 , lo que lleva a la posibilidad de que se produzcan errores aritméticos de punto flotante. Observe que puede multiplicar los corchetes f2*(np.max(f1)-np.min(f1)) a f2*np.max(f1) - f2*np.min(f1) , y luego factorizar el resultado - f2*np.min(f1) + np.min(f1) a np.min(f1)*(f2-1) dando

 f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1) 

Entonces, cuando un elemento de f2 es 1, tenemos 1*np.max(f1) - np.min(f1)*0 . A la inversa, cuando un elemento de f2 es 0, tenemos 0*np.max(f1) - np.min(f1)*1 . Los números 1 y 0 se pueden representar exactamente, por lo que no debería haber errores.

Las salidas del progtwig modificado.

 [ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03] [ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03] 

es decir, como se desee.

Sin embargo, todavía recomendaría encarecidamente el uso de una comparación de punto flotante inexacta (con límites ajustados si es necesario) a menos que tenga una buena razón para no hacerlo. Hay todo tipo de errores sutiles que pueden ocurrir en la aritmética de punto flotante y la forma más fácil de evitarlos es nunca usar una comparación exacta.

Un enfoque alternativo al anterior, que podría ser preferible, sería volver a escalar ambas matrices entre 0 y 1. Esta podría ser la forma más adecuada para usar dentro del progtwig. (Y ambas matrices podrían multiplicarse por un factor de escala tal como el rango original de f1 , si es necesario).

Re utilizando redondeo para resolver su problema, no recomendaría esto. El problema con el redondeo, aparte del hecho de que reduce innecesariamente la precisión de sus datos, es que los números que están muy cerca pueden redondearse en diferentes direcciones. P.ej

 f1 = np.array([1.000049]) f2 = np.array([1.000051]) print (f1) print (f2) scale = 10**(-np.floor(np.log10(np.max(f1))) + 4) f2 = np.round(f2*scale)/scale f1 = np.round(f1*scale)/scale print (f1) print (f2) 

Salida

 [ 1.000049] [ 1.000051] [ 1.] [ 1.0001] 

Esto se relaciona con el hecho de que aunque es común discutir los números que coinciden con tantas cifras significativas, las personas no las comparan de esta manera en la computadora. Calcula la diferencia y luego la divide por el número correcto (para un error relativo).

Re mantissas y exponentes, vea math.frexp y math.ldexp , documentado aquí . Sin embargo, no recomendaría que los configure usted mismo (considere dos números que están muy cerca pero que tienen diferentes exponentes, por ejemplo, ¿realmente desea establecer la mantisa)? Mucho mejor es establecer directamente el máximo de f2 explícitamente al máximo de f1 , si desea asegurarse de que los números sean exactamente iguales (y de manera similar para el mínimo).

Depende de lo que quieras decir con “mantisa”.

Internamente, los flotadores se almacenan usando la notación científica en la base 2. Entonces, si te refieres a la mantisa base 2 , en realidad es muy fácil: simplemente multiplica o divide por potencias de dos (no potencias de 10), y la mantisa se mantendrá igual ( siempre que el exponente no se salga del rango; si lo hace, quedará limitado a infinito o cero, o posiblemente irá a números denormales según los detalles arquitectónicos). Es importante entender que las expansiones de decimales no coincidirán cuando vuelvas a escalar en potencias de dos. Es la expansión binaria que se conserva con este método.

Pero si te refieres a la mantisa base 10, no, no es posible con flotadores, porque el valor reescalado puede no ser exactamente representable. Por ejemplo, 1.1 no se puede representar exactamente en la base 2 (con un número finito de dígitos) de la misma manera que 1/3 no se puede representar en la base 10 (con un número finito de dígitos). Así que el reescalado 11 por 1/10 no se puede hacer con precisión:

 >>> print("%1.29f" % (11 * 0.1)) 1.10000000000000008881784197001 

Sin embargo, puedes hacer lo último con s decimal . Los decimales funcionan en base 10 y se comportarán como se espera en términos de reescalado de base 10. También proporcionan una gran cantidad de funcionalidades especializadas para detectar y manejar varios tipos de pérdida de precisión. Pero los decimales no se benefician de las aceleraciones de NumPy , por lo que si tiene que trabajar con un gran volumen de datos, es posible que no sean lo suficientemente eficientes para su caso de uso. Dado que NumPy depende del soporte de hardware para el punto flotante, y la mayoría de las architectures modernas no proporcionan soporte de hardware para la base 10, esto no se puede remediar fácilmente.

Intenta reemplazar la segunda línea por

 f2 = f2*np.max(f1) + (1.0-f2)*np.min(f1) 

Explicación: Hay 2 lugares donde la diferencia podría deslizarse en:

Paso 1) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2))

Cuando inspeccionas np.min(f2) y np.max(f2) , ¿obtienes exactamente 0 y 1 o algo así como 1.0000003?

Paso 2) f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)

La expresión como (ab)+b no siempre produce exactamente a , debido a un error de redondeo. La expresión sugerida es ligeramente más estable.

Para obtener una explicación muy detallada, consulte Lo que todo científico informático debe saber sobre la aritmética de punto flotante por David Goldberg.

 def rescale(val, in_min, in_max, out_min, out_max): return out_min + (val - in_min) * ((out_max - out_min) / (in_max - in_min)) value_to_rescale = 5 current_scale_min = 0 current_scale_max = 10 target_scale_min = 100 target_scale_max = 200 new_value = rescale(value_to_rescale, current_scale_min, current_scale_max, target_scale_min, target_scale_max) print(new_value) new_value = rescale(10, 0, 10, 0, 100) print(new_value) 

responder:

150 100

Aquí hay uno con decimales.

 from decimal import Decimal, ROUND_05UP num1 = Decimal('{:.5f}'.format(5.0230593)) ## Decimal('5.02306') num2 = Decimal('{}'.format(5.0230602)) ## Decimal('5.0230602') print num2.quantize(num1, rounding=ROUND_05UP) ## 5.02306 

EDITAR ** estoy un poco confundido de por qué recibo tantos comentarios negativos, por lo que aquí hay otra solución que no usa decimales:

 a = 5.0230593 b = 5.0230602 if abs(a - b) < 1e-6: b = a