web-dev-qa-db-fra.com

Le moyen le plus rapide d'obtenir un modulo positif en C/C++

Souvent, dans mes boucles internes, j'ai besoin d'indexer un tableau de manière "enveloppante", de sorte que si la taille du tableau est 100 et que mon code demande l'élément -2, il convient de lui attribuer l'élément 98. Dans de nombreux langages de haut niveau tels que En tant que Python, on peut le faire simplement avec my_array[index % array_size], mais pour une raison quelconque, l'arithmétique entière de C arrondit (généralement) à zéro au lieu d'être arrondie systématiquement vers le bas, et par conséquent son opérateur modulo renvoie un résultat négatif lorsqu'un premier argument est négatif.

Souvent, je sais que index ne sera pas inférieur à -array_size et, dans ces cas, je ne fais que my_array[(index + array_size) % array_size]. Cependant, parfois, cela ne peut pas être garanti, et dans ces cas, j'aimerais connaître le moyen le plus rapide d'implémenter une fonction modulo toujours positive. Il existe plusieurs façons "intelligentes" de le faire sans créer de branche, telles que

inline int positive_modulo(int i, int n) {
    return (n + (i % n)) % n
}

ou 

inline int positive_modulo(int i, int n) {
    return (i % n) + (n * (i < 0))
}

Bien sûr, je peux les profiler pour trouver celui qui est le plus rapide sur mon système, mais je ne peux pas m'empêcher de craindre d'en avoir oublié un meilleur, ou de constater que ce qui est rapide sur ma machine risque de ralentir sur un autre.

Alors, y a-t-il un moyen standard de faire cela, ou un truc astucieux que j'ai manqué qui soit probablement le moyen le plus rapide possible?

De plus, je sais que c'est probablement un voeu pieux, mais s'il existe un moyen de le faire qui peut être auto-vectorisé, ce serait génial.

49
Nathaniel

La manière standard que j'ai apprise est

inline int positive_modulo(int i, int n) {
    return (i % n + n) % n;
}

Cette fonction est essentiellement votre première variante sans la abs (ce qui la fait renvoyer un résultat erroné). Je ne serais pas surpris si un compilateur optimiseur pouvait reconnaître ce modèle et le compiler dans un code machine qui calcule un "modulé non signé".

Modifier:

Passons à votre deuxième variante: Tout d’abord, elle contient un bogue également - le n < 0 devrait être i < 0.

Cette variante ne donne peut-être pas l'air de créer une branche, mais sur beaucoup d'architectures, le i < 0 sera compilé en un saut conditionnel. Dans tous les cas, il sera au moins aussi rapide de remplacer (n * (i < 0)) par i < 0? n: 0, ce qui évite la multiplication; en plus, c'est "plus propre" car cela évite de réinterpréter le bool comme un int.

Laquelle de ces deux variantes est la plus rapide, cela dépend probablement de l'architecture du compilateur et du processeur - chronométrez les deux variantes et voyez. Je ne pense pas qu'il existe un moyen plus rapide que l'une ou l'autre de ces deux variantes.

63
Martin B

Modulo une puissance de deux, les travaux suivants (en supposant la représentation du complément à deux):

return i & (n-1);
22
nneonneo

Une méthode classique permettant d’obtenir l’addend optionnel à l’aide de la propagation bit-signe à deux complément

int positive_mod(int i, int n)
{
    /* constexpr */ int shift = CHAR_BIT*sizeof i - 1;
    int m = i%n;
    return m+ (m>>shift & n);
}
6
jthill

Si vous pouvez vous permettre de promouvoir un type plus important (et modulo sur le type le plus grand), ce code ne fait qu'un seul modulo et non si

int32_t positive_modulo(int32_t number, int32_t modulo) {
    return (number + ((int64_t)modulo << 32)) % modulo;
}
1
user15006

Vous pouvez aussi faire array[(i+array_size*N) % array_size], où N est un entier suffisamment grand pour garantir un argument positif, mais suffisamment petit pour ne pas déborder.

Lorsque la valeur array_size est constante, il existe des techniques pour calculer le module sans division. Outre l'approche de la puissance de deux, on peut calculer une somme pondérée de groupes de bits multipliée par le 2 ^ i% n, où i est le bit le moins significatif de chaque groupe:

par exemple. Entier 32 bits 0xaabbccdd% 100 = jj + cc * [2] 56 + bb * [655] 36 + aa * [167772] 16, plage maximale de (1 + 56 + 36 + 16) * 255 = 27795. Avec des applications répétées et des subdivisions différentes, l'opération peut être réduite à quelques soustractions conditionnelles.

Les pratiques courantes incluent également l’approximation de la division avec une réciproque de 2 ^ 32/n, qui peut généralement traiter une gamme raisonnablement large d’arguments.

 i - ((i * 655)>>16)*100; // (gives 100*n % 100 == 100 requiring adjusting...)
1
Aki Suihkonen

Votre deuxième exemple est meilleur que le premier. Une multiplication est une opération plus complexe qu'une opération if/else, utilisez donc ceci:

inline int positive_modulo(int i, int n) {
    int tmp = i % n;
    return tmp ? i >= 0 ? tmp : tmp + n : 0;
}
0
SkYWAGz