Extraer parte fraccionaria de doble*eficientemente*en C
floating-point double (6)
Propuesta
El remainder
función calcula el resto, pero no la parte entera, como modf
hace modf
:
#include <math.h>
double fracpart(double input)
{
return remainder(input, 1.);
}
Esta es la forma más eficiente (y portátil), ya que no calcula valores innecesarios para realizar el trabajo (ver modf
, (long)
, fmod
, etc.)
Punto de referencia
Como Mattew sugirió en los comentarios, escribí un código de referencia para comparar esta solución con todas las otras que se ofrecen en esta página.
A continuación, encontrará las mediciones de tiempo para 65536 cálculos (compilados con Clang con las optimizaciones desactivadas):
method 1 took 0.002389 seconds (using remainder)
method 2 took 0.000193 seconds (casting to long)
method 3 took 0.000209 seconds (using floor)
method 4 took 0.000257 seconds (using modf)
method 5 took 0.010178 seconds (using fmod)
De nuevo con Clang, esta vez usando la bandera -O3
:
method 1 took 0.002222 seconds (using remainder)
method 2 took 0.000000 seconds (casting to long)
method 3 took 0.000000 seconds (using floor)
method 4 took 0.000223 seconds (using modf)
method 5 took 0.010131 seconds (using fmod)
Resulta que la solución más simple parece dar los mejores resultados en la mayoría de las plataformas, y los métodos específicos para realizar esa tarea ( fmod
, modf
, remainder
) son realmente muy lentos.
Estoy buscando tomar un doble IEEE y eliminar cualquier parte entera de la manera más eficiente posible.
quiero
1035 ->0
1045.23->0.23
253e-23=253e-23
No me importa el manejo adecuado de denormales, infinitos o NaNs. No me importa hacer pequeños retoques, ya que sé que estoy trabajando con dobles IEEE, por lo que debería funcionar en todas las máquinas.
Código sin sucursales sería mucho más preferido.
Mi primer pensamiento es (en pseudo código)
char exp=d.exponent;
(set the last bit of the exponent to 1)
d<<=exp*(exp>0);
(& mask the last 52 bits of d)
(shift d left until the last bit of the exponent is zero, decrementing exp each time)
d.exponent=exp;
Pero el problema es que no puedo pensar en una forma eficiente de desplazar d hacia la izquierda hasta que el último bit del exponente sea cero, más parece que tendría que dar salida a cero si no se establecieron todos los últimos bits. Esto parece estar relacionado con el problema de logaritmo de base 2.
La ayuda con este algoritmo o cualquiera mejor sería muy apreciada.
Probablemente debería tener en cuenta que la razón por la que quiero el código sin sucursales es porque quiero que se vectorice de manera eficiente.
¿Qué tal algo simple?
double fraction = whole - ((long)whole);
Esto solo resta la parte entera del doble del valor en sí, el resto debe ser el componente fraccional. Es posible, por supuesto, esto podría tener algunos problemas de representación.
Algunos perfiles y experimentos con C ++ en Microsoft Visual Studio 2015 indican que el mejor método para números positivos es:
double n;
// ...
double fractional_part = n - floor(n);
Es más rápido que modf
y, como ya se ha mencionado, la función de resto se redondea al número entero más cercano y, por lo tanto, no sirve de nada.
La función de biblioteca estándar modf resuelve este problema bastante bien.
#include <math.h>
/*...*/
double somenumber;
double integralPart;
double fractionalPart = modf(somenumber, &integralPart);
Esto debería hacer lo que ha pedido, es portátil y razonablemente eficiente.
Un detalle no documentado es si el segundo argumento podría ser NULL y luego evitar la parte integral temporal, lo que sería deseable en usos como los que ha descrito.
Lamentablemente, muchas de las implementaciones no admiten NULL para el segundo argumento, por lo que tendrá que usar un temporal, ya sea que use o no este valor.
La implementación óptima depende completamente de la arquitectura de destino.
En los procesadores Intel recientes, esto se puede lograr con dos instrucciones: roundsd
y subsd
, pero eso no se puede expresar en código C portátil .
En algunos procesadores, la forma más rápida de hacerlo es con operaciones de enteros en la representación de punto flotante. Temprano Atom y muchas CPUs ARM vienen a la mente.
En algunos otros procesadores, lo más rápido es convertir a entero y viceversa, luego restar, bifurcar para proteger valores grandes.
Si va a manejar muchos valores, puede configurar el modo de redondeo a cero, luego sumar y restar +/- 2 ^ 52 al número truncado a entero, luego restar del valor original para obtener la fracción. Si no tiene SSE4.1, pero tiene una CPU Intel moderna y desea vectorizar, esto es lo mejor que puede hacer. Sin embargo, solo tiene sentido si tiene muchos valores para procesar, ya que cambiar el modo de redondeo es algo costoso.
En otras arquitecturas, otras implementaciones son óptimas. En general, no tiene sentido hablar de "eficiencia" de los programas de C; solo la eficiencia de una implementación específica en una arquitectura específica.
#include <math.h>
double fraction = fmod(d, 1.0);