Pour fournir peut-être un exemple plus clair, sur x86_64, compilé avec l' -O
indicateur, la fonction
pub fn leet(a : i128) -> i128 {
a + 1337
}
compile en
example::leet:
mov rdx, rsi
mov rax, rdi
add rax, 1337
adc rdx, 0
ret
(Mon message d'origine avait u128
plutôt que ce que i128
vous avez demandé. La fonction compile le même code dans les deux cas, une bonne démonstration que l'ajout signé et non signé est le même sur un processeur moderne.)
L'autre liste a produit du code non optimisé. Il est sûr d'intervenir dans un débogueur, car cela garantit que vous pouvez placer un point d'arrêt n'importe où et inspecter l'état de n'importe quelle variable sur n'importe quelle ligne du programme. C'est plus lent et plus difficile à lire. La version optimisée est beaucoup plus proche du code qui fonctionnera réellement en production.
Le paramètre a
de cette fonction est passé dans une paire de registres 64 bits, rsi: rdi. Le résultat est renvoyé dans une autre paire de registres, rdx: rax. Les deux premières lignes de code initialisent la somme à a
.
La troisième ligne ajoute 1337 au mot bas de l'entrée. Si cela déborde, il porte le 1 dans le drapeau de retenue du CPU. La quatrième ligne ajoute zéro au mot haut de l'entrée, plus le 1 s'il a été porté.
Vous pouvez considérer cela comme une simple addition d'un nombre à un chiffre à un nombre à deux chiffres
a b
+ 0 7
______
mais en base 18 446 744 073 709 551 616. Vous ajoutez toujours le «chiffre» le plus bas en premier, en portant éventuellement un 1 à la colonne suivante, puis en ajoutant le chiffre suivant plus le report. La soustraction est très similaire.
La multiplication doit utiliser l'identité (2⁶⁴a + b) (2⁶⁴c + d) = 2¹²⁸ac + 2⁶⁴ (ad + bc) + bd, où chacune de ces multiplications renvoie la moitié supérieure du produit dans un registre et la moitié inférieure du produit dans un autre. Certains de ces termes seront supprimés, car les bits au-dessus du 128e ne rentrent pas dans a u128
et sont supprimés. Même ainsi, cela nécessite un certain nombre d'instructions de la machine. La division prend également plusieurs étapes. Pour une valeur signée, la multiplication et la division auraient en outre besoin de convertir les signes des opérandes et le résultat. Ces opérations ne sont pas du tout très efficaces.
Sur d'autres architectures, cela devient plus facile ou plus difficile. RISC-V définit une extension de jeu d'instructions de 128 bits, bien qu'à ma connaissance personne ne l'ait implémentée en silicium. Sans cette extension, le manuel d'architecture RISC-V recommande une branche conditionnelle:addi t0, t1, +imm; blt t0, t1, overflow
SPARC a des codes de contrôle comme les indicateurs de contrôle de x86, mais vous devez utiliser une instruction spéciale,, add,cc
pour les définir. MIPS, d'autre part, vous oblige à vérifier si la somme de deux entiers non signés est strictement inférieure à l'un des opérandes. Si tel est le cas, l'addition a débordé. Au moins, vous pouvez définir un autre registre à la valeur du bit de retenue sans branche conditionnelle.