Extrait de partie fractionnaire de la double *efficace* en C
Je suis à la recherche de prendre un IEEE double et enlever toute la partie entière de la manière la plus efficace possible.
Je veux
1035 ->0
1045.23->0.23
253e-23=253e-23
Je ne se soucient pas correctement la manipulation de la denormals, infinis, ou de NaNs. Je n'ai pas l'esprit à peu se tourner, car je sais que je suis en train de travailler avec la norme IEEE double, donc, il faut travailler sur des machines.
Sans branches code serait bien préférable.
Ma première pensée est (en pseudo code)
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;
Mais le problème est que je ne peux pas penser à un moyen efficace de changement d à gauche jusqu'à ce que le dernier bit de l'exposant est égal à zéro, et en plus il semble qu'il aurait besoin à la sortie de zéro si tous les derniers morceaux n'étaient pas définis. Cela semble être liée à la base 2 logarithme problème.
Aider avec cet algorithme, ou mieux serait très apprécié.
Je devrais sans doute remarque que la raison pour laquelle je veux sans branches code est parce que je le veux efficacement vectoriser.
Vous devez vous connecter pour publier un commentaire.
Comment parler de quelque chose de simple?
Ce juste soustrait la partie entière du double de la valeur elle-même, le reste devrait être la fraction de la composante. Il est possible, bien sûr, cela pourrait avoir quelques problèmes de représentation.
%
ne fonctionne pas.fmod
(%
est entier uniquement), et je suis presque sûr que c'est plus lent depuis%
implique une division.fmod
est le chemin à parcourir.int64_t
, paslong
.long
peut-être seulement 32 bits, auquel cas les valeurs que vous avez besoin de ne pas s'adapter.roundpd
). La mise en œuvre de cette méthode exige le compilateur afin de rendre le code qui va échouer si le double est en dehors de la plage de représentable valeurs longues. Un aller-retour à partir de la FP en entier et à l'arrière peut être plus lent que l'arrondissement. J'ai essayé les deux versions sur le Godbolt compilateur explorer.floor
n'est pas en ligne avec juste-fno-math-errno
, malheureusement, j'ai donc utilisé-ffast-math
. Si vous ne pouvez pas le faire, et ne pouvez pas assumer SSE4, le casting a l'air bon.double
àlong
a UB pour les valeurs en dehors de la plage delong
, de sorte que le compilateur doit être en mesure de l'optimiser en supposant que la valeur est dans la gamme (et donc d'éviter toute conversion de type entier).La mise en œuvre optimale dépend entièrement de l'architecture cible.
Sur les récents processeurs Intel, ce qui peut être réalisé avec deux instructions:
roundsd
etsubsd
, mais qui ne peuvent pas être exprimées dans portable code C.Sur certains processeurs, le moyen le plus rapide de le faire est avec les opérations sur entiers sur la représentation à virgule flottante. Début de l'Atome et de nombreux Processeurs ARM viennent à l'esprit.
Sur d'autres processeurs, le plus rapide c'est de cast en entier et à l'arrière, puis de soustraire, branchement pour protéger les grandes valeurs.
Si vous allez à la manipulation de beaucoup de valeurs, vous pouvez définir le mode d'arrondi à la ronde-à-zéro, puis ajouter et soustraire +/-2^52 le nombre tronqué en entier, puis soustraire la valeur d'origine pour obtenir la fraction. Si vous n'avez pas SSE4.1, mais ne avoir un moderne type de PROCESSEUR Intel et souhaitez vectoriser, c'est généralement le meilleur que vous pouvez faire. Il n'a de sens que si vous avez beaucoup de valeurs à traiter, cependant, parce que changer le mode d'arrondi est un peu cher.
Sur d'autres architectures, d'autres implémentations sont optimales. En général, il n'a pas de sens à parler de "l'efficacité" des programmes C; seule l'efficacité d'une mise en œuvre spécifique sur une architecture spécifique.
floor()
et la-
opérateur? Puis un compilateur de ciblage SSE4 pouvez utiliserroundsd
/roundpd
pour mettre en œuvre ces sémantique. (Mais je suppose que vous pourriez avoir besoin-fno-math-errno
ou peut-être la pleine-ffast-math
de le laisser en fait vectoriserfloor()
àroundpd
.)floor
? Ne nécessite pas de branchement.smmintrin.h
et compiler avec le drapeau de l'ESS?-msse4
. Mais si votre PROCESSEUR ne prend pas en charge, alors il ne fonctionnera pas; dans ce cas, vous ne pouvez pas vraiment utiliser SIMD pour vectoriser ce calcul particulier. :\Proposition
La fonction
reste
calcule le reste, mais pas la partie entière commemodf
n':C'est le plus efficace (et portable), comme il n'est pas inutile de calculer les valeurs de la tâche (cf.
modf
,(long)
,fmod
, etc.)De référence
Que Mattew suggéré dans les commentaires, j'ai écrit quelques référence code de comparer cette solution à tous les autres proposés sur cette page.
Veuillez trouver ci-dessous les mesures de temps de 65536 calculs (compilé avec Clang avec les optimisations désactivé):
De nouveau avec Clang, en utilisant cette fois les
-O3
drapeau:S'avère la solution la plus simple semble donner les meilleurs résultats sur la plupart des plates-formes et les méthodes spécifiques à effectuer cette tâche (
fmod
,modf
,remainder
) sont en fait des super lent!compiled with Clang with optimizations turned off
. Vos résultats sont vides de sens. L'optimisation n'a pas d'accélérer le tout par le même pourcentage. Voir ma réponse sur cette question au sujet d'une mission où ils ont eu à optimiser pour-O0
.0.000000 seconds
. Sonne comme vos performances optimisées à l'écart. Je n'attends quefloor
ou de la conversion enlong
vont être très efficace, basé sur le fait qu'ils ne prennent une couple de rapide asm instructions x86 (vs lent FP division), mais ce n'est pas de démontrer quoi que ce soit.De la bibliothèque Standard de la fonction modf résout ce problème assez facilement.
Cela devrait faire ce que vous avez demandé, est portable, et raisonnablement efficace.
Un sans-papiers détail est de savoir si le deuxième argument peut être NULL et éviter la partie intégrante temporaire, qui serait pourtant souhaitable utilise comme celle que vous avez décrite.
Malheureusement il coutures de nombreuses implémentations ne supportent pas la valeur NULL pour le deuxième argument, nous allons donc utiliser un temporaire de savoir si ou non vous utilisez cette valeur.
Certains de profilage et d'expérimentation à l'aide de C++ dans Visual Studio 2015 indique que la meilleure méthode pour les nombres positifs est:
Il est plus rapide que
modf
, et, comme il a déjà été mentionné, le reste de la fonction arrondit à l'entier le plus proche, et n'est donc pas de l'utiliser.