Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Le coprocesseur 87

La VIRGULE FLOTTANTE


Tutoriel JScript

Nombres flottants et processeurs

Table des matières Plier Déplier

  • I. Prise en charge des flottants par le hardware
    • I-A. Instructions flottantes microcodées
    • I-B. FPU
    • I-C. Coprocesseurs arithmétiques
  • II. Rappels sur la norme IEEE 754
    • II-A. Nombres flottants
      • II-A-1. Exemple
      • II-A-2. Formats de flottants
    • II-B. Flottants spéciaux
      • II-B-1. Flottants dénormalisés
      • II-B-2. Le zéro
      • II-B-3. l'infini !
      • II-B-4. NaN
    • II-C. Exceptions et arrondis
      • II-C-1. Arrondis
      • II-C-2. Autres exceptions
    • II-D. Calculs standards
    • II-E. Processeur et norme IEEE 754
  • III. Exemple : l'architecture x86
    • III-A. FPU x87
      • III-A-1. Pile de registres
      • III-A-2. Registres de contrôle et de statut
      • III-A-3. Instructions x87
      • III-A-4. Double Rounding
    • III-B. Unités de calcul SSE
      • III-B-1. Jeu d'instructions SSE
      • III-B-2. Registres SSE
  • IV. Conclusion

Un ordinateur est souvent décrit comme une machine à calculer programmable. Autrefois, les ordinateurs ne pouvaient faire de calculs que sur des nombres entiers, et pas sur des nombres à virgule. Pour combler ce manque, diverses techniques ont été inventées pour permettre à nos ordinateurs de gérer ces nombres à virgule. La méthode actuelle utilisée pour les représenter s'appelle la virgule flottante, qui permet de coder sous forme binaire un sous-ensemble des nombres à virgule.

Si vous avez déjà programmé, vous savez sûrement ce qu'est un nombre flottant. Mais savez-vous comment les calculs avec ces nombres flottants sont effectués dans notre ordinateur ?

Si la réponse est non, alors ce tutoriel va vous ravir.

I. Prise en charge des flottants par le hardware

L'ensemble des instructions machine que peut effectuer un processeur s'appelle son jeu d'instructions. Ce jeu d'instructions est déterminant quand il s'agit de fournir un support matériel efficace des nombres flottants. Il y a une quarantaine d'années, nos processeurs n'étaient capables d'utiliser que des nombres entiers et aucune instruction machine ne pouvait manipuler de nombres flottants. On devait alors émuler les calculs sur les flottants par une suite d'instructions machine effectuées sur des entiers. Cette émulation était effectuée par un programme écrit par le développeur d'une application, voire par le système d'exploitation : celui-ci fournissait un certain nombre d'appels système permettant de simuler des calculs effectués sur des nombres flottants à partir d'entiers. Dans les deux cas, les calculs sur des nombres flottants étaient alors vraiment lents, et la manière de stocker des flottants en mémoire dépendait de l'application ou du système d'exploitation.

I-A. Instructions flottantes microcodées

Pour limiter la catastrophe, nos processeurs ont alors incorporé des instructions de calculs sur des nombres flottants. Les premiers processeurs de ce type n'incorporaient néanmoins pas le moindre circuit capable d'effectuer des opérations flottantes. Ceux-ci microcodaient les instructions flottantes.

Sur ces processeurs, chaque instruction machine capable de gérer des nombres flottants était convertie en interne (c'est-à-dire dans le processeur) en une suite d'instructions entières qui émulaient l'instruction voulue.

Pour ce faire, notre processeur incorpore dans ses circuits une petite mémoire ROM. Celle-ci stocke, pour chaque instruction qui doit être émulée, une suite d'instructions de base exécutables par les circuits de notre processeur. Lorsqu'une instruction à émuler doit être exécutée, notre processeur se contente de lire la suite d'instructions à exécuter dans la mémoire ROM et l'exécute.

I-B. FPU

De nos jours, les circuits capables de calculer sur des nombres flottants sont intégrés dans le processeur et ne sont pas séparés de celui-ci. Néanmoins, ces circuits sont parfois regroupés dans une grosse unité de calcul nommée une Floating Point Unit, aussi appelée FPU. Ces FPU peuvent effectuer des instructions de calcul assez diverses : addition, multiplication, soustraction, division, racine carrée, logarithmes, exponentielles, tangentes, sinus, cosinus, et d'autres fonctions mathématiques.

Néanmoins, ce regroupement des circuits calculant sur les nombres flottants n'est pas aussi strict qu'on pourrait le croire. Dans certains cas, les circuits capables d'effectuer les divisions flottantes sont séparés des autres circuits (c'est le cas dans la majorité des PC modernes) : tout dépend de l'architecture interne du processeur utilisé.

Autre détail : notre processeur contient quasiment toujours des petites mémoires très rapides nommées registres qui sont utilisées pour stocker temporairement des données que le processeur doit manipuler. Cela permet de stocker des résultats temporaires qui n'ont rien à faire en mémoire, ou encore à accélérer les calculs (ces registres sont des mémoires très rapides).

Dans certains processeurs, des registres sont spécialement dédiés aux unités de calcul flottantes et ne peuvent stocker que des nombres flottants : ces registres sont alors séparés des registres qui stockent des nombres entiers. Dans ce cas, une instruction entière ne peut être appliquée sur un registre contenant un nombre flottant. Parfois, un processeur peut même utiliser plusieurs registres pour stocker un seul flottant et possède des circuits conçus pour travailler sur les deux registres en même temps ! On peut ainsi supporter des flottants d'une taille plus grande que celle des registres.

Dans d'autres processeurs, les registres ne sont pas dédiés à une unité particulière et peuvent stocker indifféremment des nombres flottants et des nombres entiers.

I-C. Coprocesseurs arithmétiques

Autrefois, ces FPU n'étaient pas incorporés dans le processeur, mais étaient regroupés dans un processeur séparé du processeur principal de la machine : un emplacement dans la carte mère était réservé à un de ces processeurs spécialisés. On appelait ces processeurs spécialisés dans les calculs flottants des coprocesseurs arithmétiques.

Ces coprocesseurs étaient très chers et relativement peu utilisés. Aussi, seules certaines applications assez rares étaient capables d'en tirer profit : des logiciels de conception assistée par ordinateur, par exemple.

II. Rappels sur la norme IEEE 754

Devant la multiplication des processeurs incorporant des FPU, les concepteurs de processeurs se sont dit qu'il fallait normaliser le stockage des flottants en mémoire ainsi que les résultats des calculs afin que tous les ordinateurs supportent les mêmes flottants et pour que les calculs flottants donnent les mêmes résultats, quel que soit l'ordinateur. C'est ainsi qu'est née la norme IEEE 754. La norme IEEE 754 normalisant les formats des flottants, la façon dont les arrondis et calculs sont faits, le matériel doit donc être prévu pour, si on veut éviter de devoir tout émuler avec des entiers, et obtenir des performances correctes.

La norme IEEE 754 impose diverses choses concernant nos flottants. Elle impose un certain format en mémoire : les flottants doivent être stockés dans la mémoire d'une certaine façon, standardisée par la norme. Elle impose une façon d'organiser les bits de nos nombres flottants en mémoire. Il faut tout de même noter qu'il existe d'autres normes et formats de nombres flottants, moins utilisés. On peut par exemple citer l'IBM floating point architecture

II-A. Nombres flottants

La façon utilisée pour écrire nos nombres flottants en binaire est très différente de celle utilisée pour coder des nombres entiers. L'écriture d'un nombre flottant en binaire est basée sur son écriture scientifique.

Pour rappel, en décimal, l'écriture scientifique d'un nombre consiste à écrire celui-ci comme un produit entre un nombre et une puissance de 10.

Ainsi, un nombre Image non disponible aura une écriture scientifique en base 10 de la forme :

Image non disponible

Notre nombre Image non disponible est un nombre à virgule dont la valeur est comprise entre 1 et 10 (1 inclus et 10 exclu). Ce nombre Image non disponible ne possède qu'un seul chiffre à gauche de la virgule : on peut toujours trouver un exposant tel que ce soit le cas.

En binaire, c'est à peu près la même chose. L'écriture scientifique binaire d'un nombre consiste à écrire celui-ci sous la forme

Image non disponible

Cette fois-ci on n'utilise plus une puissance de 10 (comme c'était le cas avec la base 10), mais une puissance de 2.

Autre changement : Image non disponible est un nombre à virgule qui ne possède qu'un seul chiffre à gauche de la virgule, comme en base 10. Le seul truc, c'est qu'en binaire, seuls deux chiffres sont possibles : 0 et 1. Le chiffre de Image non disponible situé à gauche de la virgule est donc soit un zéro ou un 1.

Si le nombre Image non disponible est différent de zéro, on peut toujours réussir à transformer notre nombre écrit ainsi en binaire de façon à ce que Image non disponible ait son chiffre situé à gauche de la virgule qui vaut 1.

En conséquence, l'écriture scientifique d'un nombre en binaire est donc quelque chose de la forme :

Image non disponible

Sauf pour quelques exceptions qu'on verra plus tard : le zéro par exemple.

Pour stocker cette écriture scientifique avec des zéros et des 1, il nous faut stocker la partie fractionnaire de notre nombre Image non disponible, qu'on appelle la mantisse et l'exposant. On rajoute souvent un bit de signe qui sert à calculer le signe du nombre flottant : ce bit vaut 1 si ce nombre est négatif et vaut 0 si notre flottant est positif.

Bit de signe | Exposant | Mantisse
0 | 0011 0001 | 111 0000 1101 1001

On calcule la valeur d'un flottant IEEE 754 en utilisant la formule qui suit :

Image non disponible

Le décalage est là pour permettre aux flottants de coder des nombres très petits : en effet, pour un exposant plus petit que le décalage, Image non disponible est négatif, et la puissance de deux correspondante sera un nombre très petit. Multiplié par la mantisse, cela permet de coder des nombres très très petits. Sans ce décalage, on ne pourrait pas coder de nombres très petits.

Remarquez bien un truc avec la mantisse : on doit lui ajouter 1 pour faire nos calculs avec : ne l'oubliez pas !

II-A-1. Exemple

Par exemple, prenons le nombre -3141,5. Celui-ci sera codé par le nombre flottant suivant :

1 1000 1010 1000 1000 1011 0000 0000 000

Ce nombre flottant utilise 23 bits pour la mantisse, 8 pour l'exposant et un bit de signe. Le décalage utilisé est de 127. Il s'agit d'un des types de flottant supportés par la norme IEEE 754 : le format simple précision.

II-A-2. Formats de flottants

Cette norme va en effet définir quatre types de flottants différents. Chacun de ces types de flottants pourra stocker plus ou moins de valeurs différentes. Ce qui différencie ces types de flottant, c'est simplement le nombre de bits utilisés pour la mantisse et l'exposant, ainsi que le décalage.

Voici quelques-uns de ces types de flottants :

Format | Nombre de bits utilisés pour coder un flottant | Nombre de bits de l'exposant | Nombre de bits pour la mantisse | Décalage
Simple précision | 32 | 8 | 23 | 127
Double précision | 64 | 11 | 52 | 1023
Double précision étendue | 80 ou plus | 15 ou plus | 64 ou plus | 16383 ou plus

Certains formats de flottants ne sont pas forcément supportés par les unités de calcul de notre processeur. Par exemple, certains ne supportent que les flottants simple précision et rien d'autre. Si un processeur ne supporte pas un format de flottant, il doit émuler son support, généralement par logiciel. Exemple : les premiers processeurs Intel ne géraient que les flottants double précision étendue.

De plus, certains processeurs gèrent des formats de flottants spéciaux qui ne font pas partie de la norme IEEE 754, même s'ils sont plus rares.

II-B. Flottants spéciaux

IEEE 754 impose aussi le support de certains nombres flottants spéciaux. Parmi eux, on trouve l'infini (aussi bien en négatif qu'en positif), la valeur NaN qui est utilisée pour signaler des erreurs ou des calculs n'ayant mathématiquement pas de sens, ou des nombres spéciaux nommés les dénormaux qui représentent des valeurs très petites et qui sont utilisés dans des scénarios de calcul assez particuliers.

II-B-1. Flottants dénormalisés

Pour les dénormaux, aussi appelés flottants dénormalisés, l'exposant auquel on a enlevé le décalage prend la plus petite valeur possible : en clair, l'exposant vaut zéro.

Ces flottants ont une particularité. On calcule leur valeur grâce à la formule suivante :

Image non disponible

On n'a pas besoin d'ajouter 1 à la mantisse pour calculer avec.

Bit de signe | Exposant | Mantisse
1 ou 0 | 0 | Mantisse différente de zéro

Là encore, ces flottants dénormalisés ne sont pas forcément supportés par certains processeurs. Si le résultat d'un calcul est un nombre dénormalisé, notre processeur va générer ce qu'on appelle une exception matérielle : le processeur va interrompre temporairement l'exécution du programme en cours et va exécuter un petit programme capable de traiter l'exception. Dans notre cas, ce programme va émuler logiciellement le traitement de ces dénormaux.

Mais même lorsque la gestion des dénormaux est implémentée en hardware (comme c'est le cas sur certains processeurs AMD), celle-ci reste malgré tout très lente.

II-B-2. Le zéro

Le zéro est le second de ces flottants spéciaux. Il ressemble un peu aux flottants dénormalisés, à un détail près : la mantisse est nulle ! C'est la seule différence avec les autres flottants dénormalisés : l'exposant est bel et bien nul, et on n'a pas besoin d'ajouter 1 à la mantisse pour calculer sa valeur à partir de sa représentation en binaire.

Bit de signe | Exposant | Mantisse
1 ou 0 | 0 | 0

Au fait, remarquez que le zéro est codé deux fois à cause du bit de signe. Si vous mettez l'exposant et la mantisse à la bonne valeur de façon à avoir zéro, le bit de signe pourra valoir aussi bien 1 que 0 : on se retrouve avec un Image non disponible et un Image non disponible.

II-B-3. l'infini !

La norme impose la présence de deux flottants représentant respectivement l'infini positif et l'infini négatif.

L'infini ( Image non disponible) est codé de la façon suivante :

Bit de signe | Exposant | Mantisse
0 ou 1 | Valeur maximale possible de l'exposant | 0000 0000 0000 0000 …

II-B-4. NaN

NaN est l'abréviation de « Not A Number », ce qui signifie : n'est pas un nombre. NaN est utilisée pour signaler des erreurs ou des calculs n'ayant mathématiquement pas de sens. Malgré son nom explicite, on peut faire des opérations avec NaN, mais cela ne sert pas vraiment à grand-chose : une opération arithmétique appliquée avec un NaN aura un résultat toujours égal à NaN.

Voici comment celui-ci est codé :

Bit de signe | Exposant | Mantisse
1 ou 0, c'est au choix | Valeur maximale possible de l'exposant | Différent de zéro

En fait, il existe différents types de NaN, qui différent par la valeur de leur mantisse, ainsi que par les effets qu'ils peuvent avoir. Pour avoir plus de renseignements, voir ce lien : Nan - Wikipédia.

II-C. Exceptions et arrondis

La norme impose aussi une gestion des exceptions (attention, rien à voir avec les exceptions matérielles vues plus haut), qui arrivent lors de calculs particuliers. Ces exceptions correspondant à des erreurs, il est parfois possible de corriger celles-ci de façon à traiter l'erreur. Cette réponse peut être un arrêt de l'exécution du programme, ou un traitement particulier.

En voici la liste :

  • Invalid operation : opération qui produit un NAN ;
  • Overflow : résultat trop grand pour être stocké dans un flottant. Le plus souvent, on traite l'erreur en arrondissant le résultat vers Image non disponible ;
  • Underflow : pareil, mais avec un résultat trop petit. Le plus souvent, on traite l'erreur en arrondissant le résultat vers 0 ;
  • Division par zéro. La réponse la plus courante est de répondre + ou - l'infini ;
  • Inexact : le résultat ne peut être représenté par un flottant et on doit l'arrondir.

De temps en temps, le processeur peut être configuré de façon à répondre silencieusement à ces exceptions en fournissant un résultat, mais peut aussi être configuré de façon à déclencher une exception matérielle (à ne pas confondre avec les exceptions de la norme) qui fera remonter l'erreur à un programme qui s'occupera de gérer cette exception, qui corrigera celle-ci.

II-C-1. Arrondis

Pour donner un exemple avec l'exception Inexact, on va prendre le nombre Image non disponible. Ce nombre ne semble pourtant pas méchant, mais c'est parce qu'il est écrit en décimal.

En binaire, ce nombre s'écrit comme ceci : Image non disponible et ainsi de suite jusqu'à l'infini. Notre nombre utilise une infinité de décimales. Bien évidemment, on ne peut pas utiliser une infinité de bits pour stocker notre nombre et on doit impérativement l'arrondir.

Comme vous le voyez avec la dernière exception, le codage des nombres flottants peut parfois poser problème : dans un ordinateur, il se peut qu'une opération sur deux nombres flottants donne un résultat qui ne peut être codé par un flottant. On est alors obligé d'arrondir ou de tronquer le résultat de façon à le faire rentrer dans un flottant.

Pour éviter que des ordinateurs différents utilisent des méthodes d'arrondis différentes, on a décidé de normaliser les calculs sur les nombres flottants et les méthodes d'arrondis. Pour cela, la norme impose le support de quatre modes d'arrondis :

  • Arrondir vers + l'infini ;
  • vers - l'infini ;
  • vers zéro ;
  • vers le nombre flottant le plus proche.

II-C-2. Autres exceptions

L'exception Invalid operation est levée dans deux cas :

  • des calculs ayant un résultat qui est un nombre complexe : Image non disponible , Image non disponible, et autres ;
  • le calcul est une forme indéterminée.

Pour ceux qui ne savent pas trop ce que sont les formes indéterminées, voici en exclusivité la liste des calculs qui retournent NaN :

  • Image non disponible ;
  • Image non disponible et Image non disponible ;
  • dans certains cas, Image non disponible , Image non disponible, et Image non disponible.

Les overflow et underflow sont beaucoup plus rares : un nombre flottant peu contenir aussi bien de nombres très grands que des nombres très petits. Par exemple, un flottant simple précision peut stocker des nombres pouvant aller jusqu'à Image non disponible : difficile d'obtenir un overflow dans ces conditions.

II-D. Calculs standards

IEEE 754 standardise aussi quelques instructions sur les flottants qui doivent impérativement être supportées :

  • l'addition + ;
  • la multiplication * ;
  • la soustraction - ;
  • la division / ;
  • la racine carrée sqrt ;
  • et les différentes comparaisons >, <, >=, <=, ==, !=.

Le support de ces instructions est souvent matériel : le processeur possède souvent des instructions machine capables d'effectuer des comparaisons, additions, soustractions, multiplications, divisions et racines carrées. De nos jours, ces instructions sont directement câblées dans les circuits du processeur et ne sont plus émulées, sauf pour quelques-unes. Le choix du mode d'arrondi ou la gestion des exceptions sont implémentés directement dans le matériel et sont souvent configurables grâce à un registre du processeur : suivant la valeur mise dans celui-ci, le processeur arrondira les résultats des calculs d'une certaine façon et pas d'une autre, ou réagira d'une certaine manière aux exceptions vues au-dessus.

II-E. Processeur et norme IEEE 754

Il faut néanmoins préciser que le support de la norme IEEE 754 n'est pas une obligation : certains processeurs s'en moquent royalement. Dans certaines applications, la précision des calculs flottants n'est pas indispensable, et les programmeurs ont souvent besoin d'avoir des calculs qui s'effectuent rapidement : de telles situations nécessitent des calculs flottants rapides et se contentent très bien d'un résultat approché. Dans ces situations, on peut utiliser des formats de flottants différents de la norme IEEE 754 et les circuits de la FPU sont simplifiés pour être plus rapides. Par exemple, certains circuits ne gèrent pas les underflow, overflow, les NaN ou les infinis, voir utilisent des formats de flottants exotiques.

De plus, certaines architectures supportent la norme IEEE 754, mais ont besoin que le compilateur ou les programmes fassent quelques manipulations pour que les calculs effectués avec des flottants respectent la norme IEEE 754. Tout dépend des instructions machine que le compilateur utilise. Par exemple, certains processeurs implémentent non seulement les instructions de la norme, mais aussi d'autres instructions sur les flottants qui ne sont pas supportées par la norme IEEE 754. Par exemple, certaines fonctions mathématiques telles que sinus, cosinus, tangente, arc tangente et d'autres sont supportées par certaines FPU. Le seul problème, c'est que ces instructions peuvent mener à des erreurs de calcul. Or, certaines de ces instructions sont parfois utilisées par certains compilateurs, ce qui peut produire des erreurs de calcul incompatibles avec la norme IEEE 754.

III. Exemple : l'architecture x86

Voyons maintenant ce qu'il en est pour les processeurs basés sur l'architecture x86. Pour rappel, ces processeurs sont ceux qui sont présents à l'intérieur de nos PC : tous les processeurs Intel et AMD actuels sont des processeurs basés sur l'architecture x86. Ceux-ci peuvent très bien se passer complètement de FPU : celle-ci n'est rien d'autre qu'une option qui a été rajoutée au cours de l'existence de l'architecture. Néanmoins, de nos jours, tous les processeurs x86 présents dans nos PC possèdent au moins une FPU. Oui, j'ai dit au moins une, car certains processeurs récents possèdent une unité de calcul spécialisée dans les calculs multimédias qui est aussi capable de travailler sur des nombres flottants : l'unité SSE.

III-A. FPU x87

Tous les processeurs x86 capables de manipuler des flottants comportent une FPU de base, capable d'effectuer des opérations sur les flottants. L'ensemble des instructions machine supportées par cette unité s'appelle le jeu d'instructions x87. En conséquence, cette FPU est couramment appelée la FPU x87.

La FPU x87 est la FPU qui est utilisée par défaut sur les PC x86 32 bits. Par contre, avec le jeu d'instructions x86-64 bits, l'unité SSE est celle qui est utilisée de base pour calculer sur les nombres flottants, l'unité x87 étant souvent inutilisée.

Cette unité de calcul incorpore :

  • 8 registres de données capables de stocker un nombre flottant chacun ;
  • 3 registres d'état chargés de stocker des bits utiles pour configurer la FPU ;
  • 1 registre interne à la FPU utilisé pour gérer les exceptions flottantes, auquel seul le processeur a accès.

III-A-1. Pile de registres

Les registres de l'unité x87 sont gérés d'une manière assez inédite : ceux-ci forment ce qu'on appelle une pile de registres. Les processeurs qui gèrent leurs registres de cette manière sont très rares. Il faut aussi préciser que les autres unités de calcul d'un processeur x86 gèrent leurs registres différemment. Mais voyons un peu ce que cette pile de registres a de particulier.

Si vous souhaitez stocker un flottant dans un registre, vous ne pouvez pas décider dans quel registre celui-ci sera stocké : nos registres sont en effet organisés sous la forme d'une pile de registres que l'on remplit dans un ordre bien précis.

Tout d'abord, ces 8 registres sont ordonnés et numérotés de 0 à 7 : le premier registre est le registre 7, tandis que le dernier registre est le registre 0. Lorsque la FPU x87 est initialisée, ces 8 registres sont complètement vides : ils ne contiennent aucun flottant.

Si on veut ajouter des flottants dans nos registres, on doit les « remplir » dans un ordre de remplissage imposé : on remplit d'abord le registre 7, puis le 6, puis le 5, et ainsi de suite jusqu'au registre 0. Si on veut ajouter un flottant dans cette pile de registres, celui-ci sera stocké dans le premier registre vide dans l'ordre de remplissage indiqué au-dessus. Prenons un exemple, les 3 premiers registres sont occupés par un flottant et on veut charger un flottant supplémentaire : le 4e registre sera utilisé pour stocker ce flottant.

Image non disponible

La même chose existe pour le « déremplissage » des registres. Imaginez que vous souhaitez déplacer le contenu d'un registre dans la mémoire RAM et effacer complètement son contenu. On ne peut pas choisir n'importe quel registre pour faire cela : on est obligé de prendre le registre non vide ayant le numéro le plus grand.

Image non disponible

Vous remarquerez sûrement que ce flottant est le dernier à avoir été dans cette « pile » de registres. Notre pile de registres est donc gérée par un algorithme de type LIFO : Last Input First Output : dernier entré, premier sorti.

III-A-2. Registres de contrôle et de statut

Si vous avez bonne mémoire, vous vous souvenez sûrement de ce que j'ai dit sur les registres de la FPU : elle contient 3 registres spéciaux qui ne stockent pas de flottants, mais sont malgré tout utiles. Ces 3 registres portent chacun un nom : Control Word, Status Word et Tag Word. Voilà, les présentations sont faites !

On va commencer par vous détailler le registre Tag Word. Celui-ci est utilisé par le processeur pour savoir si un registre est vide ou non : avouez que c'est pratique pour gérer la pile de registres vue au-dessus. Ce registre contient 16 bits et pour chacun des 8 registres de données de la FPU, 2 bits sont réservés dans le registre Tag Word. Ces deux bits contiennent des informations sur le contenu du registre de données réservé.

  • Si ces deux bits valent 00, le registre contient un flottant « normal » différent de zéro ;
  • Si ces deux bits valent 01, le registre contient une valeur nulle : 0 ;
  • Si ces deux bits valent 10, le registre contient un NAN, un infini, ou un dénormal ;
  • Si ces deux bits valent 11, le registre est vide et ne contient pas de nombre flottant.

Passons maintenant au Status Word. Celui-ci fait lui aussi 16 bits et chacun de ces bits a une utilité particulière. On ne va en citer que quelques-uns.

Bit | Utilité
TOP | Ce registre contient trois bits regroupés en un seul ensemble nommé TOP, qui stocke le numéro du premier registre vide dans l'ordre de remplissage. Idéal pour gérer notre pile de registres
U | Sert à détecter les underflow. Il est mis à 1 lorsqu'un underflow a lieu.
O | Pareil que U, mais pour les overflow : ce registre est mis à 1 lors d'un overflow
Z | C'est un bit qui est mis à 1 lorsque notre FPU exécute une division par zéro
D | Ce bit est mis à 1 lorsqu'un résultat de calcul est un dénormal ou lorsqu'une instruction doit être exécutée sur un dénormal
I | Bit mis à 1 lors de certaines erreurs telles que l'exécution d'une instruction de racine carrée sur un négatif ou une division du type 0/0

Comme vous le voyez, le Status Word contient tout ce qu'il faut pour qu'un programme puisse comprendre la cause d'une exception.

Enfin, voyons le Control Word, le petit dernier. Celui-ci fait toujours 16 bits et contient lui aussi des bits ayant chacun une utilité précise. Beaucoup de bits de ce registre sont inutilisés et on ne va citer que les plus utiles.

Bit | Utilité
Infinity Control | S'il vaut zéro, les infinis sont tous traités comme s'ils valaient Image non disponible. S'il vaut un, les infinis sont traités normalement
Rouding Control C'est un ensemble de deux bits qui détermine le mode d'arrondi utilisé
  • 00 : vers le nombre flottant le plus proche : c'est la valeur par défaut ;
  • 01 : vers - l'infini ;
  • 10 : vers + l'infini ;
  • 11 : vers zéro
Precision Control Ensemble de deux bits qui détermine la taille de la mantisse de l'arrondi du résultat d'un calcul. En effet, on peut demander à notre FPU d'arrondir le résultat de chaque calcul qu'elle effectue. Cette instruction ne touche pas à l'exposant, mais seulement à la mantisse. La valeur par défaut de ces deux bits est 11 : notre FPU utilise donc des flottants double précision étendue. Les valeurs 00 et 10 demandent au processeur d'utiliser des flottants non pris en compte par la norme IEEE 754.
  • 00 : mantisse codée sur 24 bits ;
  • 01 : valeur inutilisée ;
  • 10 : mantisse codée sur 53 bits ;
  • 11 : mantisse codée sur 64 bits

III-A-3. Instructions x87

Notre FPU est capable d'effectuer un certain nombre d'instructions sur ces registres : les instructions de base supportées par la norme IEEE 754. Mais elle est aussi capable d'en effectuer d'autres.

La FPU x87 dispose d'instructions de comparaisons compatibles avec la norme IEEE 754, capables de comparer le flottant au sommet de la pile avec un autre nombre qui peut être flottant ou entier !

Voici une liste de quelques instructions de comparaisons supportées par les FPU 87 :

  • FCOM : compare le contenu du registre 0 avec une constante flottante ;
  • FCOMI : compare le contenu des registres 0 et 1 ;
  • FICOM : compare le contenu du registre 0 avec une constante entière ;
  • FTST : compare le registre numéroté 0 avec la valeur 0.

On trouve aussi des instructions de calculs, bien évidemment compatibles avec la norme IEE754 :

  • l'addition : FADD ;
  • la soustraction FSUB ;
  • la multiplication FMUL ;
  • la division FDIV ;
  • la racine carrée FSQRT ;
  • des instructions de calcul de la valeur absolue (FABS) ou encore de changement de signe (FCHS).

Notre FPU implémente aussi des instructions non prévues dans la norme IEE754 telles que :

  • le cosinus : instruction FCOS ;
  • le sinus : instruction FSIN ;
  • la tangente : instruction FPTAN ;
  • l'arc tangente : instruction FPATAN ;
  • ou encore des instructions de calcul de logarithmes ou d'exponentielles.

Il va de soi que ces instructions ne sont pas supportées par la norme IEEE 754 et que tout compilateur qui souhaite être compatible avec la norme IEEE 754 ne doit pas utiliser ces instructions de calcul non supportées.

- Les instructions de calcul n'ayant besoin que d'un seul flottant pour s'exécuter (les instructions de calcul d'une tangente, d'une racine carrée et d'autres) vont utiliser le flottant situé au sommet de la pile.
- Les instructions qui ont besoin de deux flottants (multiplication, addition, soustraction et autres) vont agir différemment suivant la situation.
1 ) Elles peuvent prendre les deux flottants les plus haut placés dans cette pile,
2) prendre le flottant au sommet de la pile, utiliser une donnée en provenance de la mémoire,
3) ou encore utiliser le flottant le plus haut placé et un flottant stocké dans l'importe quel registre de cette pile de registres.

En plus de ces instructions de calcul, notre FPU possède des instructions permettant de transférer des flottants entre la mémoire et les registres, ou entre registres. On peut citer par exemple

Instruction | Ce qu'elle fait
FLD | Elle est capable de charger un nombre flottant depuis la mémoire vers notre pile de registres vue au-dessus. Cette instruction peut charger un flottant codé sur 32 bits, 64 bits ou 80 bits
FSTP | Déplace le contenu d'un registre vers la mémoire. Une autre instruction existe qui est capable de copier le contenu d'un registre vers la mémoire sans effacer le contenu du registre : c'est l'instruction FST
FXCH | Échange le contenu du dernier registre non vide dans l'ordre de remplissage (celui situé au sommet de la pile) avec un autre registre

D'autres instructions existent qui chargent certaines constantes (PI, 1, 0, certains logarithmes en base 2) dans le registre au sommet de la pile de registres.

III-A-4. Double Rounding

Chacun des registres de données vus plus haut stocke un nombre flottant codé sur 80 bits. Oui, vous avez bien lu, 80 bits et non 32 ou 64 : cette FPU calcule sur des nombres flottants double précision étendue et non sur des flottants simple ou double précision, qui ne sont pas gérés par la FPU x87.

Mais comment cette FPU fait-elle pour calculer avec des flottants simple et double précision ?

Tout se joue lors de l'accès à la mémoire avec l'instruction FLD : celle-ci se comporte différemment suivant le flottant qu'on lui demande de charger. En effet, cette instruction peut charger depuis la mémoire un flottant simple précision, double précision ou double précision étendue. Le format du flottant qui doit être chargé est stocké directement dans l'instruction. Je m'explique : une instruction machine est stockée en mémoire sous la forme d'une suite de bits, et pour certaines instructions, des bits supplémentaires sont ajoutés. Dans notre cas, ces bits optionnels servent à indiquer à notre instruction le format du flottant qu'elle doit charger.

La FPU x87 peut charger depuis la mémoire un nombre flottant 80 bits directement dans un registre. Pour les flottants 32 et 64 bits, la FPU va devoir effectuer une conversion de notre flottant simple ou double précision en un flottant 80 bits. Tous les calculs faits par notre FPU vont donner des résultats codés sur 80 bits, et ceux-ci restent codés sur 80 bits tant que ceux-ci sont stockés dans les registres de la FPU. Par contre, dès qu'il faut enregistrer un nombre flottant en mémoire RAM, les problèmes commencent. Si le flottant en question est stocké dans la mémoire sur 32 ou 64 bits, notre processeur doit convertir le contenu du registre dans le format du flottant en mémoire, histoire de conserver le bon format de base. Cette conversion est faite automatiquement par l'instruction d'écriture en mémoire utilisée. Par contre, si notre flottant est représenté en mémoire sur 80 bits, l'écriture en mémoire est directe : pas de conversion.

Et ces conversions posent problème : elles ne respectent pas la norme IEEE 754 !

Comparons un calcul effectué sur un processeur gérant nativement les formats 64 et 32 bits et ce même calcul exécuté par la x87. Sur le premier processeur, le calcul sera effectué sur des flottants simple ou double précision. Notre processeur va faire ses calculs sur notre flottant directement dans ses registres.

Image non disponible

On se retrouve donc avec deux cas de figure :

  • si un des résultats de ces calculs est un flottant, celui-ci sera directement codé sur 32 ou 64 bits sans arrondi ;
  • si un de ces résultats n'est pas un flottant, celui-ci sera arrondi de façon à donner un résultat qui peut être contenu dans un flottant 32/64 bits. On a donc un seul arrondi : celui qui arrive lors des calculs.

Avec la x87, le scénario est différent. Nos flottants 32/64 bits sont convertis en un flottant x87 80 bits lorsque l'on charge ceux-ci depuis la mémoire. Le calcul est ensuite effectué sur des flottants 80 bits. Là encore, le résultat est soit un flottant x87, soit est arrondi pour donner un flottant x87.

Maintenant, si vous voulez enregistrer ce flottant x87 80 bits dans la mémoire, il va falloir convertir celui-ci dans son format de base : 32 ou 64 bits. Notre résultat se fait arrondir au flottant 32 ou 64 bits le plus proche. On se retrouve donc avec un arrondi supplémentaire : c'est le phénomène du double rounding (qui signifie double-arrondi en français).

Image non disponible

Et rien n'implique que le résultat de ces deux conversions aurait donné le même résultat que le calcul effectué sur des flottants 64 bits avec un seul éventuel arrondi ! Le pire, c'est qu'on n'a aucune solution à ce problème, pour les calculs faits avec la FPU x87.

Pour citer un exemple, sachez que des failles de sécurité de PHP et de Java aujourd'hui corrigées et qui avaient fait la une de la presse informatique étaient causées par ces arrondis supplémentaires. Bien sûr, sachez que ce bogue a pu être reproduit sur de nombreux autres langages et n'était certainement pas limité au PHP ou au Java : c'est le non-respect de la norme IEE754 par notre unité de calcul x87 qui était clairement en cause.

Mais ces problèmes d'arrondis ne s'arrêtent pas là. Si une série de calculs est faite sur des flottants stockés dans l'un de ces 8 registres, les résultats intermédiaires auront une précision supérieure, et seul le résultat final, stocké en mémoire sera arrondi. Dans ces conditions, le résultat peut être différent de celui qu'on aurait obtenu en utilisant seulement des flottants 64 bits lors des calculs.

Par exemple, prenons le calcul de Image non disponible, la base des logarithmes népériens. Cette constante est égale à la somme de l'inverse des factorielles des nombres entiers Image non disponible.

Si vous calculez cette constante sur un ordinateur vous allez devoir effectuer le calcul suivant : Image non disponible. Bien sûr, vous allez devoir arrêter de calculer quand le résultat aura atteint la précision requise (nombre k).

Et bien, sachez que ce calcul effectué sur l'unité x87 ne donnera pas un résultat identique au même calcul effectué sur autre unité de calcul flottant. Bien sûr, l'erreur de calcul n'est pas énorme et n'apparaît qu'après une bonne dizaine de décimales, mais elle existe.

Autre problème : rares sont les calculs effectués intégralement dans les registres, et on est parfois obligé de temporairement sauvegarder en mémoire le contenu d'un registre pour laisser le registre libre pour un autre nombre flottant. C'est le programmeur ou le compilateur qui gère quand effectuer ce genre de sauvegarde et sur quels registres. Chacune de ces sauvegardes va arrondir le flottant que l'on souhaite sauvegarder. Conséquence : suivant l'ordre de ces sauvegardes, le moment auquel elles ont lieu et les flottants qui sont choisis pour être sauvegardés, le résultat ne sera pas le même ! Avec le même programme, si vous décidez de sauvegarder un flottant et votre voisin un autre, ce ne sera pas le même flottant qui sera arrondi lors de son transfert en mémoire, et le résultat des calculs sur votre ordinateur sera différent des résultats obtenus sur l'ordinateur de votre voisin.

Pour limiter la casse, il existe une solution : sauvegarder tout résultat d'un calcul sur un flottant directement dans la mémoire RAM. Comme cela, on se retrouve avec des calculs effectués uniquement sur des flottants 32/64 bits ce qui supprime pas mal d'erreurs de calcul.

III-B. Unités de calcul SSE

Notre unité SSE est plus vertueuse : elle gère totalement la norme IEEE 754 et possède des circuits parfaitement compatibles avec la norme IEEE 754. Elle gère nativement les nombres flottants simple et double précision sans problème ni conversions en flottants 80 bits : plus aucun problème de double rounding. Cette unité est rarement utilisée de nos jours par les programmes compilés en 32 bits. Pour des raisons de compatibilité, rares sont les compilateurs qui utilisent par défaut les instructions SSE pour calculer sur les flottants : le jeu d'instructions SSE a en effet été rajouté récemment à l'architecture x86 et certains processeurs anciens ne le supportent pas.

Sur les processeurs x86 64 bits, c'est l'inverse : les instructions SSE font partie intégrante du jeu d'instructions x86-64 et l'unité SSE est utilisée par défaut à la place de l'unité x87 pour tout calcul sur les flottants.

III-B-1. Jeu d'instructions SSE

Il faut savoir qu'il existe plusieurs versions du jeu d'instructions SSE. Une première version est apparue en 1999 sur le processeur Pentium III, qui comportait 70 instructions. Depuis, de nombreuses autres instructions ont été ajoutées aux unités SSE, et chacun de ces rajouts a été accompagné d'un changement de nom du jeu d'instructions, dans un but marketing évident : SSE2, SSE3, SSSE3, SSE4, SSE5…

Il faut remarquer une chose : ces instructions ne travaillent pas toutes avec des nombres flottants et certaines peuvent « instructionner » sur des nombres entiers ! Notre unité SSE n'est donc pas vraiment une FPU au sens propre du terme.

Bien sûr, on retrouve les instructions de base de la norme IEEE 754 pour les calculs flottants, ainsi que de nombreuses autres instructions supplémentaires, non normalisées par la norme IEEE 754. Mais ne comptez pas sur moi pour vous détailler les centaines d'instructions supportées par cette unité de calcul comme on l'a fait pour la FPU x87.

L'unité SSE possède un mode de gestion des dénormaux, activé par défaut, qui consiste à remplacer tous les dénormaux par des zéros, afin de gagner en performances. En effet, les calculs sur les dénormaux sont particulièrement lents et remplacer ceux-ci par des zéros permettra d'améliorer la rapidité des calculs (en diminuant légèrement leur précision). Il va de soi que la norme IEE754 n'est pas censée tolérer ce genre de comportements.

III-B-2. Registres SSE

La FPU SSE des processeurs 32 bits contient 8 registres dédiés à cette unité de calcul. Ces registres sont nommés de XMM0 à XMM7. Sur les processeurs 64 bits, on trouve 16 registres numérotés de XMM0 à XMM15. En plus de ces registres de données, un nouveau registre 32 bits de contrôle/statut nommé MXCSR a fait son apparition. Chacun de ces registres peut contenir 128 bits, ce qui leur permet de contenir plusieurs nombres flottants à la fois : 4 flottants simple précision et 2 flottants double précision. Il faut savoir que le jeu d'instructions SSE a été inventé pour que chaque instruction SSE puisse être effectuée sur plusieurs données à la fois. Pour faciliter la conception des unités de calcul SSE, les concepteurs de cette unité ont décidé de placer plusieurs de ces données dans un seul et même registre. Il a donc fallu utiliser des registres assez grands en taille.

Image non disponible

Ces registres ne sont pas organisés comme les registres de la FPU x87 et peuvent être lus, sélectionnés et écrits de façon indépendante : il n' y a pas d'ordre de remplissage et une instruction peut agir sur des données venant de n'importe quel registre.

Vu que le jeu d'instructions SSE n'est pas limité aux flottants, mais gère aussi des calculs sur des entiers, ces registres peuvent stocker aussi bien des flottants que des entiers.

IV. Conclusion

Voilà, ce tutoriel est enfin terminé.

Je vais finir en vous donnant quelques liens qui pourraient vous être utiles si vous voulez approfondir le sujet.

  • Pitfalls with floating point calculus ;
  • Jeu d'instructions d'une FPU x87.


MASM_PASCAL

Les instructions du FPU

The Pascal language type does not cause names to be mapped to uppercase.

F2XM1 Instruction
----------------------------------------------------------------------------

  Title:    Calculate 2(^X)-1

  Syntax:   F2XM1

  See also: FYL2X, FYL2XP1

  Description:

     Calculates Y = 2(^X) - 1 where X is taken from ST. The result, Y,
     is returned in ST. X must be in the range 0 <= X <= 0.5 on the
     8087/287, or in the range -1.0 <= X <= +1.0 on the 80387/486.

-----------------+------------------------+---------------------------------
 F2XM1           | f2xm1                  |  87 310-630
                 |                        | 287 310-630
                 |                        | 387 211-476
                 |                        | 486 140-279
-----------------+------------------------+---------------------------------

FABS Instruction
----------------------------------------------------------------------------

  Title:    Absolute Value

  Syntax:   FABS

  See also: FCHS, FRNDINT

  Description:

     Converts the element in ST to its absolute value.

-----------------+------------------------+---------------------------------
 FABS            | fabs                   |  87 10-17
                 |                        | 287 10-17
                 |                        | 387 22
                 |                        | 486 3
-----------------+------------------------+---------------------------------

FADD Instruction
----------------------------------------------------------------------------

  Title:    Add

  Syntax:   FADD [dest,src]
            FADDP dest,ST
            FADD src
            FIADD src

  See also: FSUB, FSUBR

  Description:

     Adds the source to the destination and returns the sum in the
     destination. If two register operands are specified, one must be
     ST. If a memory operand is specified, the sum replaces the value
     in ST. Memory operands can be 32- or 64-bit real numbers or 16-
     or 32-bit integers.

     If no operand is specified, ST is added to ST(1) and the stack is
     popped; the sum is returned in ST. For FADDP, the source must be
     ST; the sum is returned in the destination and ST is popped.

----------------+-------------------------+---------------------------------
 FADD [reg,reg] | fadd   st,st(2)         |  87 70-100
                | fadd   st(5),st         | 287 70-100
                | fadd                    | 387 to=23-31,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FADDP reg,ST   | faddp  st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 23-31
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FADD memreal   | fadd   QWORD PTR [bx]   |  87 (s=90-120,s=95-125)+EA
                | fadd   shortreal        | 287 s=90-120,l=95-125
                |                         | 387 s=24-32,l=29-37
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FIADD memint   | fiadd  int16            |  87 (w=102-137,d=108-143)+EA
                | fiadd  warray[di]       | 287 w=102-137,d=108-143
                | fiadd  double           | 387 w=71-85,d=57-72
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------

FCHS Instruction

----------------------------------------------------------------------------

  Title:    Change Sign

  Syntax:   FCHS

  See also: FABS

  Description:

     Reverses the sign of the value in ST.

-----------------+------------------------+---------------------------------
 FCHS            | fchs                   |  87 10-17
                 |                        | 287 10-17
                 |                        | 387 24-25
                 |                        | 486 6
-----------------+------------------------+---------------------------------

FCLEX Instruction

----------------------------------------------------------------------------

  Title:    Clear Exceptions

  Syntax:   FCLEX
            FNCLEX

  See also: Status Word

  Description:

     Clears all exception flags, the busy flag, and bit 7 in the status
     word. Bit 7 is the interrupt-request flag on the 8087 and the
     error-status flag on the 80287, 80387, and 80486.

     This instruction has wait and no-wait versions. The wait version
     (FCLEX) checks for unmasked numeric errors. The no-wait version
     (FNCLEX) does not. When the .8087 directive is used, the assembler
     puts the WAIT instruction before the wait versions and the NOP
     instruction before the no-wait versions.

-----------------+------------------------+---------------------------------
 FCLEX           | fclex                  |  87 2-8
 FNCLEX          |                        | 287 2-8
                 |                        | 387 11
                 |                        | 486 7
-----------------+------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.

FCOM Instruction
----------------------------------------------------------------------------

  Title:    Compare

  Syntax:   FCOM [src]
            FCOMP [src]
            FCOMPP
            FICOM src
            FICOMP src

  See also: FTST, FUCOM, FSTSW, SAHF, FXAM, Status Word

  Description:

     Compares the specified source operand to ST and sets the condition
     codes of the status word according to the result. The instruction
     subtracts the source operand from ST without changing either
     operand. Memory operands can be 32- or 64-bit real numbers or 16-
     or 32-bit integers.

     If no operand is specified or if two pops are specified, ST is
     compared to ST(1) and the stack is popped. If one pop is specified
     with an operand, the operand is compared to ST. If one of the
     operands is a NAN, an invalid-operation exception occurs.
     See: FUCOM

     The condition codes for FCOM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST > source
     0      0      ?      1      ST < source
     1      0      ?      0      ST = source
     1      1      ?      1      ST not comparable to source

----------------+-------------------------+---------------------------------
 FCOM [reg]     | fcom   st(2)            |  87 40-50
                | fcom                    | 287 40-50
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMP [reg]    | fcomp  st(7)            |  87 42-52
                | fcomp                   | 287 42-52
                |                         | 387 26
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMPP         | fcompp                  |  87 45-55
                |                         | 287 45-55
                |                         | 387 26
                |                         | 486 5
----------------+-------------------------+---------------------------------
 FCOM memreal   | fcom   shortreals[di]   |  87 (s=60-70,l=65-75)+EA
                | fcom   longreal         | 287 s=60-70,l=65-75
                |                         | 387 s=26,l=31
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMP memreal  | fcomp  longreal         |  87 (s=63-73,l=67-77)+EA
                | fcomp  shorts[di]       | 287 s=63-73,l=67-77
                |                         | 387 s=26,l=31
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FICOM memint   | ficom  double           |  87 (w=72-86,d=78-91)+EA
                | ficom  warray[di]       | 287 w=72-86,d=78-91
                |                         | 387 w=71-75,d=56-63
                |                         | 486 w=16-20,d=15-17
----------------+-------------------------+---------------------------------
 FICOMP memint  | ficomp WORD PTR [bp+6]  |  87 (w=74-88,d=80-93)+EA
                | ficomp darray[di]       | 287 w=74-88,d=80-93
                |                         | 387 w=71-75,d=56-63
                |                         | 486 w=16-20,d=15-17
----------------+-------------------------+---------------------------------

FSIN/FCOS/FSINCOS Instructions

----------------------------------------------------------------------------

  Title:    Sine/Cosine
            80387/486 Only

  Syntax:   FSIN
            FCOS
            FSINCOS

  See also: .387, .486, FPREM, FPREM1, FPTAN

  Description:

     FSIN replaces a value in radians in ST with its sine. FCOS
     replaces a value in radians in ST with its cosine. FSINCOS
     calculates the sine and cosine of a radian value in ST. The sine
     replaces the value in ST, and then the cosine is pushed onto the
     stack.

     If |ST| < 2^63, the C2 bit of the status word is cleared and the
     sine and/or cosine is calculated. Otherwise, C2 is set and no
     calculation is performed. ST can be reduced to the required range
     with FPREM or FPREM1.

----------------+-------------------------+---------------------------------
 FSIN           | fsin                    |  87 --
                |                         | 287 --
                |                         | 387 122-771*
                |                         | 486 257-354œ
----------------+-------------------------+---------------------------------
 FCOS           | fcos                    |  87 --
                |                         | 287 --
                |                         | 387 123-772*
                |                         | 486 257-354œ
----------------+-------------------------+---------------------------------
 FSINCOS        | fsincos                 |  87 --
                |                         | 287 --
                |                         | 387 194-809*
                |                         | 486 292-365œ
----------------+-------------------------+---------------------------------
  * For operands with an absolute value greater than pi/4, up to 76
    additional clocks may be required.

  œ For operands with an absolute value greater than pi/4, add n clocks
    where n = operand / (pi/4).

FINCSTP/FDECSTP Instructions

----------------------------------------------------------------------------

  Title:    Increment or Decrement Stack Pointer

  Syntax:   FINCSTP
            FDECSTP

  Description:

     Increments or decrements the stack-top pointer in the status word.
     No tags or registers are changed, and no data is transferred. If
     the stack pointer is 7, FINCSTP changes it to 0. If the stack
     pointer is 0, FDECSTP changes it to 7.

----------------+-------------------------+---------------------------------
 FINCSTP        | fincstp                 |  87 6-12
                |                         | 287 6-12
                |                         | 387 21
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FDECSTP        | fdecstp                 |  87 6-12
                |                         | 287 6-12
                |                         | 387 22
                |                         | 486 3
----------------+-------------------------+---------------------------------

FDISI/FENI Instructions

----------------------------------------------------------------------------

  Title:    Enable or Disable Interrupts
            8087 Only

  Syntax:   FDISI
            FNDISI
            FENI
            FNENI

  See also: .8087

  Description:

     FDISI disables interrupts by setting the interrupt-enable mask in
     the control word; FENI enables them by clearing the interrupt-
     enable mask. Since the 80287, 80387, and 80486 do not have an
     interrupt-enable mask, FDISI and FENI are recognized but ignored
     on these coprocessors.

     These instructions have wait and no-wait versions. The wait
     versions (FDISI and FENI) check for unmasked numeric errors; the
     no-wait versions (FNDISI and FNENI) do not. When the .8087
     directive is used, the assembler puts the WAIT instruction before
     the wait versions and the NOP instruction before the no-wait
     versions.

----------------+-------------------------+---------------------------------
 FDISI          | fdisi                   |  87 2-8
 FNDISI         |                         | 287 2
                |                         | 387 2
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FENI           | feni                    |  87 2-8
 FNENI          |                         | 287 2
                |                         | 387 2
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instructions.
        The wait versions may take additional clock cycles.

FDIV Instructions

----------------------------------------------------------------------------

  Title:    Divide

  Syntax:   FDIV [dest,src]
            FDIVP dest,ST
            FDIV src
            FIDIV src

  See also: FDIVR, FMUL

  Description:

     Divides the destination by the source and returns the quotient in
     the destination. If two register operands are specified, one must
     be ST. If a memory operand is specified, the quotient replaces the
     value in ST. Memory operands can be 32- or 64-bit real numbers or
     16- or 32-bit integers.

     If no operand is specified, ST(1) is divided by ST and the stack
     is popped; the result is returned in ST. For FDIVP, the source
     must be ST; the quotient is returned in the destination register
     and ST is popped.

----------------+-------------------------+---------------------------------
 FDIV [reg,reg] | fdiv   st,st(2)         |  87 193-203
                | fdiv   st(5),st         | 287 193-203
                | fdiv                    | 387 to=88,fr=91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVP reg,ST   | fdivp  st(6),st         |  87 197-207
                |                         | 287 197-207
                |                         | 387 91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIV memreal   | fdiv   DWORD PTR [bx]   |  87 (s=215-225,l=220-230)+EA
                | fdiv   shortreal[di]    | 287 s=215-225,l=220-230
                | fdiv   longreal         | 387 s=89,l=94
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FIDIV memint   | fidiv  int16            |  87 (w=224-238,d=230-243)+EA
                | fidiv  warray[di]       | 287 w=224-238,d=230-243
                | fidiv  double           | 387 w=136-140,d=120-127
                |                         | 486 w=85-89,d=84-86
----------------+-------------------------+---------------------------------

FDIVR Instructions

----------------------------------------------------------------------------

  Title:    Divide Reversed

  Syntax:   FDIVR [dest,src]
            FDIVRP dest,ST
            FDIVR src
            FIDIVR src

  See also: FDIV, FMUL

  Description:

     Divides the source by the destination and returns the quotient in
     the destination. If two register operands are specified, one must
     be ST. If a memory operand is specified, the quotient replaces the
     value in ST. Memory operands can be 32- or 64-bit real numbers or
     16- or 32-bit integers.

     If no operand is specified, ST is divided by ST(1) and the stack
     is popped; the result is returned in ST. For FDIVRP, the source
     must be ST; the quotient is returned in the destination register
     and ST is popped.

----------------+-------------------------+---------------------------------
 FDIVR [reg,reg]| fdivr  st,st(2)         |  87 194-204
                | fdivr  st(5),st         | 287 194-204
                | fdivr                   | 387 to=88,fr=91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVRP reg,ST  | fdivrp st(6),st         |  87 198-208
                |                         | 287 198-208
                |                         | 387 91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVR memreal  | fdivr  longreal         |  87 (s=216-226,l=221-231)+EA
                | fdivr  shortreal[di]    | 287 s=216-226,l=221-231
                |                         | 387 s=89,l=94
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FIDIVR memint  | fidivr double           |  87 (w=225-239,d=231-245)+EA
                | fidivr warray[di]       | 287 w=225-239,d=231-245
                |                         | 387 w=135-141,d=121-128
                |                         | 486 w=85-89,d=84-86
----------------+-------------------------+---------------------------------

FFREE Instruction
----------------------------------------------------------------------------

  Title:    Free Register

  Syntax:   FFREE dest

  See also: Coprocessor Environment

  Description:

     Changes the specified register's tag to empty without changing the
     contents of the register.

----------------+-------------------------+---------------------------------
 FFREE ST(i)    | ffree  st(3)            |  87 9-16
                |                         | 287 9-16
                |                         | 387 18
                |                         | 486 3
----------------+-------------------------+---------------------------------

FINIT Instruction

----------------------------------------------------------------------------

  Title:    Initialize Coprocessor

  Syntax:    FINIT
             FNINIT

  Description:

     Initializes the coprocessor and resets all the registers and flags
     to their default values. On the 80387/486, the condition codes of
     the status word are cleared. On the 8087/287, they are unchanged.

     This instruction has wait and no-wait versions. The wait version
     (FINIT) checks for unmasked numeric errors; the no-wait version
     (FNINIT) does not. When the .8087 directive is used, the assembler
     puts the WAIT instruction before the wait versions and the NOP
     instruction before the no-wait versions.

----------------+-------------------------+---------------------------------
 FINIT          | finit                   |  87 2-8
 FNINIT         |                         | 287 2-8
                |                         | 387 33
                |                         | 486 17
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.

FLD Instruction
----------------------------------------------------------------------------

  Title:    Load

  Syntax:   FLD src
            FILD src
            FBLD src

  See also: FLDconst, FST, FXCH

  Description:

     Pushes the specified operand onto the stack. All memory operands
     are automatically converted to temporary-real numbers before being
     loaded.

     Memory operands can be 32-, 64-, or 80-bit real numbers or 16-,
     32-, or 64-bit integers.

--------------+------------------------+-----------------------------------
 FLD reg      | fld   st(3)            |  87 17-22
              |                        | 287 17-22
              |                        | 387 14
              |                        | 486  4
--------------+------------------------+-----------------------------------
 FLD memreal  | fld   longreal         |  87 (s=38-56,l=40-60,t=53-65)+EA
              | fld   shortarray[bx+di]| 287 s=38-56,l=40-60,t=53-65
              | fld   tempreal         | 387 s=20,l=25,t=44
              |                        | 486 s=3,l=3,t=6
--------------+------------------------+-----------------------------------
 FILD memint  | fild  mem16            |  87 (w=46-54,d=52-60,q=60-68)+EA
              | fild  DWORD PTR [bx]   | 287 w=46-54,d=52-60,q=60-68
              | fild  quads[si]        | 387 w=61-65,d=45-52,q=56-67
              |                        | 486 w=13-16,d=9-12,q=10-18
--------------+------------------------+-----------------------------------
 FBLD membcd  | fbld  packbcd          |  87 (290-310)+EA
              |                        | 287 290-310
              |                        | 387 266-275
              |                        | 486 70-103
--------------+------------------------+-----------------------------------

FLDconst Instruction
----------------------------------------------------------------------------

  Title:    Load Constant

  Syntax:   FLDconst

  See also: FLD

  Description:

     Pushes a constant onto the stack. The following constants can be
     loaded:

     Instruction     Constant

     FLD1            +1.0

     FLDZ            +0.0

     FLDPI           pi

     FLDL2E          Log (e)
                        2

     FLDL2T          Log (10)
                        2

     FLDLG2          Log  (2)
                        10

     FLDLN2          Log (2)
                        e
----------------+-------------------------+---------------------------------
 FLD1           | fld1                    |  87 15-21
                |                         | 287 15-21
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FLDZ           | fldz                    |  87 11-17
                |                         | 287 11-17
                |                         | 387 20
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FLDPI          | fldpi                   |  87 16-22
                |                         | 287 16-22
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDL2E         | fldl2e                  |  87 15-21
                |                         | 287 15-21
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDL2T         | fldl2t                  |  87 16-22
                |                         | 287 16-22
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDLG2         | fldlg2                  |  87 18-24
                |                         | 287 18-24
                |                         | 387 41
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDLN2         | fldln2                  |  87 17-23
                |                         | 287 17-23
                |                         | 387 41
                |                         | 486 8
----------------+-------------------------+---------------------------------

FLDCW/FSTCW Instructions

----------------------------------------------------------------------------

  Title:    Load or Store Control Word

  Syntax:   FLDCW src
            FSTCW dest
            FNSTCW dest

  See also: FSTSW, FSTENV, FSAVE, Control Word

  Description:

     FLDCW loads the specified word into the coprocessor control word.
     FSTCW stores the control word to a specified 16-bit memory
     operand.

     FSTCW has wait and no-wait versions. The wait version (FSTCW)
     checks for unmasked numeric errors; the no-wait version (FNSTCW)
     does not. When the .8087 directive is used, the assembler puts the
     WAIT instruction before the wait version and the NOP instruction
     before the no-wait version.

----------------+-------------------------+---------------------------------
 FLDCW mem16    | fldcw  ctrlword         |  87 (7-14)+EA
                |                         | 287 7-14
                |                         | 387 19
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FSTCW mem16    | fstcw  ctrlword         |  87 12-18
 FNSTCW mem16   |                         | 287 12-18
                |                         | 387 15
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the FSTCW. The wait
        version may take additional clock cycles.

FSTENV/FLDENV Instructions
----------------------------------------------------------------------------

  Title:    Load or Store Environment State

  Syntax:   FSTENV dest
            FNSTENV dest
            FLDENV mem

  See also: FSAVE, FSTCW, FSTSW, Coprocessor Environment

  Description:

     FSTENV stores the 14-byte coprocessor environment state to a
     specified memory location. FLDENV loads it from a specified memory
     location.

     The environment state includes the control word, status word, tag
     word, instruction pointer, and operand pointer. On the 80387/486
     in 32-bit mode, the environment state is 28 bytes.

     The FSTENV instruction has wait and no-wait versions. The wait
     version (FSTENV) checks for unmasked numeric errors; the no-wait
     version (FNSTENV) does not. When the .8087 directive is used, the
     assembler puts the WAIT instruction before the wait version and
     the NOP instruction before the no-wait version.

----------------+-------------------------+---------------------------------
 FLDENV mem     | fldenv [bp+10]          |  87 (35-45)+EA
 FLDENVW mem*   |                         | 287 35-45
 FLDENVD mem*   |                         | 387 71
                |                         | 486 44,pm=34
----------------+-------------------------+---------------------------------
 FSTENV mem     | fstenv [bp-14]          |  87 (40-50)+EA
 FSTENVW mem*   |                         | 287 40-50
 FSTENVD mem*   |                         | 387 103-104
 FNSTENV mem    |                         | 486 67,pm=56
 FNSTENVW mem*  |                         |
 FNSTENVD mem*  |                         |
----------------+-------------------------+---------------------------------
  * 80387/486 only.

  NOTE: The timings reflect the no-wait versions of FSTENV. The wait
        versions may take additional clock cycles.

FMUL Instruction
----------------------------------------------------------------------------

  Title:    Multiply

  Syntax:   FMUL [dest,src]
            FMULP dest,ST
            FMUL src
            FIMUL src

  See also: FDIV, FDIVR

  Description:

     Multiplies the source by the destination and returns the product
     in the destination. If two register operands are specified, one
     must be ST. If a memory operand is specified, the product replaces
     the value in ST. Memory operands can be 32- or 64-bit real numbers
     or 16- or 32-bit integers.

     If no operand is specified, ST(1) is multiplied by ST and the
     stack is popped; the product is returned in ST. For FMULP, the
     source must be ST; the product is returned in the destination
     register and ST is popped.

----------------+------------------------+----------------------------------
 FMUL [reg,reg] | fmul   st,st(2)        |  87 130-145 (90-105)*
                | fmul   st(5),st        | 287 130-145 (90-105)*
                | fmul                   | 387 to=46-54 (49),fr=29-57 (52)œ
                |                        | 486 16
----------------+------------------------+----------------------------------
 FMULP reg,ST   | fmulp  st(6),st        |  87 134-148 (94-108)*
                |                        | 287 134-148 (94-108)*
                |                        | 387 29-57 (52)œ
                |                        | 486 16
----------------+------------------------+----------------------------------
 FMUL memreal   | fmul   DWORD PTR [bx]  |  87 (s=110-125,l=154-168)+EA
                | fmul   shortreal[di+3] | 287 s=110-125,l=154-168
                | fmul   longreal        | 387 s=27-35,l=32-57
                |                        | 486 s=11,l=14
----------------+------------------------+----------------------------------
 FIMUL memint   | fimul  int16           |  87 (w=124-138,d=130-144)+EA
                | fimul  warray[di]      | 287 w=124-138,d=130-144
                | fimul  double          | 387 w=76-87,d=61-82
                |                        | 486 w=23-27,d=22-24
----------------+------------------------+----------------------------------
  * The clocks in parentheses show times for short values--those with 40
    trailing zeros in their fraction because they were loaded from a
    short-real memory operand.

  œ The clocks in parentheses show typical speeds.

   If the register operand is a short value--having 40 trailing zeros
    in its fraction because it was loaded from a short-real memory
    operand--then the timing is (112-126)+EA on the 8087 or 112-126 on
    the 80287.

FNOP Instruction
----------------------------------------------------------------------------

  Title:    No Operation

  Syntax:   FNOP

  See also: NOP

  Description:

     Performs no operation. FNOP can be used for timing delays or
     alignment.

----------------+-------------------------+---------------------------------
 FNOP           | fnop                    |  87 10-16
                |                         | 287 10-16
                |                         | 387 12
                |                         | 486 3
----------------+-------------------------+---------------------------------

FPATAN/FPTAN Instructions
----------------------------------------------------------------------------

  Title:    Partial Arctangent and Tangent

  Syntax:   FPATAN
            FPTAN

  See also: FSIN

  Description:

     FPATAN finds the partial tangent by calculating Z = ARCTAN(Y / X)
     where X is taken from ST and Y from ST(1). On the 8087/287, Y and
     X must be in the range 0 <= Y < X < infinity. On the 80387/486,
     there is no restriction on X and Y. X is popped from the stack and
     Z replaces Y in ST.

     FPTAN finds the partial tangent by calculating Y / X = TAN(Z). Z
     is taken from ST. Z must be in the range 0 <= Z <= pi/4 on the
     8087/287. On the 80387/486, |Z| must be less than 2^63. The result
     is the ratio Y / X. Y replaces Z, and X is pushed into ST. Thus, Y
     is returned in ST(1) and X in ST.

----------------+-------------------------+---------------------------------
 FPATAN         | fpatan                  |  87 250-800
                |                         | 287 250-800
                |                         | 387 314-487
                |                         | 486 218-303
----------------+-------------------------+---------------------------------
 FPTAN          | fptan                   |  87 30-540
                |                         | 287 30-540
                |                         | 387 191-497*
                |                         | 486 200-273œ
----------------+-------------------------+---------------------------------
  * For operands with an absolute value greater than pi/4, up to 76
    additional clocks may be required.

  œ For operands with an absolute value greater than pi/4, add n clocks
    where n = operand / (pi/4).

FPREM/FPREM1 Instructions
----------------------------------------------------------------------------

  Title:    Partial Remainder

  Syntax:   FPREM
            FPREM1 (80387/487 only)

  Description:

     Calculates the remainder of ST divided by ST(1); the result is
     returned in ST. The remainder retains the same sign as the original
     dividend. The calculation uses the following formula:

          remainder = ST - ST(1) * quotient

     For FPREM, the quotient is the exact value obtained by chopping
     ST / ST(1) toward 0. For FPREM1, the quotient is the integer
     nearest to the exact value of ST / ST(1). When two integers are
     equally close to the given value, the even integer is used. FPREM1
     conforms to the IEEE standard.

     These instructions are normally used in a loop that repeats until
     the reduction is complete, as indicated by the condition codes of
     the status word.

     The condition codes for FPREM and FPREM1 are as follows:

     C3     C2     C1     C0     Meaning

     ?      1      ?      ?      Incomplete reduction
     0      0      0      0      Quotient MOD 8 = 0
     0      0      0      1      Quotient MOD 8 = 4
     0      0      1      0      Quotient MOD 8 = 1
     0      0      1      1      Quotient MOD 8 = 5
     1      0      0      0      Quotient MOD 8 = 2
     1      0      0      1      Quotient MOD 8 = 6
     1      0      1      0      Quotient MOD 8 = 3
     1      0      1      1      Quotient MOD 8 = 7

----------------+-------------------------+---------------------------------
 FPREM          | fprem                   |  87 15-190
                |                         | 287 15-190
                |                         | 387 74-155
                |                         | 486 70-138
----------------+-------------------------+---------------------------------
 FPREM1         | fprem1                  |  87 --
                |                         | 287 --
                |                         | 387 95-185
                |                         | 486 72-167
----------------+-------------------------+---------------------------------

FRNDINT Instruction
----------------------------------------------------------------------------

  Title:    Round to Integer

  Syntax:   FRNDINT

  See also: FABS

  Description:

     Rounds ST from a real number to an integer. The rounding control
     (RC) field of the control word specifies the rounding method.

----------------+-------------------------+---------------------------------
 FRNDINT        | frndint                 |  87 16-50
                |                         | 287 16-50
                |                         | 387 66-80
                |                         | 486 21-30
----------------+-------------------------+---------------------------------

FSAVE/FRSTOR Instructions
----------------------------------------------------------------------------

  Title:    Save or Restore Coprocessor State

  Syntax:   FSAVE dest
            FNSAVE dest
            FRSTOR src

  See also: FSTENV, FSTSW, FSTCW, Coprocessor Environment

  Description:

     FSAVE stores the 94-byte coprocessor state to a specified memory
     location; FRSTOR restores it from a specified memory location.

     In 32-bit mode on the 80387/486, the environment state takes 108
     bytes.

     FSAVE has wait and no-wait versions. The wait version (FSAVE)
     checks for unmasked numeric errors; the no-wait version (FNSAVE)
     does not. After saving the state, the coprocessor is initialized
     as if FINIT had been executed.

     When the .8087 directive is used, the assembler puts the WAIT
     instruction before the wait version and the NOP instruction before
     the no-wait version.

----------------+-------------------------+---------------------------------
 FSAVE mem      | fsave  [bp-94]          |  87 (197-207)+EA
 FSAVEW mem*    | fsave  cobuffer         | 287 œ
 FSAVED mem*    |                         | 387 375-376
 FNSAVE mem     |                         | 486 154,pm=143
 FNSAVEW mem*   |                         |
 FNSAVED mem*   |                         |
----------------+-------------------------+---------------------------------
 FRSTOR mem     | frstor [bp-94]          |  87 (197-207)+EA
 FRSTORW mem*   |                         | 287 œ
 FRSTORD mem*   |                         | 387 308
                |                         | 486 131,pm=120
----------------+-------------------------+---------------------------------
  * 80387/486 only.

  œ Clock counts are not meaningful in determining overall execution
    time of this instruction. Timing is determined by operand
    transfers.

  NOTE: The timings reflect the no-wait versions of FSAVE. The wait
        versions may take additional clock cycles.

FSCALE Instruction
----------------------------------------------------------------------------

  Title:    Scale

  Syntax:   FSCALE

  See also: FSQRT

  Description:

     Scales by powers of 2 by calculating the function Y = Y * 2^X. X
     is the scaling factor taken from ST(1), and Y is the value to be
     scaled from ST. The scaled result replaces the value in ST; the
     scaling factor remains in ST(1). If the scaling factor is not an
     integer, it will be truncated toward zero before the scaling.

     On the 8087/287 processors, if X is not in the range -2^15 <= X < 2^15
     or if X is in the range 0 < X < 1, the result will be undefined. The
     80387/486 processors have no restrictions on the range of operands.

----------------+-------------------------+---------------------------------
 FSCALE         | fscale                  |  87 32-38
                |                         | 287 32-38
                |                         | 387 67-86
                |                         | 486 30-32
----------------+-------------------------+---------------------------------

FSETPM Instruction
----------------------------------------------------------------------------

  Title:    Set Protected Mode
            80287 Only

  Syntax:   FSETPM

  See also: .287

  Description:

     Sets the 80287 to protected mode. The instruction and operand
     pointers are in the protected-mode format after this instruction.
     On the 80387/486, FSETPM is recognized but interpreted as the FNOP
     instruction, since the 80386/486 processors handle addressing
     identically in real and protected modes.

----------------+-------------------------+---------------------------------
 FSETPM         | fsetpm                  |  87 --
                |                         | 287 2-8
                |                         | 387 12
                |                         | 486 3
----------------+-------------------------+---------------------------------

FSQRT Instruction
----------------------------------------------------------------------------

  Title:    Square Root

  Syntax:   FSQRT

  See also: FSCALE

  Description:

     Replaces the value of ST with its square root. The square root
     of -0 is -0.

----------------+-------------------------+---------------------------------
 FSQRT          | fsqrt                   |  87 180-186
                |                         | 287 180-186
                |                         | 387 122-129
                |                         | 486 83-87
----------------+-------------------------+---------------------------------

FST Instruction
----------------------------------------------------------------------------

  Title:    Store

  Syntax:   FST dest
            FSTP dest
            FIST dest
            FISTP dest
            FBSTP dest

  Description:

     Stores the value in ST to the specified memory location or
     register. Temporary-real values in registers are converted to the
     appropriate integer, BCD, or floating-point format as they are
     stored. With FSTP, FISTP, and FBSTP, the ST register value is
     popped off the stack.

     Memory operands can be 32-, 64-, or 80-bit real numbers for FSTP,
     or 16-, 32-, or 64-bit integers for FISTP.

---------------+-----------------------+------------------------------------
 FST reg       | fst    st(6)          |  87 15-22
               | fst    st             | 287 15-22
               |                       | 387 11
               |                       | 486 3
---------------+-----------------------+------------------------------------
 FSTP reg      | fstp   st             |  87 17-24
               | fstp   st(3)          | 287 17-24
               |                       | 387 12
               |                       | 486 3
---------------+-----------------------+------------------------------------
 FST memreal   | fst    shortreal      |  87 (s=84-90,l=96-104)+EA
               | fst    longs[bx]      | 287 s=84-90,l=96-104
               |                       | 387 s=44,l=45
               |                       | 486 s=7,l=8
---------------+-----------------------+------------------------------------
 FSTP memreal  | fstp   longreal       |  87 (s=86-92,l=98-106,t=52-58)+EA
               | fstp   tempreals[bx]  | 287 s=86-92,l=98-106,t=52-58
               |                       | 387 s=44,l=45,t=53
               |                       | 486 s=7,l=8,t=6
---------------+-----------------------+------------------------------------
 FIST memint   | fist   int16          |  87 (w=80-90,d=82-92)+EA
               | fist   doubles[8]     | 287 w=80-90,d=82-92
               |                       | 387 w=82-95,d=79-93
               |                       | 486 w=29-34,d=28-34
---------------+-----------------------+------------------------------------
 FISTP memint  | fistp  longint        |  87 (w=82-92,d=84-94,q=94-105)+EA
               | fistp  doubles[bx]    | 287 w=82-92,d=84-94,q=94-105
               |                       | 387 w=82-95,d=79-93,q=80-97
               |                       | 486 29-34
---------------+-----------------------+------------------------------------
 FBSTP membcd  | fbstp  bcds[bx]       |  87 (520-540)+EA
               |                       | 287 520-540
               |                       | 387 512-534
               |                       | 486 172-176
---------------+-----------------------+------------------------------------

FSTSW Instruction
----------------------------------------------------------------------------

  Title:    Store Status Word

  Syntax:   FSTSW dest
            FNSTSW dest

  Description:

     Stores the status word to a specified 16-bit memory operand. On
     the 80287, 80387, and 80486, the status word can also be stored to
     the processor's AX register.

     FSTSW has wait and no-wait versions. The wait version (FSTSW)
     checks for unmasked numeric errors; the no-wait version (FNSTSW)
     does not. When the .8087 directive is used, the assembler puts the
     WAIT instruction before the wait version and the NOP instruction
     before the no-wait version.

----------------+-------------------------+---------------------------------
 FSTSW mem16    | fstsw  statword         |  87 12-18
 FNSTSW mem16   |                         | 287 12-18
                |                         | 387 15
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FSTSW AX       | fstsw  ax               |  87 --
 FNSTSW AX      |                         | 287 10-16
                |                         | 387 13
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.

FSUB Instruction

----------------------------------------------------------------------------

  Title:    Subtract

  Syntax:   FSUB [dest,src]
            FSUBP dest,ST
            FSUB src
            FISUB src

  See also: FSUBR, FADD

  Description:

     Subtracts the source from the destination and returns the
     difference in the destination. If two register operands are
     specified, one must be ST. If a memory operand is specified, the
     result replaces the value in ST. Memory operands can be 32- or
     64-bit real numbers or 16- or 32-bit integers.

     If no operand is specified, ST is subtracted from ST(1) and the
     stack is popped; the difference is returned in ST. For FSUBP, the
     source must be ST; the difference (destination minus source) is
     returned in the destination register and ST is popped.

----------------+-------------------------+---------------------------------
 FSUB [reg,reg] | fsub   st,st(2)         |  87 70-100
                | fsub   st(5),st         | 287 70-100
                | fsub                    | 387 to=29-37,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBP reg,ST   | fsubp  st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUB memreal   | fsub   longreal         |  87 (s=90-120,s=95-125)+EA
                | fsub   shortreals[di]   | 287 s=90-120,l=95-125
                |                         | 387 s=24-32,l=28-36
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FISUB memint   | fisub  double           |  87 (w=102-137,d=108-143)+EA
                | fisub  warray[di]       | 287 w=102-137,d=108-143
                |                         | 387 w=71-83,d=57-82
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------

FSUBR Instruction
----------------------------------------------------------------------------

  Title:    Subtract Reversed

  Syntax:   FSUBR [dest,src]
            FSUBRP dest,ST
            FSUBR src
            FISUBR src

  See also: FSUB, FADD

  Description:

     Subtracts the destination operand from the source operand and
     returns the result in the destination operand. If two register
     operands are specified, one must be ST. If a memory operand is
     specified, the result replaces the value in ST. Memory operands
     can be 32- or 64-bit real numbers or 16- or 32-bit integers.

     If no operand is specified, ST(1) is subtracted from ST and the
     stack is popped; the difference is returned in ST. For FSUBRP, the
     source must be ST; the difference (source minus destination) is
     returned in the destination register and ST is popped.

----------------+-------------------------+---------------------------------
 FSUBR [reg,reg]| fsubr  st,st(2)         |  87 70-100
                | fsubr  st(5),st         | 287 70-100
                | fsubr                   | 387 to=29-37,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBRP reg,ST  | fsubrp st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBR memreal  | fsubr  QWORD PTR [bx]   |  87 (s=90-120,s=95-125)+EA
                | fsubr  shortreal[di]    | 287 s=90-120,l=95-125
                | fsubr  longreal         | 387 s=25-33,l=29-37
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FISUBR memint  | fisubr int16            |  87 (w=103-139,d=109-144)+EA
                | fisubr warray[di]       | 287 w=103-139,d=109-144
                | fisubr double           | 387 w=72-84,d=58-83
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------

FTST Instruction
----------------------------------------------------------------------------

  Title:    Test for Zero

  Syntax:   FTST

  See also: FCOM, Status Word

  Description:

     Compares ST with +0.0 and sets the condition of the status word
     according to the result.

     The condition codes for FTST are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST is positive
     0      0      ?      1      ST is negative
     1      0      ?      0      ST is 0
     1      1      ?      1      ST is not comparable (NAN or
                                 projective infinity)
----------------+-------------------------+---------------------------------
 FTST           | ftst                    |  87 38-48
                |                         | 287 38-48
                |                         | 387 28
                |                         | 486 4
----------------+-------------------------+---------------------------------

FUCOM Instruction
----------------------------------------------------------------------------

  Title:    Unordered Compare
            80387/486 Only

  Syntax:   FUCOM [src]
            FUCOMP [src]
            FUCOMPP

  See also: FCOM, FTST, Status Word

  Description:

     Compares the specified source to ST and sets the condition codes
     of the status word according to the result. The instruction
     subtracts the source operand from ST without changing either
     operand. Memory operands are not allowed.

     If no operand is specified or if two pops are specified, ST is
     compared to ST(1). If one pop is specified with an operand, the
     given register is compared to ST.

     Unlike FCOM, FUCOM does not cause an invalid-operation exception
     if one of the operands is a NAN. Instead, the condition codes are
     set to unordered.

     The condition codes for FUCOM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST > source
     0      0      ?      1      ST < source
     1      0      ?      0      ST = source
     1      1      ?      1      Unordered

----------------+-------------------------+---------------------------------
 FUCOM [reg]    | fucom  st(2)            |  87 --
                | fucom                   | 287 --
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FUCOMP [reg]   | fucomp st(7)            |  87 --
                | fucomp                  | 287 --
                |                         | 387 26
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FUCOMPP        | fucompp                 |  87 --
                |                         | 287 --
                |                         | 387 26
                |                         | 486 5
----------------+-------------------------+---------------------------------

FWAIT Instruction
----------------------------------------------------------------------------

  Title:    Wait

  Syntax:   FWAIT

  See also: WAIT

  Description:

     Suspends execution of the processor until the coprocessor is
     finished executing. This is an alternate mnemonic for the
     processor WAIT instruction.

----------------+-------------------------+---------------------------------
 FWAIT          | fwait                   |  87 4
                |                         | 287 3
                |                         | 387 6
                |                         | 486 1-3
----------------+-------------------------+---------------------------------

FXAM Instruction
----------------------------------------------------------------------------

  Title:    Examine

  Syntax:   FXAM

  See also: Status Word

  Description:

     Reports the contents of ST in the condition flags of the status
     word.

     The condition codes for FXAM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      0      0      + Unnormal*
     0      0      0      1      + NAN
     0      0      1      0      - Unnormal*
     0      0      1      1      - NAN
     0      1      0      0      + Normal
     0      1      0      1      + Infinity
     0      1      1      0      - Normal
     0      1      1      1      - Infinity
     1      0      0      0      + 0
     1      0      0      1      Empty
     1      0      1      0      - 0
     1      0      1      1      Empty
     1      1      0      0      + Denormal
     1      1      0      1      Empty*
     1      1      1      0      - Denormal
     1      1      1      1      Empty*

     * Not used on the 80387/486 processors. Unnormals are not supported by
       the 80387/486. Also, the 80387/486 processors use two codes instead
       of four to identify empty registers.

----------------+-------------------------+---------------------------------
 FXAM           | fxam                    |  87 12-23
                |                         | 287 12-23
                |                         | 387 30-38
                |                         | 486 8
----------------+-------------------------+---------------------------------

FXCH Instruction
----------------------------------------------------------------------------

  Title:    Exchange Registers

  Syntax:   FXCH [dest]

  See also: FLD

  Description:

     Exchanges the specified (destination) register and ST. If no
     operand is specified, ST and ST(1) are exchanged.

----------------+-------------------------+---------------------------------
 FXCH [reg]     | fxch   st(3)            |  87 10-15
                | fxch                    | 287 10-15
                |                         | 387 18
                |                         | 486 4
----------------+-------------------------+---------------------------------

FXTRACT Instruction
----------------------------------------------------------------------------

  Title:    Extract Exponent and Significand

  Syntax:   FXTRACT

  Description:

     Extracts the exponent and significand (mantissa) fields of ST. The
     exponent replaces the value in ST, and then the significand is
     pushed onto the stack.

----------------+-------------------------+---------------------------------
 FXTRACT        | fxtract                 |  87 27-55
                |                         | 287 27-55
                |                         | 387 70-76
                |                         | 486 16-20
----------------+-------------------------+---------------------------------

FYL2X Instruction
----------------------------------------------------------------------------

  Title:    Y log(sub2)(X)

  Syntax:   FYL2X

  See also: FYL2XP1, F2XM1

  Description:

     Calculates Z = Y log(sub2)(X) where X is taken from ST and Y from
     ST(1). The stack is popped and the result, Z, replaces Y in ST. X
     must be in the range 0 < X < infinity and Y in the range
     minus infinity < Y < infinity.

----------------+-------------------------+---------------------------------
 FYL2X          | fyl2x                   |  87 900-1100
                |                         | 287 900-1100
                |                         | 387 120-538
                |                         | 486 196-329
----------------+-------------------------+---------------------------------

FYL2XP1 Instruction
----------------------------------------------------------------------------

  Title:    Y log(sub2)(X+1)

  Syntax:   FYL2XP1

  See also: FYL2X, F2XM1

  Description:

     Calculates Z = Y log(sub2)(X + 1) where X is taken from ST and Y
     from ST(1). The stack is popped once and the result, Z, replaces Y
     in ST. X must be in the range 0 <= |X| < (1 - (SQRT2 / 2)) and Y in
     the range minus infinity < Y < infinity.

----------------+-------------------------+---------------------------------
 FYL2XP1        | fyl2xp                  |  87 700-1000
                |                         | 287 700-1000
                |                         | 387 257-547
                |                         | 486 171-326
----------------+-------------------------+---------------------------------


Intel 8087 — Wikipédia

Intel 8087
Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher
Intel C8087
Architecture du 8087

Les Intel 8087 furent les premiers coprocesseurs mathématiques conçus par Intel en 1980 pour les machines 16 bits (le 8231 est plus ancien, mais conçu pour le processeur 8 bit Intel 8080).

Il était conçu pour être utilisé avec les microprocesseurs Intel 8088 et 8086. Le but du 8087, le premier de la famille x87, était d'accélérer des calculs pour des applications demandant un traitement avec des nombres à virgule flottante. Les performances d'exécution augmentèrent de 20 % à 500 % selon les applications. Le 8087 pouvait atteindre la performance de 50 kFLOP/s, dépendant de l'opération demandée.

Ce coprocesseur introduit environ 60 nouvelles instructions disponibles pour le programmeur, dont les mnémoniques commencent tous par la lettre F pour les différencier des instructions arithmétiques sur les entiers disponibles nativement sur les 8086/88. Par exemple, pour les additions et multiplications sont réalisées par les instructions 8086/88 ADD et MUL, qui portent sur des entiers. Le 8087 ajoute les instructions FADD et FMUL, portant sur des nombres en virgule flottante. Dans le code machine binaire, toutes ces instructions commençaient par la suite de bits 11011, ce qui fait 27 en décimal, le code ASCII de la touche ESCAPE : on parle parfois d'escape opcodes.

Les 8087 (et, en fait, toute la famille des x87) ne fournissent pas un ensemble linéaire de registres tels que les registres AX/BX/CX/DX des processeurs 8086/88 et 80286. Les registres de travail des x87 sont structurés sous une certaine forme de pile (cependant elle n'est pas exactement comme une structure de données typique de pile) s'étendant de ST0 à ST7. Les instructions en virgule flottante des coprocesseurs x87 opèrent des push et des pop de valeurs sur cette pile.

Quand Intel conçut le 8087, il souhaita standardiser en partie ses spécifications. Intel intégra donc le groupe de travail de la future norme IEEE 754 portant sur la représentation des nombres en virgule flottante. Le 8087 est donc historiquement très important car ses spécifications servirent de base à ce qui devint la norme IEEE 754 en 1985. Cependant le 8087 est sorti en 1980, à une époque où le standard IEEE 754 n'en était qu'au stade de brouillon. Il n'implémente donc pas le standard IEEE 754 dans tous ses détails. La première unité de calcul en virgule flottante IEEE 754 est l'Intel 80387.

Les 8087 fournissaient deux représentations pour les nombres virgule flottante, sur 32 et 64 bits, ainsi qu'un support interne étendu aux données 80 bits pour améliorer la précision sur de gros calculs grands et complexes. Indépendamment de ceci, les 8087 offraient un format 80 bits à 17 chiffres en décimal codé en binaire et un format de données 16, 32 et 64 bits dans les entiers.

Le 8087 différait des coprocesseurs Intel suivants par le fait qu'il était directement connecté aux bus d'adresses et de données. Le 8088/86 cherchait une instruction qui commençait avec la séquence '11011' et laissait le contrôle au coprocesseur. Le coprocesseur rendait la main une fois que la séquence d'instruction du coprocesseur prenait fin. Les coprocesseurs suivant d'Intel ne se connectaient pas de la même façon aux bus, mais leurs instructions étaient délivrée par le processeur principal.

Les 8087, annoncés en 1980, ont été remplacés par les 80287, 80387 et le 80487. Ce dernier modèle venait compléter le processeur 80486SX. Les autres versions du 80486 ainsi que les générations suivantes de processeurs Intel x86, à commencer par le Pentium, incluent une unité de calcul en virgule flottante sur la même puce que le processeur.

[masquer]
v · m
Processeurs Intel
Abandonnés
4 bits
  • 4004
  • 4040
Pré-x86 (8-bits)
  • 8008
  • 8080
  • 8085
x86-16 (16-bits)
  • 8086
  • 8088
  • 80186
  • 80188
  • 80286
x87 (FPU externes)
  • Bus 8/16 bits : 8087
  • Bus 16 bits : 80287
  • Bus 32 bits : 80387, 80487
x86-32/IA-32 (32-bit)
  • 80386
    • SX
    • 376
    • EX
  • 80486
    • SX
    • DX2
    • DX4
    • SL
    • RapidCAD
    • OverDrive
  • Pentium
    • Original
    • OverDrive
    • Pro
    • II
    • II OverDrive
    • III
    • 4
    • M
  • Core
  • Celeron M
  • Celeron D
  • A100/A110
x86-64/EM64T (64-bit)
  • Pentium 4
  • Pentium D
  • Pentium Extreme Edition
  • Celeron D
  • Core 2
  • Pentium Dual-Core
RISC
  • i860
  • i960
  • StrongARM
  • IOP and XScale
Autres
  • Pentium 4-M
  • Centrino
Actuels
x86-32
  • EP80579
  • Atom
x86-64
  • Atom
  • Celeron
  • Pentium
  • Core i3
  • Core i5
  • Core i7
  • Xeon
  • Core M
Autres Itanium
Microarchitectures
  • P5
  • P6
  • NetBurst
  • Core
  • Bonnell
  • Nehalem
  • Sandy Bridge
  • Haswell
  • Silvermont
  • Skylake
  • Sockets
  • Chipsets
  • PCH
  • ICH
  • Listes
    • Microprocesseurs
    • Xeon
    • Celeron


D:\COM_CHM\Documentation CHM\_ASM\ASM_FPU\Jourlin\virgule flottante Jourlin.rtf

Calculs en virgule flottante Vu dans http://www.jourlin.com

11.1 Introduction

Nous avons vu que le microprocesseur n'est pas capable de travailler directement dans l'ensemble des nombres entiers naturels ou relatifs. Le processeur peut en revanche manipuler des sous-ensembles finis : entiers signés ou non signés représentés le plus souvent sur 8x2n bits. Par conséquent, nos sous-ensembles sont définis très simplement par leurs bornes inférieures et supérieures.
Malheureusement, lorsque nous cherchons à représenter les nombres réels, les choses se compliquent : nous avons une infinité de valeurs entre une borne inférieure et une borne supérieure. Ceci pose évidemment un problème lorsqu'il s'agit de représenter ces nombres avec des valeurs binaires de taille fixe. Le choix de notre représentation va donc déterminer en plus des limites inférieures et supérieures, la précision maximale de nos calculs.

De même qu'un simple débordement lors d'une addition de nombres entiers
peut produire un résultat final incorrect, une très légère erreur de précision à

un moment donné dans un calcul sur des nombres à virgule flottante peut mener à des résultats ultimes totalement erronés.

11.2. La norme IEEE 754
 Dans cette norme, les nombres sont représentés en plusieurs parties : le signe,

exposant et la mantisse. C'est la représentation que nous adoptons

naturellement en base décimale quand nous écrivons : -1234.10-9. Or, -1234.10-9 peut s'écrire aussi -12340.10-10 ou -123,4.10-8 . Dans la première écriture, la mantisse a une taille de 4 chiffres et l'exposant s'écrit avec 1 chiffre, dans le 2e cas la mantisse a une taille de 5 chiffres et l'exposant s'écrit avec 2 chiffres, etc.
Pour simplifier les choses et gagner de la précision à taille mémoire constante, nos nombres seront normalisés à chaque fois que cela sera possible : notre

mantisse aura toujours en base binaire la forme de « 1 » « virgule » « fraction
binaire ». Notre exposant sera négatif pour les valeurs inférieures à 0 et positif
ou nul pour les valeurs supérieures ou égales à 1.
Cette normalisation n'est bien entendu plus possible lorsque nous atteignons les bornes supérieures ou inférieures de l'exposant. Lorsque nous atteignons la limite inférieure de l'exposant, c'est-à-dire lorsque notre valeur (positive ou négative) est très proche de 0, nous dirons que le nombre est dénormalisé. L'exposant prendra la valeur 0 et la mantisse aura la forme « 0 » « virgule »

fraction binaire ». Lorsque nous dépassons la limite supérieure de l'exposant, notre nombre prendra une valeur qui symbolise +infini ou -infini.

Après une opération invalide dans les réels (racine d'un nombre négatif, division par 0), le résultat aura également une valeur remarquable. On peut voir ci-dessous la représentation 32 bits des nombres à virgule flottante. L'exposant (entier signé) n'est pas représenté en complément à 2 mais sous la

forme biaisée : sur 8 bits, la valeur biaisée 127 correspond au zéro (non

biaisé). Les valeurs biaisées qui vont de 1 à 126 correspondent aux valeurs non biaisées de -126 à -1 et les valeurs biaisées qui vont de 128 à 254 correspondent aux valeurs non biaisées de 1 à 126.

nombre Signe (1bit) Exposant (8bits)Mantisse (23 bits)

plus » zéro 00 0
moins » zéro 10 0

dénormalisé positif 0 0 0b0,xxxx...xxxx
dénormalisé négatif 1 0 0b0,xxxx...xxxx
normalisé positif 0 1..0d254 0b1,xxxx...xxxx
normalisé négatif 1 1..0d254 0b1,xxxx...xxxx
+infini 0 0d255 0
-infini 1 0d255 0
±NaN (not a 0 ou 1 0d255 x ≠ 0
number)
11.3 Les registres du processeur à virgule flottante (x87)

Du point de vue du programmeur, le processeur à virgule flottante (FPU : floating point unit) est une unité distincte du processeur central (CPU: central processing unit). Il existe bien entendu des « ponts » entre les deux, mais chacun a ses propres registres et son propre jeu d'instructions. Ceci est encore un héritage du du 8086 qui était limité aux instructions sur les entiers signés et non signés, auquel on pouvait adjoindre un second processeur, le 8087 que l'on appelait coprocesseur arithmétique. Lorsque l'on a intégré les instructions et registres à virgule flottante, il a été décidé de préserver la compatibilité avec les logiciels écrits pour des machines à 2 processeurs 8086 et 8087.
Le FPU possède 8 registres de 80 bits chacun (1 bit pour le signe, 15 bits pour l'exposant, 64 bits pour la mantisse). Ces 8 registres ne sont pas adressables directement comme dans le cas de l'unité d'arithmétique entière. Ils constituent

une pile de registres, que l'on nommera %st(0), %st(1), ..., %st(7) . %st(0)
désigne le sommet de la pile (c'est-à-dire le dernier élément empilé) et %st(i)
désigne le i-ème élément de la pile de registres.
11.4 Principales instructions de calcul

fld, fst

L'instruction fld (float load) permet d'empiler un nombre à virgule flottante
dans la pile des registres. La lettre qui suit indique la taille de l'opérande : flds
pour empiler un nombre flottant de 32 bits, fldl pour empiler un nombre
flottant de 64 bits et fldt pour empiler un flottant de 80 bits. Lorsqu'une valeur
est empilée, elle est automatiquement convertie en nombre flottant de 80 bits.
L'opérande qui suit la mnémonique fld, c'est-à-dire la valeur à empiler, peut
être soit un registre st(i) soit un emplacement en mémoire. fild et fist (avec

i » pourinteger ) sont utilisés pour les conversions virgule flottante/entiers.

L'instruction fst (float store) copie la valeur contenue dans le sommet de la
pile, st(0) à l'emplacement donné en opérande. fstp réalise la même opération
en dépilant le sommet de la pile.

fadd, fsub, fmul, fdiv

Ces instructions réalisent respectivement l'addition, la soustraction, la multiplication et la division de deux opérandes dont une au moins est un

emplacement de la pile de registres, l'autre opérande étant soit un registre de la pile, soit un emplacement en mémoire. Lorsqu'un seul opérande est spécifié,

l'opération est réalisée entre l'opérande et le sommet de la pile st(0). Le suffixe

p » peut être ajouté pour permettre un postdépilement des registres. Le suffixe « r » peut être ajouté aux mnémoniques fsub et fdiv afin d'inverser l'ordre des opérandes. Le résultat d'une opération est toujours empilé et se retrouve donc toujours dans %st(0).

Ici aussi, les suffixes « s », « l » ou « t » peuvent être ajoutés pour spécifier la taille des nombres flottants.

Par exemple : « fdivrps diviseur » va prendre 32 bits à l'adresse

symbolique « diviseur », convertir ce nombre flottant sur 80 bits, diviser cette valeur par le sommet de la pile, remplacer le sommet de la pile par le résultat de la division et enfin dépiler la pile de registres.

fiadd, fisub, fimul, fidiv

Ces instructions sont à utiliser lorsque l'emplacement mémoire contient un entier. Il sera converti en nombre flottant de 80 bits avant l'opération.

11.5 Comparaisons et branchements conditionnels

Le FPU possède son propre registre d'état et donc ses propres drapeaux. Lors d'une comparaison de 2 nombres à virgule flottante, les drapeaux C0, C1, C2 et C3 indiquent le résultat de la comparaison. Pour pouvoir utiliser les instructions classiques de branchement conditionnel, il faudra transférer les valeurs des drapeaux C0,...,C3 dans les drapeaux ZF, CF et PF du registre

RFLAGS. Cependant pour les microprocesseurs récents, l'instruction fcomi

effectue la comparaison et affecte directement les registres ZF, PF et CF, ce qui permet d'utiliser immédiatement après les instructions de branchement

conditionnel classiques (jc, jnc, jo, jz, etc.). Après l'instruction « fcomi %st(i) »
les trois registres de EFLAGS sont modifiés de la façon suivante :

Condition ZF PF CF
st(0) > st(i) 0 0 0
st(0) < st(i) 0 0 1
st(0) = st(i) 1 0 0
non comparable 1 1 1

PF=1 par exemple lorsqu'un des deux registres a NaN16 pour valeur. Si la
comparaison s'est bien déroulée, alors PF =0 et les branchements conditionnels
je, ja, jb, jae, jbe, jna, jnb, jnae et jnbe prennent la même signification que
lorsqu'il suivent une comparaison de valeurs entières (voir chapitre 5).

Parallélisme (MMX, SSE, 3DNow!)

Les instructions de type MMX, SSE et 3DNow sont ce que l'on apelle des
SIMD, pour Single Instruction Multiple Data. Une instruction va lancer une

série d'opérations sur une série de données en parallèle. Nous allons voir les principaux concepts de programmation avec la technologie MMX. Les technologies SSE et 3DNow! étant de simples ajouts ou améliorations de MMX, nous n'allons pas les étudier ici.

12.1 Registres MMX

Les registres MMX se superposent aux registres du FPU. Le programmeur ne pourra donc pas utiliser les instructions du FPU et celle de MMX

simultanément. Il y a 8 registres MMX, nommés mm0..., mm7 . Ils ont chacun

une taille de 64 bits. Dans chacun de ces registres, nous pouvons stocker, au choix :

1 nombre entier de 64 bits

16Not a Number (résultat d'une opération invalide)

2 nombres entiers de 32 bits
4 nombres entiers de 16 bits
8 nombres entiers sur 8 bits

Par exemple, si l'on veut stocker 8 entiers dans le registre mm0, ses bits

numérotés de 0 à 7 contiendront le 1er nombre, ses bits numérotés de 8 à 15 contiendront le 2e nombre, etc.

12.2 Instructions MMX

Avec le CPU ou le FPU, les différents problèmes qui pouvaient se produire lors d'un calcul (débordement, division par 0, etc.) étaient indiqués par des drapeaux ou par des exceptions. Dans le cas des instructions MMX, soit la retenue est ignorée (on obtient le modulo du résultat), soit les valeurs sont

saturées : lorsqu'à la suite d'une opération, le résultat est supérieur à la borne

maximale, il sera rendu égal à la borne maximale. Si le résultat est inférieur à la borne minimale, il sera rendu égal à la borne minimale. Cette manière de traiter les débordements est particulièrement bien adaptée aux algorithmes traitant de l'image, du son ou de la vidéo.
Les mnémoniques utilisées sont très proches de celles utilisées pour les opérations d'arithmétique entière classique. Pour les copies de données, on

utilisera les instructions movd (32 bits) et movq (64 bits). Pour additionner de

octets à 8 autres octets en parallèle sans saturation s'écrira paddb <source>,

<destination>. La destination doit être un registre MMX, la source est soit un
registre MMX, soit un emplacement. Pour additionner en parallèle 4 nombres
de 16 bits à 4 autres nombres de 16 bits, on écrira paddw.
Enfin, une addition de 2x2 nombres de 32 bits en parallèle s'écrira paddd.
L'addition avec saturation d'entiers signés s'écrira padds (avec « b » ou « w »
en suffixe) et l' addition avec saturation d'entiers non signés s'écrira paddus
(avec « b » ou « w » en suffixe) . Les mnémoniques correspondantes pour la
soustraction sont psubb, psubw, psubd, psubsb, psubsw, psubusb et psubusw.

pmull réalise la multiplication en parallèle des 4 entiers signés de 16 bits de la source aux 4 entiers signés de 16 bits de la destination et stocke la partie basse (16 bits) du résultat dans la destination. pmulh réalise le même calcul, mais stocke la partie haute du résultat. pmullw et pmulhw réalisent les mêmes opérations avec 2x2 entiers signés de 32 bits.

Le jeu d'instructions MMX contient également des opérations de comparaison, de conversion, d'opération logiques et de décalage, mais nous avons vu les

concepts principaux. Supposons qu'à l'adresse photo, nous ayons une image de

256x256 pixels en 256 niveaux de gris (0-> noir, 255 -> blanc). Voici un petit programme parallèle, 8 fois plus rapide que son équivalent séquentiel, qui augmente d'un cran la luminosité de l'image :

movq $photo, %rsi
movq $0x0101010101010101, %mm0
# 8 octets de valeur 1 > mm0
movw $0, %cx # pixel courant

boucle:

movq (%rsi), %mm1 # charge 8 octets > mm1
paddusb %mm0, %mm1 # ajoute 1 à 8 pixels en //
movq %mm1, (%rsi) # transfère 8 pixels > photo
addl $8, %rsi # avance de 8 pixels
addw $8, %cx # MAJ du compteur de pixels
jnc boucle # boucle dans que cx <=65535

 Bibliographie

AMD64 Architecture Programmer's Manual. Volume 1 : application programming,

http://www.amd.com/us-
en/assets/content_type/white_papers_and_tech_docs/24592.pdf

Intel ® Extended Memory 64 Technology - Porting IA-32 Applications

to the Intel® Xeon® Processor

http://www.intel.com/cd/ids/developer/asmona/eng/171850.htm? page=1

Dean Elsner, Jay Fenlason & friends, Using as : The GNU Assembler, January 1994.

http://www.gnu.org/software/binutils/manual/gas2.9.1/


VII. Virgule flottante

VII-A. Représentation en virgule flottante

VII-A-1. Nombres binaires non entiers

Lorsque nous avons parlé des systèmes numériques dans le premier chapitre, nous n'avons abordé que les nombres entiers. Évidemment, il est possible de représenter des nombres non entiers dans des bases autres que le décimal.

En décimal, les chiffres à droite du symbole décimal sont associés à des puissances

négatives de 10 :

0, 123 = 1 × 10-1 + 2 × 10-2 + 3 × 10-3

Ce n'est pas étonnant, les nombres binaires fonctionnent de la même façon :

0, 1012 = 1 × 2-1 + 0 × 2-2 + 1 × 2-3 = 0, 625

Cette idée peut être associée avec les méthodes appliquées aux entiers dans le Chapitre II pour convertir un nombre quelconque :

110, 0112 = 4 + 2 + 0, 25 + 0, 125 = 6, 375

Convertir du binaire vers le décimal n'est pas très difficile non plus. Divisez le nombre en deux parties : entière et décimale. Convertissez la partie entière en binaire en utilisant les méthodes du Chapitre II, la partie décimale est convertie en utilisant la méthode décrite ci-dessous.

Considérons une partie décimale binaire dont les bits sont notés a, b, c… Le nombre en binaire ressemble alors à :

0, abcdef…

Multipliez le nombre par deux. La représentation du nouveau nombre sera :

a, bcdef…

Notez que le premier bit est maintenant en tête. Remplacez le a par 0 pour obtenir :

0, bcdef…

et multipliez à nouveau par deux, vous obtenez :

b, cdef…

Maintenant le deuxième bit (b) est à la première place. Cette procédure peut être répétée jusqu'à ce qu'autant de bits que nécessaire soient trouvés.

La Figure 6.1 montre un exemple réel qui converti 0,5625 en binaire.

Image non disponible

La méthode s'arrête lorsque la partie décimale arrive à zéro.

Prenons un autre exemple, considérons la conversion de 23,85 en binaire, il est facile de convertir la partie entière (23 = 101112), mais qu'en est-il de la partie décimale (0, 85) ?

La Figure 6.2 montre le début de ce calcul.

Image non disponible Image non disponible

Une conséquence importante du calcul ci-dessus est que 23,85 ne peut pas être représenté exactement en binaire en utilisant un nombre fini de bits (tout comme 1/3 ne peut pas être représenté en décimal avec un nombre fini de chiffres). Comme le montre ce chapitre, les variables float et double en C sont stockées en binaire. Donc, les valeurs comme 23,85 ne peuvent pas être stockées exactement dans ce genre de variables. Seule une approximation de 23,85 peut être stockée.

Pour simplifier le matériel, les nombres en virgule flottante sont stockés dans un format standard. Ce format utilise la notation scientifique (mais en binaire, en utilisant des puissances de 2, pas de 10). Par exemple, 23,85 ou 10111, 11011001100110…2 serait stocké sous la forme :

1, 011111011001100110… × 2100

(où l'exposant (100) est en binaire). Un nombre en virgule flottante normalisé a la forme :

1, ssssssssssssssss × 2eeeeeee

où 1, sssssssssssss est la mantisse et eeeeeeee est l'exposant.

VII-A-2. Représentation en virgule flottante IEEE

L'IEEE (Institute of Electrical and Electronic Engineers) est une organisation internationale qui a conçu des formats binaires spécifiques pour stocker les nombres en virgule flottante. Ce format est utilisé sur la plupart des ordinateurs (mais pas tous !) fabriqués de nos jours. Souvent, il est supporté par le matériel de l'ordinateur lui-même. Par exemple, les coprocesseurs numériques (ou arithmétiques) d'Intel (qui sont intégrés à tous leurs processeurs depuis le Pentium) l'utilisent. L'IEEE définit deux formats différents avec des précisions différentes : simple et double précision. La simple précision est utilisée pour les variables float en C et la double précision pour les variables double.

Le coprocesseur arithmétique d'Intel utilise également une troisième précision plus élevée appelée précision étendue. En fait toutes les données dans le coprocesseur sont dans ce format. Lorsqu'elles sont stockées en mémoire depuis le coprocesseur, elles sont converties soit en simple, soit en double précision automatiquement (28). La précision étendue utilise un format général légèrement différent des formats float et double de l'IEEE, nous n'en parlerons donc pas ici.

VII-A-2-a. Simple précision IEEE

La virgule flottante simple précision utilise 32 bits pour encoder le nombre.

Elle est généralement précise jusqu'à 7 chiffres significatifs. Les nombres en virgule flottante sont généralement stockés dans un format beaucoup plus compliqué que celui des entiers.

La Figure 6.3 montre le format de base d'un nombre en simple précision IEEE.

Image non disponible

Ce format a plusieurs inconvénients. Les nombres en virgule flottante n'utilisent pas la représentation en complément à 2 pour les nombres négatifs. Ils utilisent une représentation en grandeur signée. Le bit 31 détermine le signe du nombre.

L'exposant binaire n'est pas stocké directement. À la place, la somme de l'exposant et de ProductID="7F" w:st="on">7F est stockée dans les bits 23 à 30, cet exposant décalé est toujours positif ou nul.

La partie décimale suppose une mantisse normalisée (de la forme 1, sssssssss).

Comme le premier bit est toujours un 1, le 1 de gauche n'est pas stocké ! Cela permet le stockage d'un bit additionnel à la fin et augmente donc légèrement la précision. Cette idée est appelée représentation en 1 masqué.

Il faut toujours garder à l'esprit que les octets 41 BE CC CD peuvent être interprétés de façons différentes selon ce qu'en fait le programme ! Vus comme un nombre en virgule flottante en simple précision, ils représentent 23,850000381, mais vus comme un entier double-mot, ils représentent 1,103,023,309 ! Le processeur ne sait pas quelle est la bonne interprétation !

Comment serait stocké 23,85 ? Tout d'abord, il est positif, donc le bit de signe est à 0. Ensuite, l'exposant réel est 4, donc l'exposant décalé est

ProductID="7F" w:st="on">7F + 4 = 8316.

Enfin, la fraction vaut 01111101100110011001100 (souvenez-vous que le 1 de tête est masqué). En les mettant bout à bout (pour clarifier les différentes sections du format en virgule flottante, le bit de signe et la fraction ont été soulignés et les bits ont été regroupés en groupes de 4 bits) :

Image non disponible

Cela ne fait pas exactement 23,85 (puisqu'il s'agit d'un binaire répétitif). Si l'on convertit le chiffre ci-dessus en décimal, on constate qu'il vaut approximativement 23,849998474, ce nombre est très proche de 23,85, mais n'est pas exactement le même. En réalité, en C, 23,85 ne serait pas représenté exactement comme ci-dessus. Comme le bit le plus à gauche qui a été supprimé de la représentation exacte valait 1, le dernier bit est arrondi à 1, donc 23,85 serait représenté par 41 BE CC CD en hexa en utilisant la simple précision. En décimal, ce chiffre vaut 23,850000381 ce qui est une meilleure approximation de 23,85.

Comment serait représenté -23,85 ? Il suffit de changer le bit de signe :

C1 BE CC CD. Ne prenez pas le complément à 2 !

Certaines combinaisons de e et f ont une signification spéciale pour les flottants IEEE. Le Tableau 6.1 décrit ces valeurs.

Image non disponible

Un infini est produit par un dépassement de capacité ou une division par zéro. Un résultat indéfini

est produit par une opération invalide comme rechercher la racine carrée d'un nombre négatif, additionner deux infinis, etc.

Les nombres en simple précision normalisés peuvent prendre des valeurs de

Image non disponible

VII-A-2-b. Nombres dénormalisés

Les nombres dénormalisés peuvent être utilisés pour représenter des nombres avec des grandeurs trop petites à normaliser (i.e. inférieures à 1, 0 × 2-126).

Par exemple, considérons le nombre Image non disponible

Dans la forme normalisée, l'exposant est trop petit. Par contre, il peut être représenté sous une forme non normalisée : 0, 010012 × 2-127. Pour stocker ce nombre, l'exposant décalé est positionné à 0 (voir Tableau 6.1) et la partie décimale est la mantisse complète du nombre écrite comme un produit avec 2-127 (i.e. tous les bits sont stockés, y compris le 1 à gauche du point décimal). La représentation de 1, 001 × 2-129 est donc :

Image non disponible

VII-A-2-c. Double précision IEEE

La double précision IEEE utilise 64 bits pour représenter les nombres et est précise jusqu'à environ 15 chiffres significatifs. Comme le montre la Figure 6.4, le format de base est très similaire à celui de la simple précision.

Image non disponible

Plus de bits sont utilisés pour l'exposant décalé (11) et la partie décimale (52).

La plage de valeurs plus grande pour l'exposant décalé a deux conséquences. La première est qu'il est calculé en faisant la somme de l'exposant réel et de 3FF (1023) (pas ProductID="7F" w:st="on">7F comme pour la simple précision). Deuxièmement, une grande plage d'exposants réels (et donc une plus grande plage de grandeurs) est disponible. Les grandeurs en double-précision peuvent prendre des valeurs entre environ 13-308 et 10308 .

C'est la plus grande taille du champ réservé à la partie décimale qui est reponsable de l'augmentation du nombre de chiffres significatifs pour les valeurs doubles.

Pour illustrer cela, reprenons 23,85. L'exposant décalé sera 4+3FF = 403 en hexa. Donc, la représentation double sera :

Image non disponible

ou 40 37 D9 99 99 99 99 9A en hexa. Si l'on reconvertit ce nombre en décimal, on trouve 23,8500000000000014 (il y a 12 zéros !) ce qui est une approximation de 23,85 nettement meilleure.

La double précision a les mêmes valeurs spéciales que la simple précision (29).

Les nombres dénormalisés sont également très similaires. La seule différence principale est que les nombres doubles dénormalisés utilisent 2-1023 au lieu de 2-127.

VII-B. Arithmétique en virgule flottante

L'arithmétique en virgule flottante sur un ordinateur est différente de celle des mathématiques. En mathématiques, tous les nombres peuvent être considérés comme exacts. Comme nous l'avons montré dans la section précédente, sur un ordinateur beaucoup de nombres ne peuvent pas être représentés exactement avec un nombre fini de bits. Tous les calculs sont effectués avec une précision limitée. Dans les exemples de cette section, des nombres avec une mantisse de 8 bits seront utilisés pour plus de simplicité.

VII-B-1. Addition

Pour additionner deux nombres en virgule flottante, les exposants doivent être égaux. S'ils ne le sont pas déjà, alors, il faut les rendre égaux en décalant la mantisse du nombre ayant le plus petit exposant. Par exemple, considérons

10, 375 + 6, 34375 = 16, 71875 ou en binaire :

Image non disponible

Ces deux nombres n'ont pas le même exposant, donc on décale la mantisse pour rendre les exposants égaux, puis on effectue l'addition :

Image non disponible

Notez que le décalage de 1, 1001011 × 22 supprime le 1 de tête et arrondi plus tard le résultat en 0, 1100110 × 23. Le résultat de l'addition, 10, 0001100 × 23 ou 1, 00001100 × 24), est égal à

10000,1102 ou 16,75, ce n'est pas égal à la réponse exacte (16,71875) ! Il ne s'agit que d'une approximation due aux erreurs d'arrondi du processus d'addition.

Il est important de réaliser que l'arithmétique en virgule flottante sur un ordinateur (ou une calculatrice) est toujours une approximation. Les lois des mathématiques ne fonctionnent pas toujours avec les nombres en virgule flottante sur un ordinateur. Les mathématiques supposent une précision infinie qu'aucun ordinateur ne peut atteindre. Par exemple, les mathématiques nous apprennent que (a + b) − b = a, cependant, ce n'est pas forcément exactement vrai sur un ordinateur !

VII-B-2. Soustraction

La soustraction fonctionne d'une façon similaire à l'addition et souffre des mêmes problèmes. Par exemple, considérons 16, 75 − 15, 9375 = 0, 8125 :

Image non disponible

Décaler 1, 1111111 × 23 donne (en arrondissant) 1, 0000000 × 24

Image non disponible

VII-B-3. Multiplication et division

Pour la multiplication, les mantisses sont multiplées et les exposants sont additionnés. Considérons 10, 375 × 2, 5 = 25, 9375 :

Image non disponible

Bien sûr, le résultat réel serait arrondi à 8 bits pour donner :

1, 1010000 × 24 = 11010, 0002 = 26

La division est plus compliquée, mais souffre des mêmes problèmes avec des erreurs d'arrondi.

VII-B-4. Conséquences sur la programmation

Le point essentiel de cette section est que les calculs en virgule flottante ne sont pas exacts. Le programmeur doit en être conscient. Une erreur courante que les programmeurs font avec les nombres en virgule flottante est de les comparer en supposant qu'un calcul est exact. Par exemple, considérons une fonction appelée f(x) qui effectue un calcul complexe et un programme qui essaie de trouver la racine de la fonction (30). On peut être tenté d'utiliser l'expression suivante pour vérifier si x est une racine :

Sélectionnez

if ( f(x) == 0.0 )

Mais, que se passe-t-il si f(x) retourne 1×10-30 ? Cela signifie très probablement que x est une très bonne approximation d'une racine réelle, cependant, l'égalité ne sera pas vérifiée. Il n'y a peut-être aucune valeur en virgule flottante IEEE de x qui renvoie exactement zéro, en raison des erreurs d'arrondi dans f(x).

Une meilleure méthode serait d'utiliser :

Sélectionnez

if ( fabs(f(x)) < EPS )

où EPS est une macro définissant une très petite valeur positive (du genre 1 x 10-10). Cette expression est vraie dès que f(x) est très proche de zéro.

En général, pour comparer une valeur en virgule flottante (disons x) à une autre (y), on utilise :

Sélectionnez

if ( fabs(x &#8722; y)/fabs(y) < EPS )

VII-C. Le coprocesseur arithmétique

VII-C-1. Matériel

Les premiers processeurs Intel n'avaient pas de support matériel pour les opérations en virgule flottante. Cela ne signifie pas qu'ils ne pouvaient pas effectuer de telles opérations. Cela signifie seulement qu'elles devaient être réalisées par des procédures composées de beaucoup d'instructions qui n'étaient pas en virgule flottante. Pour ces systèmes, Intel fournissait une puce appelée coprocesseur mathématique. Un coprocesseur mathématique a des instructions machine qui effectuent beaucoup d'opérations en virgule flottante beaucoup plus rapidement qu'en utilisant une procédure logicielle (sur les premiers processeurs, au moins 10 fois plus vite !). Le coprocesseur pour le 8086/8088 s'appelait le 8087, pour le 80286, il y avait un 80287 et pour le 80386, un 80387, le processeur 80486DX intégrait le coprocesseur mathématique dans le 80486 lui-même (31). Depuis le Pentium, toutes les générations de processeurs 80x86 ont un coprocesseur mathématique intégré. Cependant, on le programme toujours comme s'il s'agissait d'une unité séparée. Même les systèmes plus anciens sans coprocesseur peuvent installer un logiciel qui émule un coprocesseur mathématique. Cet émulateur est automatiquement activé lorsqu'un programme exécute une instruction du coprocesseur et lance la procédure logicielle qui produit le même résultat que celui qu'aurait donné le coprocesseur (bien que cela soit plus lent, bien sûr).

Le coprocesseur arithmétique a huit registres de virgule flottante. Chaque registre contient 80 bits de données. Les nombres en virgule flottante sont toujours stockés sous la forme de nombres 80 bits en précision étendue dans ces registres. Les registres sont appelés ST0, ST1, ST2… ST7. Les registres en virgule flottante sont utilisés différemment des registres entiers du processeur principal. Ils sont organisés comme une pile. Souvenez-vous qu'une pile est une liste Last-In First-Out (LIFO, dernier entré, premier sorti). ST0 fait toujours référence à la valeur au sommet de la pile. Tous les nouveaux nombres sont ajoutés au sommet de la pile. Les nombres existants sont décalés vers le bas pour faire de la place au nouveau.

Il y a également un registre de statut dans le coprocesseur arithmétique.

Il a plusieurs drapeaux. Seuls les 4 drapeaux utilisés pour les comparaisons seront traités : C0, C1, C2 et C3. Leur utilisation est traitée plus tard.

VII-C-2. Instructions

Pour faciliter la distinction entre les instructions du processeur normal et celles du coprocesseur, tous les mnémoniques du coprocesseur commencent par un F.

VII-C-2-a. Chargement et stockage

Il y a plusieurs instructions qui chargent des données au sommet de la pile du coprocesseur :

FLD source charge un nombre en virgule flottante depuis la mémoire vers

le sommet de la pile. La source peut être un nombre en simple,

double ou précision étendue ou un registre du coprocesseur.

FILD source lit un entier depuis la mémoire, le convertit en flottant et stocke le résultat au sommet de la pile. La source peut être un mot, un double-mot ou un quadruple-mot.

FLD1 Stocke un 1 au sommet de la pile.

FLDZ Stocke un zéro au sommet de la pile.

Il y a également plusieurs instructions qui déplacent les données depuis la pile vers la mémoire. Certaines de ces instructions retirent le nombre de la pile en même temps qu'elles le déplacent.

FST dest stocke le sommet de la pile (ST0) en mémoire. La destination peut être un nombre en simple ou double précision ou un registre du coprocesseur.

FSTP dest stocke le sommet de la pile en mémoire, exactement comme FST ; cependant, une fois le nombre stocké, sa valeur est retirée de la pile. La destination peut être un nombre en simple, double ou précision étendue ou un registre du coprocesseur.

FIST dest stocke la valeur au sommet de la pile convertie en entier en mémoire. La destination peut être un mot ou un double-mot. La pile en elle-même reste inchangée. La façon dont est converti le nombre en entier dépend de certains bits dans le mot de contrôle du coprocesseur. Il s'agit d'un registre d'un mot spécial (pas en virgule flottante) qui contrôle le fonctionnement du coprocesseur. Par défaut, le mot de contrôle est initialisé afin qu'il arrondisse à l'entier le plus proche lorsqu'il convertit vers un entier. Cependant, les instructions FSTCW (Store Control Word, stocker le mot de contrôle) et FLDCW (Load Control Word, charger le mot de contrôle) peuvent être utilisées pour changer ce comportement.

FISTP dest identique à FIST sauf en deux points. Le sommet de la pile est supprimé et la destination peut également être un quadruple-mot.

Il y a deux autres instructions qui peuvent placer ou retirer des données de la pile.

FXCH STn échange les valeurs de ST0 et STn sur la pile (où n est un numéro de registre entre 1 et 7).

FFREE STn libère un registre sur la pile en le marquant comme inutilisé ou vide.

VII-C-2-b. Addition et soustraction

Chacune des instructions d'addition calcule la somme de ST0 et d'un autre opérande. Le résultat est toujours stocké dans un registre du coprocesseur.

FADD source ST0 += source . La source peut être n'importe quel registre du coprocesseur ou un nombre en simple ou double précision en mémoire.

FADD dest, ST0 dest += ST0. La destination peut être n'importe quel registre du coprocesseur.

FADDP dest ou FADDP dest, STO dest += ST0 puis ST0 est retiré de la pile. La destination peut être n'importe quel registre du coprocesseur.

FIADD source ST0 += (float) source . Ajoute un entier à ST0. La source doit être un mot ou un double-mot en mémoire.

Il y a deux fois plus d'instructions pour la soustraction que pour l'addition, car l'ordre des opérandes est important pour la soustraction (i.e. a+b = b+a, mais a−b ≠ b−a !). Pour chaque instruction, il y a un miroir qui effectue la soustraction dans l'ordre inverse. Ces instructions inverses se finissent toutes soit par R soit par RP. La Figure 6.5 montre un court extrait de code qui ajoute les éléments d'un tableau de doubles.

Image non disponible

Au niveau des lignes 10 et 13, il faut spécifier la taille de l'opérande mémoire. Sinon l'assembleur ne saurait pas si l'opérande mémoire est un float (dword) ou un double (qword).

FSUB source ST0 -= source. La source peut être n'importe quel registre du coprocesseur ou un nombre en simple ou double précision en mémoire.

FSUBR source ST0 = source - ST0. La source peut être n'importe quel registre du coprocesseur ou un nombre en simple ou double précision en mémoire.

FSUB dest, ST0 dest -= ST0. La destination peut être n'importe quel registre du coprocesseur.

FSUBR dest, ST0 dest = ST0 - dest . La destination peut être n'importe quel registre du coprocesseur.

FSUBP dest ou FSUBP dest, STO dest -= ST0 puis retire ST0 de la pile. La destination peut être n'importe quel registre du coprocesseur.

FSUBRP dest ou FSUBRP dest, ST0 dest = ST0 - dest puis retire ST0 de la pile. La destination peut être n'importe quel registre du coprocesseur.

FISUB source ST0 -= (float) source . Soustrait un entier de ST0. La source doit être un mot ou un double mot en mémoire.

FISUBR source ST0 = (float) source - ST0. Soustrait ST0 d'un entier. La source doit être un mot ou un double-mot en mémoire.

VII-C-2-c. Multiplication et division

Les instructions de multiplication sont totalement analogues à celles d'addition.

FMUL source ST0 *= source . La source peut être n'importe quel registre du coprocesseur ou un nombre en simple ou double précision en mémoire.

FMUL dest, ST0 dest *= ST0. La destination peut être n'importe quel registre du coprocesseur.

FMULP dest ou FMULP dest, ST0 dest *= ST0 puis retire ST0 de la pile. La destination peut être n'importe quel registre du coprocesseur.

FIMUL source ST0 *= (float) source. Multiplie un entier avec ST0. La source peut être un mot ou un double-mot en mémoire.

Ce n'est pas étonnant, les instructions de division sont analogues à celles de soustraction. La division par zéro donne l'infini.

FDIV source ST0 /= source . La source peut être n'importe quel registre du coprocesseur ou un nombre en simple ou double précision en mémoire.

FDIVR source ST0 = source / ST0. La source peut être n'importe quel registre du coprocesseur ou un nombre en simple ou double précision en mémoire.

FDIV dest, ST0 dest /= ST0. La destination peut être n'importe quel registre du coprocesseur.

FDIVR dest, ST0 dest = ST0 / dest . La destination peut être n'importe quel registre du coprocesseur.

FDIVP dest ou FDIVP dest, ST0 dest /= ST0 puis retire ST0 de la pile. La destination peut être n'importe quel registre du coprocesseur.

FDIVRP dest ou FDIVRP dest, ST0 dest = ST0 / dest puis retire ST0 de la pile. La destination peut être n'importe quel registre du coprocesseur.

FIDIV source ST0 /= (float) source. Divise ST0 par un entier. La src doit être un mot ou un double-mot en mémoire.

FIDIVR source ST0 = (float) source / ST0. Divise un entier par ST0. La source doit être un mot ou un double-mot en mémoire.

VII-C-2-d. Comparaisons

Le coprocesseur effectue également des comparaisons de nombres en virgule flottante. La famille d'instructions FCOM est faite pour ça.

FCOM source compare ST0 et source. La source peut être un registre du coprocesseur ou un float ou un double en mémoire.

FCOMP source compare ST0 et source, puis retire ST0 de la pile. La source peut être un registre du coprocesseur ou un float ou un double en mémoire.

FCOMPP compare ST0 et ST1, puis retire ST0 et ST1 de la pile.

FICOM source compare ST0 et (float) source. La source peut être un entier sur un mot ou un double-mot en mémoire.

FICOMP source compare ST0 et (float)source, puis retire ST0 de la pile. La source peut être un entier sur un mot ou un double-mot en mémoire.

FTST compare ST0 et 0,

Ces instructions changent les bits C0, C1, C2 et C3 du registre de statut du coprocesseur. Malheureusement, il n'est pas possible pour le processeur d'accéder à ces bits directement. Les instructions de branchement conditionnel utilisent le registre FLAGS, pas le registre de statut du coprocesseur.

Cependant, il est relativement simple de transférer les bits du mot de statut dans les bits correspondants du registre FLAGS en utilisant quelques instructions nouvelles :

FSTSW destination stocke le mot de statut du coprocesseur soit dans un mot en mémoire soit dans le registre AX.

SAHF stocke le registre AH dans le registre FLAGS.

LAHF charge le registre AH avec les bits du registre FLAGS.

La Figure 6.6 montre un court extrait de code en exemple.

Image non disponible

Les lignes 5 et 6 transfèrent les bits C0, C1, C2 et C3 du mot de statut du coprocesseur dans le registre FLAGS. Les bits sont transférés de façon à être identiques au résultat d'une comparaison de deux entiers non signés. C'est pourquoi la ligne 7 utilise une instruction JNA.

Les Pentium Pro (et les processeurs plus récents (Pentium II and III)) supportent deux nouveaux opérateurs de comparaison qui modifient directement le registre FLAGS du processeur.

FCOMI source compare ST0 et source. La source doit être un registre du coprocesseur.

FCOMIP source compare ST0 et source, puis retire ST0 de la pile. La source doit être un registre du coprocesseur.

La Figure 6.7 montre une sous-routine qui trouve le plus grand de deux double en utilisant l'instruction FCOMIP. Ne confondez pas ces instructions avec les fonctions de comparaisons avec un entier (FICOM et FICOMP).

Image non disponible

VII-C-2-e. Instructions diverses

Cette section traite de diverses autres instructions fournies par le coprocesseur.

FCHS ST0 = - ST0 change le signe de ST0.

FABS ST0 = |ST0| extrait la valeur absolue de ST0.

FSQRT extrait la racine carrée de ST0.

FSCALE ST0 = ST0 × 2[ST1] multiplie ST0 par une puissance de 2 rapidement. ST1 n'est pas retiré de la pile du coprocesseur.

La Figure 6.8 montre un exemple d'utilisation de cette instruction.

Image non disponible

VII-C-3. Exemple : formule quadratique

Le premier exemple montre comment la formule quadratique peut être codée en assembleur. Souvenez-vous, la formule quadratique calcule les solutions de l'équation :

ax2 + bx + c = 0

La formule donne deux solutions pour x : x1 et x2.

Image non disponible

L'expression sous la racine carrée (b2 − 4ac) est appelée le déterminant. Sa valeur est utile pour déterminer laquelle des trois possibilités suivantes est vérifiée pour les solutions :

1. Il n'y a qu'une seule solution double. b2 − 4ac = 0 ;

2. Il y a deux solutions réelles. b2 − 4ac > 0 ;

3. Il y a deux solutions complexes. b2 − 4ac < 0.

Voici un court programme C qui utilise la sous-routine assembleur :

quadt.c

Sélectionnez

2
3 int quadratic ( double, double, double, double , double );
4
5  int main()
6 {
7 double a,b,c , root1 , root2;
8
9 printf ("Entrez a , b, c : ");
10 scanf("%lf %lf %lf", &a, &b, &c);
11 if ( quadratic ( a , b, c, &root1, &root2 ) )
12 printf (" racines : %.10g %.10g\n", root1, root2 );
13 else
14 printf ("pas de racine réelle \n");
15 return 0;
16 }

Voici la routine assembleur :

quad.asm

Sélectionnez

1 ; fonction quadratic
2 ; trouve les solutions à l'équation quadratique :
3 ; a*x**2 + b*x + c = 0
4 ; prototype C :
5 ; int quadratic( double a, double b, double c,
6 ; double * root1, double *root2 )
7 ; Paramètres :
8 ; a, b, c - coefficients des puissances de l'équation quadratique (voir ci-dessus)
9 ; root1 - pointeur vers un double où stocker la première racine
10 ; root2 - pointeur vers un double où stocker la deuxième racine
11 ; Valeur de retour :
12 ; retourne 1 si des racines réelles sont trouvées, sinon 0
13
14 ?fine a qword [ebp+8]
15 ?fine b qword [ebp+16]
16 ?fine c qword [ebp+24]
17 ?fine root1 dword [ebp+32]
18 ?fine root2 dword [ebp+36]
19 ?fine disc qword [ebp-8]
20 ?fine one_over_2a qword [ebp-16]
21
22 segment .data
23 MinusFour dw -4
24
25 segment .text
26 global _quadratic
27 _quadratic:
28 push ebp
29 mov ebp, esp
30 sub esp, 16 ; alloue 2 doubles (disc & one_over_2a)
31 push ebx ; on doit sauvegarder l'ebx original
32
33 fild word [MinusFour]; pile : -4
34 fld a ; pile : a, -4
35 fld c ; pile : c, a, -4
36 fmulp st1 ; pile : a*c, -4
37 fmulp st1 ; pile : -4*a*c
38 fld b
39 fld b ; pile : b, b, -4*a*c
40 fmulp st1 ; pile : b*b, -4*a*c
41 faddp st1 ; pile : b*b - 4*a*c
42 ftst ; compare avec 0
43 fstsw ax
44 sahf
45 jb no_real_solutions ; si < 0, pas de solution réelle
46 fsqrt ; pile : sqrt(b*b - 4*a*c)
47 fstp disc ; stocke et décale la pile
48 fld1 ; pile : 1,0
49 fld a ; pile : a, 1,0
50 fscale ; pile : a * 2(1,0) = 2*a, 1
51 fdivp st1 ; pile : 1/(2*a)
52 fst one_over_2a ; pile : 1/(2*a)
53 fld b ; pile : b, 1/(2*a)
54 fld disc ; pile : disc, b, 1/(2*a)
55 fsubrp st1 ; pile : disc - b, 1/(2*a)
56 fmulp st1 ; pile : (-b + disc)/(2*a)
57 mov ebx, root1
58 fstp qword [ebx] ; stocke dans *root1
59 fld b ; pile : b
60 fld disc ; pile : disc, b
61 fchs ; pile : -disc, b
62 fsubrp st1 ; pile : -disc - b
63 fmul one_over_2a ; pile : (-b - disc)/(2*a)
64 mov ebx, root2
65 fstp qword [ebx] ; stocke dans *root2
66 mov eax, 1 ; la valeur de retour est 1
67 jmp short quit
68
69 no_real_solutions :
70 mov eax, 0 ; la valeur de retour est 0
71
72 quit :
73 pop ebx
74 mov esp, ebp
75 pop ebp
76 ret

VII-C-4. Exemple : lire un tableau depuis un fichier

Dans cet exemple, une routine assembleur lit des doubles depuis un fichier. Voici un court programme de test en C :

readt.c

Sélectionnez

1 /
2 Ce programme teste la procédure assembleur 32 bits read_doubles().
3 Il lit des doubles depuis stdin (utilisez une redirection pour lire depuis un fichier).
4 /
5 #include <stdio.h>
6 extern int read_doubles( FILE , double , int );
7 #dene MAX 100
8
9 int main()
10 {
11 int i ,n;
12 double a[MAX];
13
14 n = read_doubles(stdin, a , MAX);
15
16 for ( i=0; i < n; i++ )
17 printf ("= %g\n", i, a[i ]);
18 return 0;
19 }

Voici la routine assembleur :

read.asm

Sélectionnez

1 segment .data
2 format db "%lf", 0 ; format pour fscanf()
3,
4 segment .text
5 global _read_doubles
6 extern _fscanf
7
8 ?fine SIZEOF_DOUBLE 8
9 ?fine FP dword [ebp + 8]
10 ?fine ARRAYP dword [ebp + 12]
11 ?fine ARRAY_SIZE dword [ebp + 16]
12 ?fine TEMP_DOUBLE [ebp - 8]
13
14 ;
15 ; fonction _read_doubles
16 ; prototype C :
17 ; int read_doubles( FILE * fp, double * arrayp, int array_size );
18 ; Cette fonction lit des doubles depuis un fichier texte dans un tableau, jusqu'à
19 ; EOF ou que le tableau soit plein.
20 ; Paramètres :
21 ; fp - FILE pointeur à partir duquel lire (doit être ouvert en lecture)
22 ; arrayp - pointeur vers le tableau de double vers lequel lire
23 ; array_size - nombre d'éléments du tableau
24 ; Valeur de retour :
25 ; nombre de doubles stockés dans le tableau (dans EAX)
26
27 _read_doubles:
28 push ebp
29 mov ebp,esp
30 sub esp, SIZEOF_DOUBLE ; définit un double sur la pile
31
32 push esi ; sauve esi
33 mov esi, ARRAYP ; esi = ARRAYP
34 xor edx, edx ; edx = indice du tableau (initialement 0)
35
36 while_loop:
37 cmp edx, ARRAY_SIZE ; edx < ARRAY_SIZE ?
38 jnl short quit ; si non, quitte la boucle
39 ;
40 ; appelle fscanf() pour lire un double dans TEMP_DOUBLE
41 ; fscanf() peut changer edx, donc on le sauvegarde
42 ;
43 push edx ; sauve edx
44 lea eax, TEMP_DOUBLE
45 push eax ; empile &TEMP_DOUBLE
46 push dword format ; emplie &format
47 push FP ; emplie file pointer
48 call _fscanf
49 add esp, 12
50 pop edx ; restaure edx
51 cmp eax, 1 ; fscanf a retourné 1 ?
52 jne short quit ; si non, on quitte la boucle
53
54 ;
55 ; copie TEMP_DOUBLE dans ARRAYP[edx]
56 ; (Les 8 octets du double sont copiés en deux fois 4 octets)
57 ;
58 mov eax, [ebp - 8]
59 mov [esi + 8*edx], eax ; copie des 4 octets de poids faible
60 mov eax, [ebp - 4]
61 mov [esi + 8*edx + 4], eax ; copie des 4 octets de poids fort
62
63 inc edx
64 jmp while_loop
65
66 quit :
67 pop esi ; restaure esi
68
69 mov eax, edx ; stocke la valeur de retour dans eax
70
71 mov esp, ebp
72 pop ebp
73 ret

VII-C-5. Exemple : rechercher les nombres premiers

Ce dernier exemple recherche les nombres premiers, une fois de plus.

Cette implémentation est plus efficace que la précédente. Elle stocke les nombres premiers déjà trouvés dans un tableau et ne divise que par ceux-ci au lieu de diviser par tous les nombres impairs pour trouver de nouveaux premiers.

Une autre différence est qu'il calcule la racine carrée du candidat pour le prochain premier afin de déterminer le moment où il peut s'arrêter de rechercher des facteurs. Il altère le fonctionnement du coprocesseur afin que lorsqu'il stocke la racine sous forme d'entier, il la tronque au lieu de l'arrondir.

Ce sont les bits 10 et 11 du mot de contrôle qui permettent de paramétrer cela. Ces bits sont appelés les bits RC (Rounding Control, contrôle d'arrondi).

S'ils sont tous les deux à 0 (par défaut), le coprocesseur arrondit lorsqu'il convertit vers un entier. S'ils sont tous les deux à 1, le copresseur tronque les conversions en entiers. Notez que la routine fait attention à sauvegarder le mot de contrôle original et à le restaurer avant de quitter.

Voici le programme C pilote :

fprime.c

Sélectionnez

1 #include <stdio.h>
2 #include <stdlib.h>
3 /
4 fonction nd_primes
5 recherche le nombre indiqué de nombres premiers
6 èParamtres :
ProductID="7 a" w:st="on">7 a &#8722; tableau pour contenir les nombres premiers
8 n &#8722; nombre de nombres premiers à trouver
9 /
10 extern void nd_primes( int a , unsigned n );
11
12 int main()
13 {
14 int statut ;
15 unsigned i;
16 unsigned max;
17 int a;
18
19 printf ("Combien de nombres premiers voulez-vous trouver ? ");
20 scanf("%u", &max);
21
ProductID="22 a" w:st="on">22 a = calloc ( sizeof(int ), max);
23
24 if ( a ) {
25
26 nd_primes(a,max);
27
28 / affiche les 20 derniers nombres premiers trouvés /
29 for( i= ( max > 20 ) ? max &#8722; 20 : 0; i < max; i++ )
30 printf ("= %d\n", i+1, a[i]);
31
32 free (a);
33 statut = 0;
34 }
35 else {
36 fprintf ( stderr , "Impossible de  créer un tableau de %u entiers\n", max);
37 statut = 1;
38 }
39
40 return statut ;
41 }

Voici la routine assembleur :

prime2.asm

Sélectionnez

1 segment .text
2 global _find_primes
3 ;
4 ; fonction find_primes
5 ; trouve le nombre indiqué de nombres premiers
6 ; Paramètres :
7 ; array - tableau pour contenir les nombres premiers
8 ; n_find - nombre de nombres premiers à trouver
9 ; Prototype C :
10 ;extern void find_primes( int * array, unsigned n_find )
11 ;
12 ?fine array ebp + 8
13 ?fine n_find ebp + 12
14 ?fine n ebp - 4 ; Nombre de nombres premiers trouvés
15 ?fine isqrt ebp - 8 ; racine du candidat
16 ?fine orig_cntl_wd ebp - 10 ; mot de contrôle original
17 ?fine new_cntl_wd ebp - 12 ; nouveau mot de contrôle
18
19 _find_primes :
20 enter 12,0 ; fait de la place pour les variables locales
21
22 push ebx ; sauvegarde les variables registre éventuelles
23 push esi
24
25 fstcw word [orig_cntl_wd] ; récupère le mot de contrôle courant
26 mov ax, [orig_cntl_wd]
27 or ax, 0C00h ; positionne les bits d'arrondi à 11 (tronquer)
28 mov [new_cntl_wd], ax
29 fldcw word [new_cntl_wd]
30
31 mov esi, [array] ; esi pointe sur array
32 mov dword [esi], 2 ; array[0] = 2
33 mov dword [esi + 4], 3 ; array[1] = 3
34 mov ebx, 5 ; ebx = guess = 5
35 mov dword [n], 2 ; n = 2
36 ;
37 ; Cette boucle externe trouve un nouveau nombre premier à chaque itération qu'il
38 ; ajoute à la fin du tableau. Contrairement au programme de recherche de nombres
39 ; premier précédent, cette fonction ne détermine pas la primauté du nombre en
40 ; divisant par tous les nombres impairs. Elle ne divise que par les nombres
41 ; premiers qu'elle a déjà trouvé (c'est pourquoi ils sont stockés dans un tableau).
42 ;
43 while_limit:
44 mov eax, [n]
45 cmp eax, [n_find] ; while ( n < n_find )
46 jnb short quit_limit
47
48 mov ecx, 1 ; ecx est utilisé comme indice
49 push ebx ; stocke le candidat sur la pile
50 fild dword [esp] ; le charge sur la pile du coprocesseur
51 pop ebx ; retire le candidat de la pile
52 fsqrt ; calcule sqrt(guess)
53 fistp dword [isqrt] ; isqrt = floor(sqrt(quess))
54 ;
55 ; Cette boucle interne divise le candidat (ebx) par les nombres premiers
56 ; calculés précédemment jusqu'à ce qu'il trouve un de ses facteurs premiers
57 ; (ce qui signifie que le candidat n'est pas premier) ou jusqu'à ce que le
58 ; nombre premier par lequel diviser soit plus grand que floor(sqrt(guess))
59 ;
60 while_factor:
61 mov eax, dword [esi + 4*ecx] ; eax = array[ecx]
62 cmp eax, [isqrt] ; while ( isqrt < array[ecx]
63 jnbe short quit_factor_prime
64 mov eax, ebx
65 xor edx, edx
66 div dword [esi + 4*ecx]
67 or edx, edx ; && guess % array[ecx] != 0 )
68 jz short quit_factor_not_prime
69 inc ecx ; essaie le nombre premier suivant
70 jmp short while_factor
71
72 ;
73 ; found a new prime !
74 ;
75 quit_factor_prime :
76 mov eax, [n]
77 mov dword [esi + 4*eax], ebx ; ajoute le candidat au tableau
78 inc eax
79 mov [n], eax ; incrémente n
80
81 quit_factor_not_prime :
82 add ebx, 2 ; essaie le nombre impair suivant
83 jmp short while_limit
84
85 quit_limit:
86
87 fldcw word [orig_cntl_wd] ; restaure le mot de contrôle
88 pop esi ; restaure les variables registre
89 pop ebx
90
91 leave
92 ret


---------------------------------------------------------------------------
       Microsoft (R) Help File Maintenance Utility
       Version 1.05 - Jan 24 1990
       Copyright (c) Microsoft Corp 1988-1990. All rights reserved.
---------------------------------------------------------------------------

.context F2XM1
.context @L8011
:nF2XM1 Instruction
:z3
:l15

----------------------------------------------------------------------------

  Title:    Calculate 2(^X)-1

  Syntax:   F2XM1

  See also: FYL2X, FYL2XP1

  Description:

     Calculates Y = 2(^X) - 1 where X is taken from ST. The result, Y,
     is returned in ST. X must be in the range 0 <= X <= 0.5 on the
     8087/287, or in the range -1.0 <= X <= +1.0 on the 80387/486.
                                    -+-
.context @L8012
:z2
:nF2XM1 Instruction
:l8

-----------------+------------------------+---------------------------------
 F2XM1           | f2xm1                  |  87 310-630
                 |                        | 287 310-630
                 |                        | 387 211-476
                 |                        | 486 140-279
-----------------+------------------------+---------------------------------
                                    -+-
.context FABS
.context @L8013
:nFABS Instruction
:z3
:l13

----------------------------------------------------------------------------

  Title:    Absolute Value

  Syntax:   FABS

  See also: FCHS, FRNDINT

  Description:

     Converts the element in ST to its absolute value.
                                    -+-
.context @L8014
:z2
:nFABS Instruction
:l8

-----------------+------------------------+---------------------------------
 FABS            | fabs                   |  87 10-17
                 |                        | 287 10-17
                 |                        | 387 22
                 |                        | 486 3
-----------------+------------------------+---------------------------------
                                    -+-
.context FADD
.context FADDP
.context FIADD
.context @L8015
:nFADD Instruction
:z3
:l24

----------------------------------------------------------------------------

  Title:    Add

  Syntax:   FADD [dest,src]
            FADDP dest,ST
            FADD src
            FIADD src

  See also: FSUB, FSUBR

  Description:

     Adds the source to the destination and returns the sum in the
     destination. If two register operands are specified, one must be
     ST. If a memory operand is specified, the sum replaces the value
     in ST. Memory operands can be 32- or 64-bit real numbers or 16-
     or 32-bit integers.

     If no operand is specified, ST is added to ST(1) and the stack is
     popped; the sum is returned in ST. For FADDP, the source must be
     ST; the sum is returned in the destination and ST is popped.
                                    -+-
.context @L8016
:z2
:nFADD Instruction
:l23

----------------+-------------------------+---------------------------------
 FADD [reg,reg] | fadd   st,st(2)         |  87 70-100
                | fadd   st(5),st         | 287 70-100
                | fadd                    | 387 to=23-31,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FADDP reg,ST   | faddp  st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 23-31
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FADD memreal   | fadd   QWORD PTR [bx]   |  87 (s=90-120,s=95-125)+EA
                | fadd   shortreal        | 287 s=90-120,l=95-125
                |                         | 387 s=24-32,l=29-37
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FIADD memint   | fiadd  int16            |  87 (w=102-137,d=108-143)+EA
                | fiadd  warray[di]       | 287 w=102-137,d=108-143
                | fiadd  double           | 387 w=71-85,d=57-72
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------
                                    -+-
.context FCHS
.context @L8017
:nFCHS Instruction
:z3
:l13

----------------------------------------------------------------------------

  Title:    Change Sign

  Syntax:   FCHS

  See also: FABS

  Description:

     Reverses the sign of the value in ST.
                                    -+-
.context @L8018
:z2
:nFCHS Instruction
:l8

-----------------+------------------------+---------------------------------
 FCHS            | fchs                   |  87 10-17
                 |                        | 287 10-17
                 |                        | 387 24-25
                 |                        | 486 6
-----------------+------------------------+---------------------------------
                                    -+-
.context FCLEX
.context FNCLEX
.context @L8019
:nFCLEX Instruction
:z3
:l22

----------------------------------------------------------------------------

  Title:    Clear Exceptions

  Syntax:   FCLEX
            FNCLEX

  See also: Status Word

  Description:

     Clears all exception flags, the busy flag, and bit 7 in the status
     word. Bit 7 is the interrupt-request flag on the 8087 and the
     error-status flag on the 80287, 80387, and 80486.

     This instruction has wait and no-wait versions. The wait version
     (FCLEX) checks for unmasked numeric errors. The no-wait version
     (FNCLEX) does not. When the .8087 directive is used, the assembler
     puts the WAIT instruction before the wait versions and the NOP
     instruction before the no-wait versions.
                                    -+-
.context @L801a
:z2
:nFCLEX Instruction
:l11

-----------------+------------------------+---------------------------------
 FCLEX           | fclex                  |  87 2-8
 FNCLEX          |                        | 287 2-8
                 |                        | 387 11
                 |                        | 486 7
-----------------+------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.
                                    -+-
.context FCOM
.context FCOMP
.context FCOMPP
.context FICOM
.context FICOMP
.context @L801b
:nFCOM Instruction
:z3
:l36

----------------------------------------------------------------------------

  Title:    Compare

  Syntax:   FCOM [src]
            FCOMP [src]
            FCOMPP
            FICOM src
            FICOMP src

  See also: FTST, FUCOM, FSTSW, SAHF, FXAM, Status Word

  Description:

     Compares the specified source operand to ST and sets the condition
     codes of the status word according to the result. The instruction
     subtracts the source operand from ST without changing either
     operand. Memory operands can be 32- or 64-bit real numbers or 16-
     or 32-bit integers.

     If no operand is specified or if two pops are specified, ST is
     compared to ST(1) and the stack is popped. If one pop is specified
     with an operand, the operand is compared to ST. If one of the
     operands is a NAN, an invalid-operation exception occurs.
     See: FUCOM

     The condition codes for FCOM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST > source
     0      0      ?      1      ST < source
     1      0      ?      0      ST = source
     1      1      ?      1      ST not comparable to source
                                    -+-
.context @L801c
:z2
:nFCOM Instruction
:l38

----------------+-------------------------+---------------------------------
 FCOM [reg]     | fcom   st(2)            |  87 40-50
                | fcom                    | 287 40-50
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMP [reg]    | fcomp  st(7)            |  87 42-52
                | fcomp                   | 287 42-52
                |                         | 387 26
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMPP         | fcompp                  |  87 45-55
                |                         | 287 45-55
                |                         | 387 26
                |                         | 486 5
----------------+-------------------------+---------------------------------
 FCOM memreal   | fcom   shortreals[di]   |  87 (s=60-70,l=65-75)+EA
                | fcom   longreal         | 287 s=60-70,l=65-75
                |                         | 387 s=26,l=31
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMP memreal  | fcomp  longreal         |  87 (s=63-73,l=67-77)+EA
                | fcomp  shorts[di]       | 287 s=63-73,l=67-77
                |                         | 387 s=26,l=31
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FICOM memint   | ficom  double           |  87 (w=72-86,d=78-91)+EA
                | ficom  warray[di]       | 287 w=72-86,d=78-91
                |                         | 387 w=71-75,d=56-63
                |                         | 486 w=16-20,d=15-17
----------------+-------------------------+---------------------------------
 FICOMP memint  | ficomp WORD PTR [bp+6]  |  87 (w=74-88,d=80-93)+EA
                | ficomp darray[di]       | 287 w=74-88,d=80-93
                |                         | 387 w=71-75,d=56-63
                |                         | 486 w=16-20,d=15-17
----------------+-------------------------+---------------------------------
                                    -+-
.context FSIN
.context FCOS
.context FSINCOS
.context @L801d
:nFSIN/FCOS/FSINCOS Instructions
:z3
:l25

----------------------------------------------------------------------------

  Title:    Sine/Cosine
            80387/486 Only

  Syntax:   FSIN
            FCOS
            FSINCOS

  See also: .387, .486, FPREM, FPREM1, FPTAN

  Description:

     FSIN replaces a value in radians in ST with its sine. FCOS
     replaces a value in radians in ST with its cosine. FSINCOS
     calculates the sine and cosine of a radian value in ST. The sine
     replaces the value in ST, and then the cosine is pushed onto the
     stack.

     If |ST| < 2^63, the C2 bit of the status word is cleared and the
     sine and/or cosine is calculated. Otherwise, C2 is set and no
     calculation is performed. ST can be reduced to the required range
     with FPREM or FPREM1.
                                    -+-
.context @L801e
:z2
:nFSIN/FCOS/FSINCOS Instructions
:l23

----------------+-------------------------+---------------------------------
 FSIN           | fsin                    |  87 --
                |                         | 287 --
                |                         | 387 122-771*
                |                         | 486 257-354œ
----------------+-------------------------+---------------------------------
 FCOS           | fcos                    |  87 --
                |                         | 287 --
                |                         | 387 123-772*
                |                         | 486 257-354œ
----------------+-------------------------+---------------------------------
 FSINCOS        | fsincos                 |  87 --
                |                         | 287 --
                |                         | 387 194-809*
                |                         | 486 292-365œ
----------------+-------------------------+---------------------------------
  * For operands with an absolute value greater than pi/4, up to 76
    additional clocks may be required.

  œ For operands with an absolute value greater than pi/4, add n clocks
    where n = operand / (pi/4).
                                    -+-
.context FINCSTP
.context FDECSTP
.context @L801f
:nFINCSTP/FDECSTP Instructions
:z3
:l15

----------------------------------------------------------------------------

  Title:    Increment or Decrement Stack Pointer

  Syntax:   FINCSTP
            FDECSTP

  Description:

     Increments or decrements the stack-top pointer in the status word.
     No tags or registers are changed, and no data is transferred. If
     the stack pointer is 7, FINCSTP changes it to 0. If the stack
     pointer is 0, FDECSTP changes it to 7.
                                    -+-
.context @L8020
:z2
:nFINCSTP/FDECSTP Instructions
:l13

----------------+-------------------------+---------------------------------
 FINCSTP        | fincstp                 |  87 6-12
                |                         | 287 6-12
                |                         | 387 21
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FDECSTP        | fdecstp                 |  87 6-12
                |                         | 287 6-12
                |                         | 387 22
                |                         | 486 3
----------------+-------------------------+---------------------------------
                                    -+-
.context FENI
.context FNENI
.context FDISI
.context FNDISI
.context @L8021
:nFDISI/FENI Instructions
:z3
:l28

----------------------------------------------------------------------------

  Title:    Enable or Disable Interrupts
            8087 Only

  Syntax:   FDISI
            FNDISI
            FENI
            FNENI

  See also: .8087

  Description:

     FDISI disables interrupts by setting the interrupt-enable mask in
     the control word; FENI enables them by clearing the interrupt-
     enable mask. Since the 80287, 80387, and 80486 do not have an
     interrupt-enable mask, FDISI and FENI are recognized but ignored
     on these coprocessors.

     These instructions have wait and no-wait versions. The wait
     versions (FDISI and FENI) check for unmasked numeric errors; the
     no-wait versions (FNDISI and FNENI) do not. When the .8087
     directive is used, the assembler puts the WAIT instruction before
     the wait versions and the NOP instruction before the no-wait
     versions.
                                    -+-
.context @L8022
:z2
:nFDISI/FENI Instructions
:l16

----------------+-------------------------+---------------------------------
 FDISI          | fdisi                   |  87 2-8
 FNDISI         |                         | 287 2
                |                         | 387 2
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FENI           | feni                    |  87 2-8
 FNENI          |                         | 287 2
                |                         | 387 2
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instructions.
        The wait versions may take additional clock cycles.
                                    -+-
.context FDIV
.context FDIVP
.context FIDIV
.context @L8023
:nFDIV Instructions
:z3
:l25

----------------------------------------------------------------------------

  Title:    Divide

  Syntax:   FDIV [dest,src]
            FDIVP dest,ST
            FDIV src
            FIDIV src

  See also: FDIVR, FMUL

  Description:

     Divides the destination by the source and returns the quotient in
     the destination. If two register operands are specified, one must
     be ST. If a memory operand is specified, the quotient replaces the
     value in ST. Memory operands can be 32- or 64-bit real numbers or
     16- or 32-bit integers.

     If no operand is specified, ST(1) is divided by ST and the stack
     is popped; the result is returned in ST. For FDIVP, the source
     must be ST; the quotient is returned in the destination register
     and ST is popped.
                                    -+-
.context @L8024
:z2
:nFDIV Instruction
:l23

----------------+-------------------------+---------------------------------
 FDIV [reg,reg] | fdiv   st,st(2)         |  87 193-203
                | fdiv   st(5),st         | 287 193-203
                | fdiv                    | 387 to=88,fr=91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVP reg,ST   | fdivp  st(6),st         |  87 197-207
                |                         | 287 197-207
                |                         | 387 91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIV memreal   | fdiv   DWORD PTR [bx]   |  87 (s=215-225,l=220-230)+EA
                | fdiv   shortreal[di]    | 287 s=215-225,l=220-230
                | fdiv   longreal         | 387 s=89,l=94
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FIDIV memint   | fidiv  int16            |  87 (w=224-238,d=230-243)+EA
                | fidiv  warray[di]       | 287 w=224-238,d=230-243
                | fidiv  double           | 387 w=136-140,d=120-127
                |                         | 486 w=85-89,d=84-86
----------------+-------------------------+---------------------------------
                                    -+-
.context FDIVR
.context FDIVRP
.context FIDIVR
.context @L8025
:nFDIVR Instructions
:z3
:l25

----------------------------------------------------------------------------

  Title:    Divide Reversed

  Syntax:   FDIVR [dest,src]
            FDIVRP dest,ST
            FDIVR src
            FIDIVR src

  See also: FDIV, FMUL

  Description:

     Divides the source by the destination and returns the quotient in
     the destination. If two register operands are specified, one must
     be ST. If a memory operand is specified, the quotient replaces the
     value in ST. Memory operands can be 32- or 64-bit real numbers or
     16- or 32-bit integers.

     If no operand is specified, ST is divided by ST(1) and the stack
     is popped; the result is returned in ST. For FDIVRP, the source
     must be ST; the quotient is returned in the destination register
     and ST is popped.
                                    -+-
.context @L8026
:z2
:nFDIVR Instruction
:l23

----------------+-------------------------+---------------------------------
 FDIVR [reg,reg]| fdivr  st,st(2)         |  87 194-204
                | fdivr  st(5),st         | 287 194-204
                | fdivr                   | 387 to=88,fr=91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVRP reg,ST  | fdivrp st(6),st         |  87 198-208
                |                         | 287 198-208
                |                         | 387 91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVR memreal  | fdivr  longreal         |  87 (s=216-226,l=221-231)+EA
                | fdivr  shortreal[di]    | 287 s=216-226,l=221-231
                |                         | 387 s=89,l=94
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FIDIVR memint  | fidivr double           |  87 (w=225-239,d=231-245)+EA
                | fidivr warray[di]       | 287 w=225-239,d=231-245
                |                         | 387 w=135-141,d=121-128
                |                         | 486 w=85-89,d=84-86
----------------+-------------------------+---------------------------------
                                    -+-
.context FFREE
.context @L8027
:nFFREE Instruction
:z3
:l14

----------------------------------------------------------------------------

  Title:    Free Register

  Syntax:   FFREE dest

  See also: Coprocessor Environment

  Description:

     Changes the specified register's tag to empty without changing the
     contents of the register.
                                    -+-
.context @L8028
:z2
:nFFREE Instruction
:l8

----------------+-------------------------+---------------------------------
 FFREE ST(i)    | ffree  st(3)            |  87 9-16
                |                         | 287 9-16
                |                         | 387 18
                |                         | 486 3
----------------+-------------------------+---------------------------------
                                    -+-
.context FINIT
.context FNINIT
.context @L8029
:nFINIT Instruction
:z3
:l20

----------------------------------------------------------------------------

  Title:    Initialize Coprocessor

  Syntax:    FINIT
             FNINIT

  Description:

     Initializes the coprocessor and resets all the registers and flags
     to their default values. On the 80387/486, the condition codes of
     the status word are cleared. On the 8087/287, they are unchanged.

     This instruction has wait and no-wait versions. The wait version
     (FINIT) checks for unmasked numeric errors; the no-wait version
     (FNINIT) does not. When the .8087 directive is used, the assembler
     puts the WAIT instruction before the wait versions and the NOP
     instruction before the no-wait versions.
                                    -+-
.context @L802a
:z2
:nFINIT Instruction
:l11

----------------+-------------------------+---------------------------------
 FINIT          | finit                   |  87 2-8
 FNINIT         |                         | 287 2-8
                |                         | 387 33
                |                         | 486 17
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.
                                    -+-
.context FLD
.context FILD
.context FBLD
.context @L802b
:nFLD Instruction
:z3
:l20

----------------------------------------------------------------------------

  Title:    Load

  Syntax:   FLD src
            FILD src
            FBLD src

  See also: FLDconst, FST, FXCH

  Description:

     Pushes the specified operand onto the stack. All memory operands
     are automatically converted to temporary-real numbers before being
     loaded.

     Memory operands can be 32-, 64-, or 80-bit real numbers or 16-,
     32-, or 64-bit integers.
                                    -+-
.context @L802c
:z2
:nFLD Instruction
:l23

--------------+------------------------+-----------------------------------
 FLD reg      | fld   st(3)            |  87 17-22
              |                        | 287 17-22
              |                        | 387 14
              |                        | 486  4
--------------+------------------------+-----------------------------------
 FLD memreal  | fld   longreal         |  87 (s=38-56,l=40-60,t=53-65)+EA
              | fld   shortarray[bx+di]| 287 s=38-56,l=40-60,t=53-65
              | fld   tempreal         | 387 s=20,l=25,t=44
              |                        | 486 s=3,l=3,t=6
--------------+------------------------+-----------------------------------
 FILD memint  | fild  mem16            |  87 (w=46-54,d=52-60,q=60-68)+EA
              | fild  DWORD PTR [bx]   | 287 w=46-54,d=52-60,q=60-68
              | fild  quads[si]        | 387 w=61-65,d=45-52,q=56-67
              |                        | 486 w=13-16,d=9-12,q=10-18
--------------+------------------------+-----------------------------------
 FBLD membcd  | fbld  packbcd          |  87 (290-310)+EA
              |                        | 287 290-310
              |                        | 387 266-275
              |                        | 486 70-103
--------------+------------------------+-----------------------------------
                                    -+-
.context FLD1
.context FLDZ
.context FLDPI
.context FLDL2E
.context FLDL2T
.context FLDLG2
.context FLDLN2
.context @L802d
:nFLDconst Instruction
:z3
:l34

----------------------------------------------------------------------------

  Title:    Load Constant

  Syntax:   FLDconst

  See also: FLD

  Description:

     Pushes a constant onto the stack. The following constants can be
     loaded:

     Instruction     Constant

     FLD1            +1.0

     FLDZ            +0.0

     FLDPI           pi

     FLDL2E          Log (e)
                        2

     FLDL2T          Log (10)
                        2

     FLDLG2          Log  (2)
                        10

     FLDLN2          Log (2)
                        e
                                    -+-
.context @L802e
:z2
:nFLDconst Instruction
:l38

----------------+-------------------------+---------------------------------
 FLD1           | fld1                    |  87 15-21
                |                         | 287 15-21
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FLDZ           | fldz                    |  87 11-17
                |                         | 287 11-17
                |                         | 387 20
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FLDPI          | fldpi                   |  87 16-22
                |                         | 287 16-22
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDL2E         | fldl2e                  |  87 15-21
                |                         | 287 15-21
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDL2T         | fldl2t                  |  87 16-22
                |                         | 287 16-22
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDLG2         | fldlg2                  |  87 18-24
                |                         | 287 18-24
                |                         | 387 41
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDLN2         | fldln2                  |  87 17-23
                |                         | 287 17-23
                |                         | 387 41
                |                         | 486 8
----------------+-------------------------+---------------------------------
                                    -+-
.context FLDCW
.context FSTCW
.context FNSTCW
.context @L802f
:nFLDCW/FSTCW Instructions
:z3
:l23

----------------------------------------------------------------------------

  Title:    Load or Store Control Word

  Syntax:   FLDCW src
            FSTCW dest
            FNSTCW dest

  See also: FSTSW, FSTENV, FSAVE, Control Word

  Description:

     FLDCW loads the specified word into the coprocessor control word.
     FSTCW stores the control word to a specified 16-bit memory
     operand.

     FSTCW has wait and no-wait versions. The wait version (FSTCW)
     checks for unmasked numeric errors; the no-wait version (FNSTCW)
     does not. When the .8087 directive is used, the assembler puts the
     WAIT instruction before the wait version and the NOP instruction
     before the no-wait version.
                                    -+-
.context @L8030
:z2
:nFLDCW/FSTCW Instructions
:l16

----------------+-------------------------+---------------------------------
 FLDCW mem16    | fldcw  ctrlword         |  87 (7-14)+EA
                |                         | 287 7-14
                |                         | 387 19
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FSTCW mem16    | fstcw  ctrlword         |  87 12-18
 FNSTCW mem16   |                         | 287 12-18
                |                         | 387 15
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the FSTCW. The wait
        version may take additional clock cycles.
                                    -+-
.context FLDENV
.context FLDENVW
.context FLDENVD
.context FSTENV
.context FSTENVW
.context FSTENVD
.context FNSTENV
.context FNSTENVW
.context FNSTENVD
.context @L8031
:nFSTENV/FLDENV Instructions
:z3
:l27

----------------------------------------------------------------------------

  Title:    Load or Store Environment State

  Syntax:   FSTENV dest
            FNSTENV dest
            FLDENV mem

  See also: FSAVE, FSTCW, FSTSW, Coprocessor Environment

  Description:

     FSTENV stores the 14-byte coprocessor environment state to a
     specified memory location. FLDENV loads it from a specified memory
     location.

     The environment state includes the control word, status word, tag
     word, instruction pointer, and operand pointer. On the 80387/486
     in 32-bit mode, the environment state is 28 bytes.

     The FSTENV instruction has wait and no-wait versions. The wait
     version (FSTENV) checks for unmasked numeric errors; the no-wait
     version (FNSTENV) does not. When the .8087 directive is used, the
     assembler puts the WAIT instruction before the wait version and
     the NOP instruction before the no-wait version.
                                    -+-
.context @L8032
:z2
:nFSTENV/FLDENV Instructions
:l19

----------------+-------------------------+---------------------------------
 FLDENV mem     | fldenv [bp+10]          |  87 (35-45)+EA
 FLDENVW mem*   |                         | 287 35-45
 FLDENVD mem*   |                         | 387 71
                |                         | 486 44,pm=34
----------------+-------------------------+---------------------------------
 FSTENV mem     | fstenv [bp-14]          |  87 (40-50)+EA
 FSTENVW mem*   |                         | 287 40-50
 FSTENVD mem*   |                         | 387 103-104
 FNSTENV mem    |                         | 486 67,pm=56
 FNSTENVW mem*  |                         |
 FNSTENVD mem*  |                         |
----------------+-------------------------+---------------------------------
  * 80387/486 only.

  NOTE: The timings reflect the no-wait versions of FSTENV. The wait
        versions may take additional clock cycles.
                                    -+-
.context FMUL
.context FMULP
.context FIMUL
.context @L8033
:nFMUL Instruction
:z3
:l25

----------------------------------------------------------------------------

  Title:    Multiply

  Syntax:   FMUL [dest,src]
            FMULP dest,ST
            FMUL src
            FIMUL src

  See also: FDIV, FDIVR

  Description:

     Multiplies the source by the destination and returns the product
     in the destination. If two register operands are specified, one
     must be ST. If a memory operand is specified, the product replaces
     the value in ST. Memory operands can be 32- or 64-bit real numbers
     or 16- or 32-bit integers.

     If no operand is specified, ST(1) is multiplied by ST and the
     stack is popped; the product is returned in ST. For FMULP, the
     source must be ST; the product is returned in the destination
     register and ST is popped.
                                    -+-
.context @L8034
:z2
:nFMUL Instruction
:l33

----------------+------------------------+----------------------------------
 FMUL [reg,reg] | fmul   st,st(2)        |  87 130-145 (90-105)*
                | fmul   st(5),st        | 287 130-145 (90-105)*
                | fmul                   | 387 to=46-54 (49),fr=29-57 (52)œ
                |                        | 486 16
----------------+------------------------+----------------------------------
 FMULP reg,ST   | fmulp  st(6),st        |  87 134-148 (94-108)*
                |                        | 287 134-148 (94-108)*
                |                        | 387 29-57 (52)œ
                |                        | 486 16
----------------+------------------------+----------------------------------
 FMUL memreal   | fmul   DWORD PTR [bx]  |  87 (s=110-125,l=154-168)+EA
                | fmul   shortreal[di+3] | 287 s=110-125,l=154-168
                | fmul   longreal        | 387 s=27-35,l=32-57
                |                        | 486 s=11,l=14
----------------+------------------------+----------------------------------
 FIMUL memint   | fimul  int16           |  87 (w=124-138,d=130-144)+EA
                | fimul  warray[di]      | 287 w=124-138,d=130-144
                | fimul  double          | 387 w=76-87,d=61-82
                |                        | 486 w=23-27,d=22-24
----------------+------------------------+----------------------------------
  * The clocks in parentheses show times for short values--those with 40
    trailing zeros in their fraction because they were loaded from a
    short-real memory operand.

  œ The clocks in parentheses show typical speeds.

   If the register operand is a short value--having 40 trailing zeros
    in its fraction because it was loaded from a short-real memory
    operand--then the timing is (112-126)+EA on the 8087 or 112-126 on
    the 80287.
                                    -+-
.context FNOP
.context @L8035
:nFNOP Instruction
:z3
:l14

----------------------------------------------------------------------------

  Title:    No Operation

  Syntax:   FNOP

  See also: NOP

  Description:

     Performs no operation. FNOP can be used for timing delays or
     alignment.
                                    -+-
.context @L8036
:z2
:nFNOP Instruction
:l8

----------------+-------------------------+---------------------------------
 FNOP           | fnop                    |  87 10-16
                |                         | 287 10-16
                |                         | 387 12
                |                         | 486 3
----------------+-------------------------+---------------------------------
                                    -+-
.context FPATAN
.context FPTAN
.context @L8037
:nFPATAN/FPTAN Instructions
:z3
:l24

----------------------------------------------------------------------------

  Title:    Partial Arctangent and Tangent

  Syntax:   FPATAN
            FPTAN

  See also: FSIN

  Description:

     FPATAN finds the partial tangent by calculating Z = ARCTAN(Y / X)
     where X is taken from ST and Y from ST(1). On the 8087/287, Y and
     X must be in the range 0 <= Y < X < infinity. On the 80387/486,
     there is no restriction on X and Y. X is popped from the stack and
     Z replaces Y in ST.

     FPTAN finds the partial tangent by calculating Y / X = TAN(Z). Z
     is taken from ST. Z must be in the range 0 <= Z <= pi/4 on the
     8087/287. On the 80387/486, |Z| must be less than 2^63. The result
     is the ratio Y / X. Y replaces Z, and X is pushed into ST. Thus, Y
     is returned in ST(1) and X in ST.
                                    -+-
.context @L8038
:z2
:nFPATAN/FPTAN Instructions
:l18

----------------+-------------------------+---------------------------------
 FPATAN         | fpatan                  |  87 250-800
                |                         | 287 250-800
                |                         | 387 314-487
                |                         | 486 218-303
----------------+-------------------------+---------------------------------
 FPTAN          | fptan                   |  87 30-540
                |                         | 287 30-540
                |                         | 387 191-497*
                |                         | 486 200-273œ
----------------+-------------------------+---------------------------------
  * For operands with an absolute value greater than pi/4, up to 76
    additional clocks may be required.

  œ For operands with an absolute value greater than pi/4, add n clocks
    where n = operand / (pi/4).
                                    -+-
.context FPREM
.context FPREM1
.context @L8039
:nFPREM/FPREM1 Instructions
:z3
:l40

----------------------------------------------------------------------------

  Title:    Partial Remainder

  Syntax:   FPREM
            FPREM1 (80387/487 only)

  Description:

     Calculates the remainder of ST divided by ST(1); the result is
     returned in ST. The remainder retains the same sign as the original
     dividend. The calculation uses the following formula:

          remainder = ST - ST(1) * quotient

     For FPREM, the quotient is the exact value obtained by chopping
     ST / ST(1) toward 0. For FPREM1, the quotient is the integer
     nearest to the exact value of ST / ST(1). When two integers are
     equally close to the given value, the even integer is used. FPREM1
     conforms to the IEEE standard.

     These instructions are normally used in a loop that repeats until
     the reduction is complete, as indicated by the condition codes of
     the status word.

     The condition codes for FPREM and FPREM1 are as follows:

     C3     C2     C1     C0     Meaning

     ?      1      ?      ?      Incomplete reduction
     0      0      0      0      Quotient MOD 8 = 0
     0      0      0      1      Quotient MOD 8 = 4
     0      0      1      0      Quotient MOD 8 = 1
     0      0      1      1      Quotient MOD 8 = 5
     1      0      0      0      Quotient MOD 8 = 2
     1      0      0      1      Quotient MOD 8 = 6
     1      0      1      0      Quotient MOD 8 = 3
     1      0      1      1      Quotient MOD 8 = 7
                                    -+-
.context @L803a
:z2
:nFPREM/FPREM1 Instructions
:l13

----------------+-------------------------+---------------------------------
 FPREM          | fprem                   |  87 15-190
                |                         | 287 15-190
                |                         | 387 74-155
                |                         | 486 70-138
----------------+-------------------------+---------------------------------
 FPREM1         | fprem1                  |  87 --
                |                         | 287 --
                |                         | 387 95-185
                |                         | 486 72-167
----------------+-------------------------+---------------------------------
                                    -+-
.context FRNDINT
.context @L803b
:nFRNDINT Instruction
:z3
:l14

----------------------------------------------------------------------------

  Title:    Round to Integer

  Syntax:   FRNDINT

  See also: FABS

  Description:

     Rounds ST from a real number to an integer. The rounding control
     (RC) field of the control word specifies the rounding method.
                                    -+-
.context @L803c
:z2
:nFRNDINT Instruction
:l8

----------------+-------------------------+---------------------------------
 FRNDINT        | frndint                 |  87 16-50
                |                         | 287 16-50
                |                         | 387 66-80
                |                         | 486 21-30
----------------+-------------------------+---------------------------------
                                    -+-
.context FSAVE
.context FNSAVE
.context FSAVEW
.context FSAVED
.context FNSAVEW
.context FNSAVED
.context FRSTOR
.context FRSTORW
.context FRSTORD
.context @L803d
:nFSAVE/FRSTOR Instructions
:z3
:l28

----------------------------------------------------------------------------

  Title:    Save or Restore Coprocessor State

  Syntax:   FSAVE dest
            FNSAVE dest
            FRSTOR src

  See also: FSTENV, FSTSW, FSTCW, Coprocessor Environment

  Description:

     FSAVE stores the 94-byte coprocessor state to a specified memory
     location; FRSTOR restores it from a specified memory location.

     In 32-bit mode on the 80387/486, the environment state takes 108
     bytes.

     FSAVE has wait and no-wait versions. The wait version (FSAVE)
     checks for unmasked numeric errors; the no-wait version (FNSAVE)
     does not. After saving the state, the coprocessor is initialized
     as if FINIT had been executed.

     When the .8087 directive is used, the assembler puts the WAIT
     instruction before the wait version and the NOP instruction before
     the no-wait version.
                                    -+-
.context @L803e
:z2
:nFSAVE/FRSTOR Instructions
:l23

----------------+-------------------------+---------------------------------
 FSAVE mem      | fsave  [bp-94]          |  87 (197-207)+EA
 FSAVEW mem*    | fsave  cobuffer         | 287 œ
 FSAVED mem*    |                         | 387 375-376
 FNSAVE mem     |                         | 486 154,pm=143
 FNSAVEW mem*   |                         |
 FNSAVED mem*   |                         |
----------------+-------------------------+---------------------------------
 FRSTOR mem     | frstor [bp-94]          |  87 (197-207)+EA
 FRSTORW mem*   |                         | 287 œ
 FRSTORD mem*   |                         | 387 308
                |                         | 486 131,pm=120
----------------+-------------------------+---------------------------------
  * 80387/486 only.

  œ Clock counts are not meaningful in determining overall execution
    time of this instruction. Timing is determined by operand
    transfers.

  NOTE: The timings reflect the no-wait versions of FSAVE. The wait
        versions may take additional clock cycles.
                                    -+-
.context FSCALE
.context @L803f
:nFSCALE Instruction
:z3
:l21

----------------------------------------------------------------------------

  Title:    Scale

  Syntax:   FSCALE

  See also: FSQRT

  Description:

     Scales by powers of 2 by calculating the function Y = Y * 2^X. X
     is the scaling factor taken from ST(1), and Y is the value to be
     scaled from ST. The scaled result replaces the value in ST; the
     scaling factor remains in ST(1). If the scaling factor is not an
     integer, it will be truncated toward zero before the scaling.

     On the 8087/287 processors, if X is not in the range -2^15 <= X < 2^15
     or if X is in the range 0 < X < 1, the result will be undefined. The
     80387/486 processors have no restrictions on the range of operands.
                                    -+-
.context @L8040
:z2
:nFSCALE Instruction
:l8

----------------+-------------------------+---------------------------------
 FSCALE         | fscale                  |  87 32-38
                |                         | 287 32-38
                |                         | 387 67-86
                |                         | 486 30-32
----------------+-------------------------+---------------------------------
                                    -+-
.context FSETPM
.context @L8041
:nFSETPM Instruction
:z3
:l18

----------------------------------------------------------------------------

  Title:    Set Protected Mode
            80287 Only

  Syntax:   FSETPM

  See also: .287

  Description:

     Sets the 80287 to protected mode. The instruction and operand
     pointers are in the protected-mode format after this instruction.
     On the 80387/486, FSETPM is recognized but interpreted as the FNOP
     instruction, since the 80386/486 processors handle addressing
     identically in real and protected modes.
                                    -+-
.context @L8042
:z2
:nFSETPM Instruction
:l8

----------------+-------------------------+---------------------------------
 FSETPM         | fsetpm                  |  87 --
                |                         | 287 2-8
                |                         | 387 12
                |                         | 486 3
----------------+-------------------------+---------------------------------
                                    -+-
.context FSQRT
.context @L8043
:nFSQRT Instruction
:z3
:l14

----------------------------------------------------------------------------

  Title:    Square Root

  Syntax:   FSQRT

  See also: FSCALE

  Description:

     Replaces the value of ST with its square root. The square root
     of -0 is -0.
                                    -+-
.context @L8044
:z2
:nFSQRT Instruction
:l8

----------------+-------------------------+---------------------------------
 FSQRT          | fsqrt                   |  87 180-186
                |                         | 287 180-186
                |                         | 387 122-129
                |                         | 486 83-87
----------------+-------------------------+---------------------------------
                                    -+-
.context FST
.context FSTP
.context FIST
.context FISTP
.context FBSTP
.context @L8045
:nFST Instruction
:z3
:l22

----------------------------------------------------------------------------

  Title:    Store

  Syntax:   FST dest
            FSTP dest
            FIST dest
            FISTP dest
            FBSTP dest

  Description:

     Stores the value in ST to the specified memory location or
     register. Temporary-real values in registers are converted to the
     appropriate integer, BCD, or floating-point format as they are
     stored. With FSTP, FISTP, and FBSTP, the ST register value is
     popped off the stack.

     Memory operands can be 32-, 64-, or 80-bit real numbers for FSTP,
     or 16-, 32-, or 64-bit integers for FISTP.
                                    -+-
.context @L8046
:z2
:nFST Instruction
:l38

---------------+-----------------------+------------------------------------
 FST reg       | fst    st(6)          |  87 15-22
               | fst    st             | 287 15-22
               |                       | 387 11
               |                       | 486 3
---------------+-----------------------+------------------------------------
 FSTP reg      | fstp   st             |  87 17-24
               | fstp   st(3)          | 287 17-24
               |                       | 387 12
               |                       | 486 3
---------------+-----------------------+------------------------------------
 FST memreal   | fst    shortreal      |  87 (s=84-90,l=96-104)+EA
               | fst    longs[bx]      | 287 s=84-90,l=96-104
               |                       | 387 s=44,l=45
               |                       | 486 s=7,l=8
---------------+-----------------------+------------------------------------
 FSTP memreal  | fstp   longreal       |  87 (s=86-92,l=98-106,t=52-58)+EA
               | fstp   tempreals[bx]  | 287 s=86-92,l=98-106,t=52-58
               |                       | 387 s=44,l=45,t=53
               |                       | 486 s=7,l=8,t=6
---------------+-----------------------+------------------------------------
 FIST memint   | fist   int16          |  87 (w=80-90,d=82-92)+EA
               | fist   doubles[8]     | 287 w=80-90,d=82-92
               |                       | 387 w=82-95,d=79-93
               |                       | 486 w=29-34,d=28-34
---------------+-----------------------+------------------------------------
 FISTP memint  | fistp  longint        |  87 (w=82-92,d=84-94,q=94-105)+EA
               | fistp  doubles[bx]    | 287 w=82-92,d=84-94,q=94-105
               |                       | 387 w=82-95,d=79-93,q=80-97
               |                       | 486 29-34
---------------+-----------------------+------------------------------------
 FBSTP membcd  | fbstp  bcds[bx]       |  87 (520-540)+EA
               |                       | 287 520-540
               |                       | 387 512-534
               |                       | 486 172-176
---------------+-----------------------+------------------------------------
                                    -+-
.context FSTSW
.context FNSTSW
.context @L8047
:nFSTSW Instruction
:z3
:l20

----------------------------------------------------------------------------

  Title:    Store Status Word

  Syntax:   FSTSW dest
            FNSTSW dest

  Description:

     Stores the status word to a specified 16-bit memory operand. On
     the 80287, 80387, and 80486, the status word can also be stored to
     the processor's AX register.

     FSTSW has wait and no-wait versions. The wait version (FSTSW)
     checks for unmasked numeric errors; the no-wait version (FNSTSW)
     does not. When the .8087 directive is used, the assembler puts the
     WAIT instruction before the wait version and the NOP instruction
     before the no-wait version.
                                    -+-
.context @L8048
:z2
:nFSTSW Instruction
:l16

----------------+-------------------------+---------------------------------
 FSTSW mem16    | fstsw  statword         |  87 12-18
 FNSTSW mem16   |                         | 287 12-18
                |                         | 387 15
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FSTSW AX       | fstsw  ax               |  87 --
 FNSTSW AX      |                         | 287 10-16
                |                         | 387 13
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.
                                    -+-
.context FSUB
.context FSUBP
.context FISUB
.context @L8049
:nFSUB Instruction
:z3
:l25

----------------------------------------------------------------------------

  Title:    Subtract

  Syntax:   FSUB [dest,src]
            FSUBP dest,ST
            FSUB src
            FISUB src

  See also: FSUBR, FADD

  Description:

     Subtracts the source from the destination and returns the
     difference in the destination. If two register operands are
     specified, one must be ST. If a memory operand is specified, the
     result replaces the value in ST. Memory operands can be 32- or
     64-bit real numbers or 16- or 32-bit integers.

     If no operand is specified, ST is subtracted from ST(1) and the
     stack is popped; the difference is returned in ST. For FSUBP, the
     source must be ST; the difference (destination minus source) is
     returned in the destination register and ST is popped.
                                    -+-
.context @L804a
:z2
:nFSUB Instruction
:l23

----------------+-------------------------+---------------------------------
 FSUB [reg,reg] | fsub   st,st(2)         |  87 70-100
                | fsub   st(5),st         | 287 70-100
                | fsub                    | 387 to=29-37,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBP reg,ST   | fsubp  st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUB memreal   | fsub   longreal         |  87 (s=90-120,s=95-125)+EA
                | fsub   shortreals[di]   | 287 s=90-120,l=95-125
                |                         | 387 s=24-32,l=28-36
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FISUB memint   | fisub  double           |  87 (w=102-137,d=108-143)+EA
                | fisub  warray[di]       | 287 w=102-137,d=108-143
                |                         | 387 w=71-83,d=57-82
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------
                                    -+-
.context FSUBR
.context FSUBRP
.context FISUBR
.context @L804b
:nFSUBR Instruction
:z3
:l25

----------------------------------------------------------------------------

  Title:    Subtract Reversed

  Syntax:   FSUBR [dest,src]
            FSUBRP dest,ST
            FSUBR src
            FISUBR src

  See also: FSUB, FADD

  Description:

     Subtracts the destination operand from the source operand and
     returns the result in the destination operand. If two register
     operands are specified, one must be ST. If a memory operand is
     specified, the result replaces the value in ST. Memory operands
     can be 32- or 64-bit real numbers or 16- or 32-bit integers.

     If no operand is specified, ST(1) is subtracted from ST and the
     stack is popped; the difference is returned in ST. For FSUBRP, the
     source must be ST; the difference (source minus destination) is
     returned in the destination register and ST is popped.
                                    -+-
.context @L804c
:z2
:nFSUBR Instruction
:l23

----------------+-------------------------+---------------------------------
 FSUBR [reg,reg]| fsubr  st,st(2)         |  87 70-100
                | fsubr  st(5),st         | 287 70-100
                | fsubr                   | 387 to=29-37,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBRP reg,ST  | fsubrp st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBR memreal  | fsubr  QWORD PTR [bx]   |  87 (s=90-120,s=95-125)+EA
                | fsubr  shortreal[di]    | 287 s=90-120,l=95-125
                | fsubr  longreal         | 387 s=25-33,l=29-37
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FISUBR memint  | fisubr int16            |  87 (w=103-139,d=109-144)+EA
                | fisubr warray[di]       | 287 w=103-139,d=109-144
                | fisubr double           | 387 w=72-84,d=58-83
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------
                                    -+-
.context FTST
.context @L804d
:nFTST Instruction
:z3
:l24

----------------------------------------------------------------------------

  Title:    Test for Zero

  Syntax:   FTST

  See also: FCOM, Status Word

  Description:

     Compares ST with +0.0 and sets the condition of the status word
     according to the result.

     The condition codes for FTST are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST is positive
     0      0      ?      1      ST is negative
     1      0      ?      0      ST is 0
     1      1      ?      1      ST is not comparable (NAN or
                                 projective infinity)
                                    -+-
.context @L804e
:z2
:nFTST Instruction
:l8

----------------+-------------------------+---------------------------------
 FTST           | ftst                    |  87 38-48
                |                         | 287 38-48
                |                         | 387 28
                |                         | 486 4
----------------+-------------------------+---------------------------------
                                    -+-
.context FUCOM
.context FUCOMP
.context FUCOMPP
.context @L804f
:nFUCOM Instruction
:z3
:l36

----------------------------------------------------------------------------

  Title:    Unordered Compare
            80387/486 Only

  Syntax:   FUCOM [src]
            FUCOMP [src]
            FUCOMPP

  See also: FCOM, FTST, Status Word

  Description:

     Compares the specified source to ST and sets the condition codes
     of the status word according to the result. The instruction
     subtracts the source operand from ST without changing either
     operand. Memory operands are not allowed.

     If no operand is specified or if two pops are specified, ST is
     compared to ST(1). If one pop is specified with an operand, the
     given register is compared to ST.

     Unlike FCOM, FUCOM does not cause an invalid-operation exception
     if one of the operands is a NAN. Instead, the condition codes are
     set to unordered.

     The condition codes for FUCOM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST > source
     0      0      ?      1      ST < source
     1      0      ?      0      ST = source
     1      1      ?      1      Unordered
                                    -+-
.context @L8050
:z2
:nFUCOM Instruction
:l18

----------------+-------------------------+---------------------------------
 FUCOM [reg]    | fucom  st(2)            |  87 --
                | fucom                   | 287 --
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FUCOMP [reg]   | fucomp st(7)            |  87 --
                | fucomp                  | 287 --
                |                         | 387 26
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FUCOMPP        | fucompp                 |  87 --
                |                         | 287 --
                |                         | 387 26
                |                         | 486 5
----------------+-------------------------+---------------------------------
                                    -+-
.context FWAIT
.context @L8051
:nFWAIT Instruction
:z3
:l15

----------------------------------------------------------------------------

  Title:    Wait

  Syntax:   FWAIT

  See also: WAIT

  Description:

     Suspends execution of the processor until the coprocessor is
     finished executing. This is an alternate mnemonic for the
     processor WAIT instruction.
                                    -+-
.context @L8052
:z2
:nFWAIT Instruction
:l8

----------------+-------------------------+---------------------------------
 FWAIT          | fwait                   |  87 4
                |                         | 287 3
                |                         | 387 6
                |                         | 486 1-3
----------------+-------------------------+---------------------------------
                                    -+-
.context FXAM
.context @L8053
:nFXAM Instruction
:z3
:l39

----------------------------------------------------------------------------

  Title:    Examine

  Syntax:   FXAM

  See also: Status Word

  Description:

     Reports the contents of ST in the condition flags of the status
     word.

     The condition codes for FXAM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      0      0      + Unnormal*
     0      0      0      1      + NAN
     0      0      1      0      - Unnormal*
     0      0      1      1      - NAN
     0      1      0      0      + Normal
     0      1      0      1      + Infinity
     0      1      1      0      - Normal
     0      1      1      1      - Infinity
     1      0      0      0      + 0
     1      0      0      1      Empty
     1      0      1      0      - 0
     1      0      1      1      Empty
     1      1      0      0      + Denormal
     1      1      0      1      Empty*
     1      1      1      0      - Denormal
     1      1      1      1      Empty*

     * Not used on the 80387/486 processors. Unnormals are not supported by
       the 80387/486. Also, the 80387/486 processors use two codes instead
       of four to identify empty registers.
                                    -+-
.context @L8054
:z2
:nFXAM Instruction
:l8

----------------+-------------------------+---------------------------------
 FXAM           | fxam                    |  87 12-23
                |                         | 287 12-23
                |                         | 387 30-38
                |                         | 486 8
----------------+-------------------------+---------------------------------
                                    -+-
.context FXCH
.context @L8055
:nFXCH Instruction
:z3
:l14

----------------------------------------------------------------------------

  Title:    Exchange Registers

  Syntax:   FXCH [dest]

  See also: FLD

  Description:

     Exchanges the specified (destination) register and ST. If no
     operand is specified, ST and ST(1) are exchanged.
                                    -+-
.context @L8056
:z2
:nFXCH Instruction
:l8

----------------+-------------------------+---------------------------------
 FXCH [reg]     | fxch   st(3)            |  87 10-15
                | fxch                    | 287 10-15
                |                         | 387 18
                |                         | 486 4
----------------+-------------------------+---------------------------------
                                    -+-
.context FXTRACT
.context @L8057
:nFXTRACT Instruction
:z3
:l13

----------------------------------------------------------------------------

  Title:    Extract Exponent and Significand

  Syntax:   FXTRACT

  Description:

     Extracts the exponent and significand (mantissa) fields of ST. The
     exponent replaces the value in ST, and then the significand is
     pushed onto the stack.
                                    -+-
.context @L8058
:z2
:nFXTRACT Instruction
:l8

----------------+-------------------------+---------------------------------
 FXTRACT        | fxtract                 |  87 27-55
                |                         | 287 27-55
                |                         | 387 70-76
                |                         | 486 16-20
----------------+-------------------------+---------------------------------
                                    -+-
.context FYL2X
.context @L8059
:nFYL2X Instruction
:z3
:l16

----------------------------------------------------------------------------

  Title:    Y log(sub2)(X)

  Syntax:   FYL2X

  See also: FYL2XP1, F2XM1

  Description:

     Calculates Z = Y log(sub2)(X) where X is taken from ST and Y from
     ST(1). The stack is popped and the result, Z, replaces Y in ST. X
     must be in the range 0 < X < infinity and Y in the range
     minus infinity < Y < infinity.
                                    -+-
.context @L805a
:z2
:nFYL2X Instruction
:l8

----------------+-------------------------+---------------------------------
 FYL2X          | fyl2x                   |  87 900-1100
                |                         | 287 900-1100
                |                         | 387 120-538
                |                         | 486 196-329
----------------+-------------------------+---------------------------------
                                    -+-
.context FYL2XP1
.context @L805b
:nFYL2XP1 Instruction
:z3
:l16

----------------------------------------------------------------------------

  Title:    Y log(sub2)(X+1)

  Syntax:   FYL2XP1

  See also: FYL2X, F2XM1

  Description:

     Calculates Z = Y log(sub2)(X + 1) where X is taken from ST and Y
     from ST(1). The stack is popped once and the result, Z, replaces Y
     in ST. X must be in the range 0 <= |X| < (1 - (SQRT2 / 2)) and Y in
     the range minus infinity < Y < infinity.
                                    -+-
.context @L805c
:z2
:nFYL2XP1 Instruction
:l8

----------------+-------------------------+---------------------------------
 FYL2XP1        | fyl2xp                  |  87 700-1000
                |                         | 287 700-1000
                |                         | 387 257-547
                |                         | 486 171-326
----------------+-------------------------+---------------------------------
                                    -+-
:l2


Simply FPU - Intro

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

CONTENTS

            Introduction

Chap. 1     Description of FPU Internals

Chap. 2     Data types used by the FPU and addressing modes

Chap. 3     Instructions related to the FPU internals

Chap. 4     Data transfer instructions - REAL numbers

Chap. 5     Data transfer instructions - integers

Chap. 6     Data transfer instructions - packed decimals

Chap. 7     Comparison instructions

Chap. 8     Arithmetic instructions - with REAL numbers

Chap. 9     Arithmetic instructions - with integer numbers

Chap. 10    Trigonometric instructions

Chap. 11    Logarithmic and exponential instructions

Chap. 12    Other instructions

Chap. 13    Commented example

Appendix 1  Alphabetical list of FPU instructions

INTRODUCTION

The FPU, also known as a co-processor, used to be an option when the first PCs came on the market. Modern PCs are now all provided with a co-processor. Although the original PC-XT has evolved considerably over the years, the FPU itself has not changed appreciably during that same period. Apart from a few minor instructions having been added, the main improvement has been the extended range of some of the existing instructions.

The entire set of assembler instructions for the FPU is relatively small and could be memorized rapidly. Learning which parameter(s) can be used with each instruction should take only slightly longer. The main difficulty is in developing sound programming techniques to avoid some of the pitfalls peculiar to the FPU.

The main purpose of this document is to show that using the FPU can be relatively easy with minimal effort. Once the basics have been mastered, any computation however complex can be performed.

The first chapter describes the internals of the FPU and how they function. Some of this knowledge is an absolute necessity to use the FPU properly or help later in understanding the outcome of some of the instructions. This is followed by a chapter describing the various data types which can be used with some of the instructions, including a detailed review of the floating point data formats.

The FPU instructions are then discussed in detail in the following chapters which regroup them according to some "biased" criteria. Although some description of each instruction is available in the Fphelp.hlp file provided with MASM32, a more in-depth review is provided with more tangible examples of their usage.

The final chapter provides a fully commented example of a semi-complex computation.

It should be noted that this document has been prepared according to the syntax used by MASM. Although most other assemblers/compilers should have a similar syntax for the FPU instruction mnemonics, their syntax may differ for the addressing mode of memory variables. It is also known that some of the instructions used without explicit parameters are not supported by some of the other assemblers/compilers.

Any comment or suggestion aimed at improving this document will be given due consideration. Those can be sent to:

rayfil@hotmail.com

Please use "Simply FPU" in the subject line to help its retrieval from the ever increasing SPAM.


Simply FPU Chap.1

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 1
Description of FPU Internals

The FPU simply has eight identical 80-bit registers and three 16-bit registers (Control Word, Status Word and Tag Word) accessible to the programmer. It also has an internal flag register which is not accessible to the programmer.

80-bit Registers

The 80-bit registers are generally designated in most literature as a stack of eight registers. To better understand how these 80-bit registers function, instead of imagining them as a stack, it will be easier to imagine them as a revolver barrel with 8 compartments numbered clock-wise from 0 to 7. When the FPU is initialized, all the compartments are empty and Barrel Compartment #0 (BC0) would be at the 12 o'clock position (at the TOP), as depicted in Fig.1.1 below.

Note: These Barrel Compartment (BC) numbers are strictly for internal use by the FPU. They are used to identify each compartment as a physical unit similarly to the EAX, EBX, etc. registers in the CPU. (There is NO revolving barrel nor any stack as such in the FPU.)

When the FPU would be instructed to LOAD a value, it would turn the barrel clockwise by one notch and load the specified value in the top compartment. The first value loaded immediately after the FPU is initialized would thus go into BC7 according to the FPU's internal numbering system.

If the FPU would be instructed to load another value while the first one is still in BC7, it would again turn the barrel clockwise by one notch and load the specified value again in the top compartment, which would now be BC6.

Values can be loaded only into the TOP compartment of the FPU

This could continue until all the compartments contain a value. If, however, an attempt is made to load a value when all the compartments have a value in them, the barrel would still turn by one notch but the attempted loading would fail (just like trying to insert a bullet into a compartment which already contains one). And, in addition, whatever valid value would have been in that compartment now at the TOP is also destroyed, leaving unusable trash in that register at the TOP.

Rule #1: An FPU 80-bit register compartment MUST be free (empty) in order to load a value into it.

Quite fortunately, these registers can be emptied with various FPU instructions. The most common way is generally referred to as "popping a register". The "pop" mnemonic used for the CPU is not available for the FPU. Instead, it can be included as a part of numerous FPU instructions; such instruction would be carried out normally and then immediately followed by popping the register at the TOP.

When the FPU is instructed to POP a value, it would first remove it from whichever compartment would currently be at the TOP and then turn the barrel counter-clockwise by one notch. For example, if BC6 would be at the TOP and popped, BC7 would then become the register compartment at the TOP.

Values can be popped only from the TOP compartment of the FPU

Those BC numbers are never used directly by the programmer. The FPU takes care of remembering where all the 80-bit values are located in its internals and which of its compartments is at the TOP. However, the programmer must remain aware of this internal numbering system.

For the programmer, while still using the revolving barrel image, the 80-bit registers are ALWAYS numbered clockwise from 0 to 7 starting from the TOP. The numbers shown in Fig.1 above would therefore never change for referring to register numbers in FPU instructions. The register at the TOP would always have the number 0.

The designation ST with its number in parenthesis (such as ST(0), ST(1), etc.) is used when reference to a given 80-bit register is required in an FPU instruction. (MASM also interprets ST without any explicit number as if ST(0) had been specified.)

Any value loaded to the FPU must initially be referred to as ST(0) because it can only be loaded to the TOP compartment. If the FPU would be instructed to load another value while the first one is still there, that second value would now be referred to as ST(0) because it has now become the one at the TOP. As a consequence, the first value would now have to be referred to as ST(1). If another value is loaded, the first value would then have to be referred to as ST(2). After popping the last loaded value, that same first value would revert back to being referred to as ST(1).

That is probably the most complex concept to understand by someone starting to learn how to use the FPU. When compared to the CPU where a value in EAX would always be referred to as EAX regardless of operations on the other registers, a value in an FPU register must be referred to according to its position relative to the register at the TOP.

Rule #2: The programmer must constantly keep track of the relative location of the existing register values while other values may be loaded to or popped from the TOP register.

A good programming practice is to insert a comment after each FPU instruction which can affect the location of register values, indicating the new ST number of each value.

When a register is popped from the FPU, its current value can no longer be used in any operation. If that value would need to be used later, it should be stored in memory before popping it and reloaded when required. (Some debuggers may still show the old value in the popped register but that should only be considered as residual "gun powder".)

16-bit Registers

The three 16-bit registers available to the programmer are the control word, the status word and the tag word.

Control Word

The Control Word 16-bit register is used by the programmer to select between the various modes of computation available from the FPU, and to define which exceptions should be handled by the FPU or by an exception handler written by the programmer.

The Control Word is divided into several bit fields as depicted in the following Fig.1.2.

The IC field (bit 12) or Infinity Control allows for two types of infinity arithmetic:

0 = Both -infinity and +infinity are treated as unsigned infinity (initialized state)
1 = Respects both -infinity and +infinity

This field has been retained for compatibility with the 287 and earlier co-processors. In the more modern FPUs, this bit is disregarded and both -infinity and +infinity are respected.

The RC field (bits 11 and 10) or Rounding Control determines how the FPU will round results in one of four ways:

00 = Round to nearest, or to even if equidistant (this is the initialized state)
01 = Round down (toward -infinity)
10 = Round up (toward +infinity)
11 = Truncate (toward 0)

The PC field (bits 9 and 8) or Precision Control determines to what precision the FPU rounds results after each arithmetic instruction in one of three ways:

00 = 24 bits (REAL4)
01 = Not used
10 = 53 bits (REAL8)
11 = 64 bits (REAL10) (this is the initialized state)

The IEM field (bit 7) or Interrupt Enable Mask determines whether any of the interrupt masks will be enabled (bit = 0) or all those masks will be disabled (bit = 1). This bit field is set to 1 in the initialized state. (This field is also for compatibility with early co-processors and not used anymore.)

Bits 5-0 are the interrupt masks. In the initialized state, they are all set to 1 which lets the FPU handle all exceptions. When any one of them is set to 0, it instruct the FPU to generate an interrupt whenever that particular exception is detected so that the program will take whatever action may be deemed necessary before returning control to the FPU.

The various interrupt masks available are:

PM (bit 5) or Precision Mask
UM (bit 4) or Underflow Mask
OM (bit 3) or Overflow Mask
ZM (bit 2) or Zero divide Mask
DM (bit 1) or Denormalized operand Mask
IM (bit 0) or Invalid operation Mask

(A more detailed description of the various exceptions and how the FPU would normally handle them is given in the following section. This document will not describe how interrupts are generated and transmitted nor how to respond to such interrupts.)

Bits 15-13 and 6 are reserved or unused.

See the FSTCW and the FLDCW instructions for details on how to access the Control Word register.

Status word

The Status Word 16-bit register indicates the general condition of the FPU. Its content may change after each instruction is completed. Part of it cannot be changed directly by the programmer. It can, however, be accessed indirectly at any time to inspect its content.

The Status Word is divided into several bit fields as depicted in the following Fig.1.3.

When the FPU is initialized, all the bits are reset to 0.

The B field (bit 15) indicates if the FPU is busy (B=1) while executing an instruction, or is idle (B=0).

The C3 (bit 14) and C2 - C0 (bits 10-8) fields contain the condition codes following the execution of some instructions such as comparisons. These codes will be explained in detail for each instruction affecting those fields.

The TOP field (bits 13-11) is where the FPU keeps track of which of its 80-bit registers is at the TOP. The BC numbers described previously for the FPU's internal numbering system of the 80-bit registers would be displayed in that field. When the programmer specifies one of the FPU 80-bit registers ST(x) in an instruction, the FPU adds (modulo 8) the ST number supplied to the value in this TOP field to determine in which of its registers the required data is located.

The IR field (bit 7) or Interrupt Request gets set to 1 by the FPU while an exception is being handled and gets reset to 0 when the exception handling is completed. When the interrupt is masked in the Control Word for the FPU to handle the exception, this bit may never be seen to be set while stepping through the instructions with a debugger. However, if the programmer handles the interrupt, that bit should remain set until the interrupt handling routine is completed.

Bits 6-0 are flags raised by the FPU whenever it detects an exception. Those exception flags are cumulative in the sense that, once set (bit=1), they are not reset (bit=0) by the result of a subsequent instruction which, by itself, would not have raised that flag. Those flags can only be reset by either initializing the FPU (FINIT instruction) or by explicitly clearing those flags (FCLEX instruction).

The SF field (bit6) or Stack Fault exception is set whenever an attempt is made to either load a value into a register which is not free (the C1 bit would also get set to 1) or pop a value from a register which is free (and the C1 bit would get reset to 0). (Such stack fault is also treated as an invalid operation and the I field flag bit0 would thus also be set by this exception; see below.)

The P field (bit5) or Precision exception is set whenever some precision is lost by instructions which do exact arithmetic. For example, dividing 1 by 10 does not yield an exact value in binary arithmetic and would set the P exception flag. Another example which sets the P exception flag would be the conversion of a REAL10 to a REAL4 when some of the least significant bits would be lost.
If the FPU handles this exception (when the PM bit is set in the Control Word), it rounds the result according to the rounding mode specified in the RC field of the Control Word.

The U field (bit4) or Underflow exception flag gets set whenever a value is too small (without being equal to 0) to be represented properly. Each of the floating point formats has a different limit on the smallest number which can be represented. The U flag gets set if the result of an operation exceeds that limit. For example, dividing a valid very small number by a large number could exceed the limit. A valid REAL10 small number may be much smaller than acceptable for the REAL4 or REAL8 formats; in such cases, conversion from the former to the latter would also set the U flag.
If the FPU handles this exception (when the UM bit is set in the Control Word), it would denormalize the value until the exponent is in range or ultimately return a 0.

The O field (bit3) or Overflow exception flag gets set whenever a value is too large in magnitude to be represented properly. Again, each of the floating point formats has a different limit on the largest number which can be represented. The O flag gets set if the result of an operation exceeds that limit. For example, multiplying a valid very large number by another large number could exceed the limit. A valid REAL10 large number may be much larger than acceptable for the REAL4 or REAL8 formats; conversion from the former to the latter would also set the O flag.
If the FPU handles this exception (when the OM bit is set in the Control Word), it would generate a properly signed INFINITY according to the IC flag of the Control Word.

The Z field (bit2) or Zero divide exception flag gets set whenever the division of a finite non-zero value by 0 is attempted.
If the FPU handles this exception (when the ZM bit is set in the Control Word), it would generate a properly signed INFINITY according to the XOR of the operand signs and then according to the IC flag of the Control Word.

The D field (bit1) or Denormalized exception flag gets set whenever an instruction attempts to operate on a denormalized number or the result of the operation is a denormalized number.
If the FPU handles this exception (when the DM bit is set in the Control Word), it would simply continue with normal processing and then check for other possible exceptions.

The I field (bit0) or Invalid operation exception flag gets set whenever an operation is considered invalid by the FPU. Examples of such operations are:
- Stack overflow or underflow
- Indeterminate arithmetic such as 0 divided by 0, or subtracting infinity from infinity
- Using a Not-A-Number (NAN) as an operand with some instructions
- Trying to extract the square root of a negative number

If the FPU handles this exception (when the IM bit is set in the Control Word), it would either return the NAN if one was used as an operand (or the larger absolute value if two NANs were used as operands), or otherwise return a special "INDEFINITE" NAN.

See the FSTSW instruction for details on how to access the Status Word register.

Tag Word

The Tag Word 16-bit register is managed by the FPU to maintain some information on the content of each of its 80-bit registers.

The Tag Word is divided into 8 fields of 2 bits each as depicted in the following Fig.1.4.

The above Tag numbers correspond to the FPU's internal numbering system for the 80-bit registers (the BC numbers). The meaning of each pair of bits is as follows:

00 = The register contains a valid non-zero value
01 = The register contains a value equal to 0
10 = The register contains a special value (NAN, infinity, or denormal)
11 = The register is empty

When the FPU is initialized, all the 80-bit registers are empty and the Tag Word would thus have an overall value of
1111111111111111b (FFFFh).

If a valid non-zero value is then loaded, the Tag Word would then be change to
0011111111111111b (3FFFh). (Remember that the very first value loaded goes into BC7.)

If a second value equal to 0 was then loaded, the Tag Word would become
0001111111111111b (1FFFh). (And the second value loaded goes into BC6.)

Although this Tag Word may contain information which could also be useful to the programmer, it cannot be accessed directly nor by itself. The only way to gain access to it is to store the FPU's environment data in memory (see the FSTENV instruction) and examine it there. However, the information available in the Tag Word could also be obtained otherwise (such as with the FXAM instruction for individual registers).

Internal flag register

The FPU also has an internal exception flag register which is not accessible to the programmer. All these flags are cleared before each instruction and are set as each exception is encountered. Those are the flags that trigger a response from the FPU or an interrupt for the programmer's exception handler. They are also OR'ed with the exception flags of the Status Word to provide a cumulative record for the programmer.

It is possible that several of the flags could be set with a single instruction. For example, using a denormal number as an operand would set the Denormal flag. The result of the operation with it could then set the Underflow flag and the Precision flag.

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 2

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 2
Data types used by the FPU and addressing modes

(Reminder: Addressing mode syntax in this document is that of the MASM assembler. The syntax may vary with other assemblers/compilers for the described addressing modes and should be modified as required.)

There are three general data types which can be used by the FPU: integer, floating point and packed BCD.

INTEGER DATA TYPES

In the context of FPU operations, integers are whole numbers, i.e. numbers which do not contain any fractional part. All integers used in FPU instructions are also considered as signed integers, the most significant bit being 0 for positive values or 1 for negative values.

Negative integer values are represented by taking the 2's complement of the positive value and adding 1 (2's complements are obtained simply by inverting each bit of the number). As a refresher, the following example would be for a decimal value of 6235 in a 16-bit WORD.

      0001 1000 0101 1011   185Bh   +6235d
      1110 0111 1010 0100   2's complement
     +                  1
      -------------------
      1110 0111 1010 0101   E7A5h   -6235d

Within the integer data types, three sizes of integers may be used: the 16-bit WORD, the 32-bit DWORD, and the 64-bit QWORD, (the 8-bit byte cannot be used with FPU instructions). The available range of values for each of those sizes is as follows:

WORD range    ±(215-1)  or  ±32767
DWORD range   ±(231-1)  or  ±2147483647
QWORD range   ±(263-1)  or  ±9223372036854775807

Addressing modes of integer numbers

All integer data used with FPU instructions can only be accessed through memory locations. The actual code generated by the assembler is different for each of the allowed integer sizes.

Integer values in memory can be specified by any of the acceptable addressing modes. For example, if a memory variable (global or local) has been declared as a WORD, the variable's name (whether it is indexed or not) is sufficient to get it treated as the declared size. However, if CPU registers are used as pointers to data in memory, it is imperative that the index be qualified as pointing to the appropriate size. Examples of referring to memory data are:

var_name ;the value of var_name being treated according to how that variable had been declared as a WORD, DWORD or QWORD

var_name[24] ;value starting at the 24th byte of the var_name variable according to how it had been declared

var_name[ebx] ;value starting at the EBX displacement in bytes of the var_name variable according to the above

word ptr [eax] ;informs the processor that EAX points to a 16-bit value

dword ptr [esi+12] ;ESI points to an array of 32-bit values

qword ptr [edi+ebx] ;EDI or EBX points to an array of 64-bit values

dword ptr [ebp+8] ;typical coding for pushed parameters of procedures when coded by the assembler

The CPU registers cannot be used directly as the source or destination of integer data related to the FPU (as opposed to using them as pointers to the location of data in memory). If the need should arise to use the actual value in one of the CPU registers as the operand for data in an FPU instruction, the following is suggested (the 16-bit signed value in AX being used for this example):

  push  ax              ;copies the value in AX to the stack or
                        ;prepares the stack for storing a value
  fixxx word ptr [esp]  ;fixxx being one of the FPU instructions
                        ;operating on integer data
  pop ax                ;restores the stack or
                        ;retrieves in AX the integer value stored by the FPU

For numerous reasons, the popped CPU register in the above example could be different from the one which was pushed. Several other CPU and/or FPU instructions could also be inserted between the push/pop instructions. And, an FWAIT instruction may need to precede the pop instruction if its purpose is to retrieve a value stored by the FPU.

Code similar to the above could be used for DWORD values in 32-bit registers.

For QWORD values, such as obtained from a signed multiplication with the result in the EDX/EAX register pair, the sequence would need to be:

    push edx
    push eax
    fild qword ptr [esp]
    pop eax
    pop edx

FLOATING POINT DATA TYPES

The floating point data types are simply binary numbers represented in a manner similar to the scientific notation used for decimal values. For example:

211 = 2.11 x 102 (2.11E+0002)

(The latter is the conventional syntax for decimal values in scientific notation when superscripts are not allowed in a text. For instance, most assemblers/compilers would not recognize superscripts.)

If the above is divided by a multiple of 10 such as 100000, the only thing which would change in the scientific notation would be the exponent:

211 ÷ 100000 = 0.00211 = 2.11 x 10-3 (2.11E-0003)

In binary, the 211 value could be expressed as:

11010011 = 1.1010011 x 27

In this case, if the above is divided by a multiple of 2 (such as 8), again the only thing which would change in the "binary scientific notation" would be the exponent:

11010011 ÷ 23 = 1.1010011 x 24

As can be deduced, this allows for the representation of binary fractions, and of very large or very small values. The formatting of this "binary scientific notation" was standardized for the original CPUs and is usually called the IEEE (Institute of Electrical and Electronics Engineers) real number format.

This real number format consists basically in dividing a binary numerical data into three fields: a sign field, an exponent field, and a number description (significand) field. The exponent field is biased to the middle of the available range such that negative exponents are effectively smaller than positive exponents. And, as opposed to the negative integer system of 2's complements, the significand field is always that of the positive number, negative numbers being distinguished strictly by the sign field.

Within the floating point data types, three sizes of real numbers are available:

the 32-bit REAL4 (also called short real or single precision),
the 64-bit REAL8 (also called long real or double precision),
the 80-bit REAL10 (also called temporary real or extended precision).

The REAL4 floating point number has the following format.

where:
S = sign bit (0=positive, 1=negative)
En = biased exponent bits
fn = fraction bits of the significand

For REAL4 numbers, the bias of the 8 exponent's bits is 7Fh (the last 7 bits). This means that if the real exponent is 0, the value of the exponent field would be 7Fh. When the exponent is negative (i.e. for absolute values lower than 1), the value in the exponent field would be lower than 7Fh, and vice versa for values of 2 and higher.

The maximum value of FFh in the exponent field is reserved for a special category of numbers designated as NAN (Not-A-Number). This category includes the special value of INFINITY and will be described later in more details.

The value of 0 in the exponent field is also reserved for a special category of numbers. When all bits in the significand field are also 0, the value of the REAL number would be equal to 0. If any of the bits in the significand field are set, the value is then called a "denormalized" REAL number. This will also be described later in more details.

Because a valid number in real format must always start with a 1, that first bit is implied in the REAL4 format and the significand field only contains the fraction bits f1, f2, etc. A value of +1.0 would thus be represented in REAL4 format as:

0 01111111 00000000000000000000000b (or 3F800000h in hex notation)
(Spaces are left in the binary representation to delineate the 3 fields.

The value of +2.0 (1.0 x 21) would be:

0 10000000 00000000000000000000000b (or 40000000h in hex notation)
S   7Fh+1    fraction bits

And the value of -2.0 would be:

1 10000000 00000000000000000000000b (or C0000000h in hex notation)

The result of dividing -211 by 8 would give -1.1010011 x 24 in binary scientific format and its REAL4 representation would be:

1 10000011 10100110000000000000000 (or C1D30000h in hex notation)

S   7Fh+4    fraction bits

As with all other numerical data, all REAL numbers are stored in memory with the least significant bytes first. The value of +1.0 in REAL4 format would thus appear in consecutive bytes of memory as:

00 00 80 3F

The largest number which can be represented properly within the REAL4 format is when the exponent field contains FEh and the significand is almost equal to 2 (or almost 280h =2128d or approx. 3.40x1038). The smallest one would be when the exponent field contains 1 and the significand contains all 0s (or 2-7Eh =2-126d or approx. 1.17x10-38).

The 24 bits describing the number (23 bits in the significand field + 1 implied bit) is approximately equivalent to 7 decimal digits.

The REAL8 floating point number has the following format.

For REAL8 numbers, the bias of the 11 exponent's bits is 3FFh (the last 10 bits). The maximum value of 7FFh in the exponent field is reserved for NANs, and the value of 0 in that field has the same purpose as described for the REAL4 format.

As with the REAL4 format, the first bit of the number is implied and the significand field only contains the fraction bits f1, f2, etc. A value of +1.0 would thus be represented in REAL8 format as:

0 01111111111 0000000000000000000000000000000000000000000000000000b
(or 3FF0000000000000h in hex notation).

The largest number which can be represented properly within the REAL8 format is when the exponent field contains 7FEh and the significand is almost equal to 2 (or almost 2400h =21024d or approx. 1.79x10308). The smallest one would be when the exponent field contains 1 and the significand contains all 0s (or 2-3FEh =2-1022d or approx. 2.22x10-308).

The 53 bits describing the number (52 bits in the significand field + 1 implied bit) is approximately equivalent to 15 decimal digits.

The REAL10 floating point number has the following format. That is the format used by the FPU's 80-bit data registers. (Real numbers in this format can be stored in memory but must be present in one of the FPU data registers in order to use them with other FPU instructions.)

For REAL10 numbers, the bias of the 15 exponent's bits is 3FFFh (the last 14 bits). The maximum value of 7FFFh in the exponent field is reserved for NANs, and the value of 0 in that field has the same purpose as described for the REAL4 format.

As opposed to the REAL4 and REAL8 formats, the first bit of the number is explicitly included in the significand field and followed by the fraction bits f1, f2, etc. A value of +1.0 would thus be represented in REAL10 format as:

0 011111111111111 10000...........0b
(or 3FFF1000000000000000h in hex notation).

The largest number which can be represented properly within the REAL10 format is when the exponent field contains 7FFEh and the significand is almost equal to 2 (or almost 24000h =216384d or approx. 1.19x104932). The smallest one would be when the exponent field contains 1 and the significand's fraction bits contains all 0s (or 2-3FFEh =2-16382d or approx. 3.36x10-4932).

The 64 bits of the significand describing the number is approximately equivalent to 19 decimal digits.

Addressing modes of real numbers

Floating point values in memory can be declared as REA4, REAL8 or REAL10, but can also be declared simply as DWORD, QWORD or TBYTE respectively. When the latter is used and the variable is initialized, it would be assembled as a floating point value if the initializing value is in the scientific decimal format or contains at least one integer digit, a decimal point and at least one decimal digit. (It would be assembled as an integer if no decimal point is present.)

When a floating point memory variable has been declared as one of the above, the variable's name (whether it is indexed or not) is sufficient to get it treated as the declared size. Examples given for declared integer variables would also apply for floating point variables. Although integer variables can also have identical sizes, specific FPU instructions are provided for each data type; there could never be any confusion for the assembler.

Rule #3: The proper instruction must be used with the proper data type

This same rule also applies when using indirect indexing for floating point values. When using CPU registers as pointers to floating point data in memory, it is imperative that the index be qualified as pointing to the appropriate size. Examples of using pointers to floating point data in memory when used with the proper FPU instructions are:

dword ptr [eax] ;informs the processor that EAX points to a REAL4 value

dword ptr [esi+12] ;ESI would point to an array of REAL4 values

qword ptr [edi+ebx] ;EDI or EBX points to an array of REAL8 values

tbyte ptr [edx] ;EDX points to a REAL10 value

dword ptr [ebp+8] ;typical coding for pushed REAL4 parameters of procedures when coded by the assembler

Floating point values in the FPU's data registers can also be accessed with numerous FPU instructions. Since those are always 80-bit values, there is obviously no need to specify their size. As indicated in the previous chapter, their addressing mode is simply:

ST(0), ST(1), ...., ST(7)

NANs (Not-A-Number)

Whenever all the bits are set to 1 in the exponent field of a real number format, the value is designated as a NAN. Two values in that category are generated by the FPU:
INFINITY and INDEFINITE.

INFINITY

In addition to the exponent field bits being all set to 1, the value of INFINITY has the following special coding to differentiate it from other NANs:

All fraction bits of the significand field are 0 (the explicit 1 in bit 63 remains set for the REAL10 format). In addition,
when the sign bit is 0, that NAN is treated as +INFINITY
when the sign bit is 1, that NAN is treated as -INFINITY

Such values of INFINITY are generated by the FPU when

- attempting to divide a valid number by 0 (Zero divide exception detected)

- the result of a computation exceeds the maximum value allowable (Overflow exception detected)

- instructed to store a value larger than the upper limit of the destination format (Overflow exception detected).

This INFINITY value can be used as an operand in FPU instructions. Depending on the instruction, the result can vary and exceptions may or may not be detected.

INDEFINITE

In addition to the exponent field bits being all set to 1, the value of INDEFINITE has the following special coding to differentiate it from other NANs:

The 1st fraction bit of the significand field (f1) is set to 1, all other fraction bits being 0 (the explicit 1 in bit 63 remains set for the REAL10 format), and the sign bit is set.

Such a value of INDEFINITE is generated by the FPU whenever a reasonable result is impossible for the given instruction. An Invalid exception is detected in some cases. Examples are:

- using the value of INDEFINITE as an operand

- using an empty register as an operand

- subtracting two values of INFINITY

- extracting the square root of a negative number.

Other NANs

Apart from the INFINITY and INDEFINITE values which can be generated by the FPU, there is a very large number of other NANs with all the possible permutations of fraction bits and sign bit being set to 1 when all the bits in the exponent field are set to 1. For example, the short REAL4 format could have over 16 million of them (224-3 to be more exact).

There are two general categories of other NANs, the QNANs (Quiet NAN) and the SNANs (Signaling NAN). The difference betwen the two is that the first fraction bit is 1 for the QNAN (such as for the special INDEFINITE NAN) and 0 for the SNAN (but with at least one other fraction bit set to 1).

Although NANs could be used as valid operands with some of the FPU instructions, they are of no practical use for the average programmer.

Denormalized REAL numbers

The lowest value of the exponent field in a real number format is 1 if the number is to be considered "normal". Any further reduction of that small number may cause the value in the exponent field to become 0.

Although the exponent field cannot be lower than 0, some numbers smaller than the smallest normal one can still be expressed when the exponent field becomes 0. The FPU does this by shifting the fraction bits to the right (along with the implicit or explicit 1) with some loss of precision due to the loss of the right-most bits. Those numbers are then qualified as denormalized.

This concept should be easier to visualize with an example using a REAL4 small number, the smallest one being 1x2-7Eh for that format.

The result of dividing the previously used decimal value of 211 by 286h would be:

1.1010011 x 27 ÷ 286h = 1.1010011 x 2-7Eh which, in REAL4 format, would still be in the "normal number" range:

0 00000001 10100110000000000000000 (or 00D30000h in hex notation).
Dividing the above by 2 would now yield the following denormalized number:
0 00000000 11010011000000000000000 (or 00698000h in hex notation).
Dividing it again by 2 would give:
0 00000000 01101001100000000000000 (or 0034C000h in hex notation).
By repeating such division, the result would eventually become:
0 00000000 00000000000000000000001 (or 00000001h in hex notation).
It should now have become obvious that denormalized numbers suffer a loss of precision as they get smaller until they eventually reach a value of zero. Although these denormalized numbers can be used by the FPU with all its instructions, this potential loss of precision must be fully understood by the programmer. Using floating point maths is not by itself a guarantee of accuracy.

For maximum accuracy, intermediate results of computations should never be stored in the REAL4 or REAL8 formats unless there is absolute certainty that the "normal number" limits of those formats would not be exceeded.

PACKED BCD DATA TYPE

The Packed BCD (Binary Coded Decimal) data type is considered by the FPU as a signed integer and has the following 80-bit special packed decimal format.

where:
S = sign bit (0=positive, 1=negative)
dn = 4-bit decimal values, d0 being the least significant
(bits 72-78 are not used and ignored)

For example, the decimal value 211 in this data type format would be:

00000000000000000211h in hex notation
The decimal value of -65536 (-216) in this data type format would be:
80000000000000065536h in hex notation
As with all other numerical data, the packed BCD format is stored in memory with the least significant bytes first. The consecutive memory bytes (in hex notation) of the above number would thus be:

36 55 06 00 00 00 00 00 00 80

As depicted, 18 decimal digits is the maximum which can be inserted in this format. The largest integer which could be represented in this format would thus be 18 consecutive 9 (or 1018-1).

Addressing modes of packed BCD numbers

Packed BCD variables in memory can be declared only as TBYTE. Such variable is rarely, if ever, initialized. Its purpose is usually to reserved a memory space to either convert decimal strings into a format which will be acceptable to the FPU, or to instruct the FPU where to store a number after converting it to this format.

Only two FPU instructions can use this data format and are specific for only this format. It is therefore never necessary to specify this memory data size, whether it is addresses by the variable's name or by a pointer. Examples of referring to memory data with the packed BCD FPU instructions are:

var_name ;the variable var_name must have been declared as a TBYTE, otherwise the assembler issues an error

var_name[30] or var_nam[ebx] ;same as above with displacement

[edi] ;this will use the 10 bytes starting at the address pointed to by the register

All other legitimate modes of indirect addressing are also acceptable. When using pointers, the programmer is responsible for insuring that the 10 bytes of memory at that address are suitable for the purpose.

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 3

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 3
Instructions related to the FPU internals

The FPU instructions covered in this chapter perform no operation on numerical data. Their main purpose is to exchange information with the FPU or modify some of its settings. Several of them have both a "WAIT" and a "NO WAIT" variant (the latter having the letter "N" inserted immediately following the "F" in the mnemonic).

None of these instructions will raise an exception flag.

The described instructions are (in alphabetical order):

FCLEX/FNCLEX     CLear EXceptions

FDECSTP          DECrement STack Pointer

FFREE            FREE a data register

FINCSTP          INCrement STack Pointer

FINIT/FNINIT     INITialize the FPU

FLDCW            LoaD Control Word

FLDENV           LoaD ENVironment

FRSTOR           ReSTORe all registers

FSAVE/FNSAVE     SAVE state of FPU

FSTCW/FNSTCW     STore Control Word

FSTENV/FNSTENV   STore ENVironment

FSTSW/FNSTSW     STore Status Word

FWAIT            WAIT while FPU is busy

FWAIT (CPU waits while FPU is busy)

Syntax:   fwait (no operand)

This instruction prevents the CPU from executing its next instruction if the Busy bit of the FPU's Status Word is set.

The FPU does not have direct access to the stream of instructions. It will start executing an instruction only when the CPU detects one in the code bits and transmits the information to the FPU. While the FPU is executing that instruction, the CPU can continue to execute in parallel other instructions which are not related to the FPU. Some of the FPU instructions being relatively slow, the CPU could execute several of its own instructions during that period.

The CPU also has some read/write access to the FPU's Status and Control registers even while the FPU is executing an instruction. In some cases, it may be desirable to read/write these registers without delay. But, in most cases, it is preferable or even necessary to wait until the FPU has completed the current instruction before proceeding with the read/write of these registers.

For example, a comparison by the FPU may require several clock cycles to execute and set the bits in the proper fields of the Status Word register. Waiting until the FPU has completed the operation and effectively updated the Status Word is a necessity if reading that Status Word is to have any meaning.

Many of the instructions which read or write to the FPU's Status and Control registers are automatically encoded with the fwait code.

Whenever the FPU is instructed to store a result (such as an integer) intended to be used by a CPU instruction, the latter should be preceded by an explicit FWAIT instruction. (For example, storing the content of a data register to memory as an integer may take some 30 clock cycles to complete.)

Note: Original versions of MASM were inserting an FWAIT instruction in front of every FPU instruction except those specified as "no-wait". This may have been necessary with the earlier co-processors but not on the more modern ones. Such coding may be observed if some old code is disassembled. The CPU can now poll the Busy bit of the Status Word whenever necessary before sending data processing instructions to the FPU.

FINIT / FNINIT (Initialize FPU)

Syntax:   finit  (no operand)
          fninit (no operand)

The FINIT and FNINIT have exactly the same coding except that the FINIT is automatically preceded by the FWAIT code.

This instruction initializes the FPU by resetting all the registers and flags to their default values. (Refer to Chap.1 Description of FPU Internals for default values.)

Due to some of the restrictions of the FPU, it is good programming practice to take the precaution of initializing it before starting any computation, unless it is known that data has been left in it for further processing. If several sections of code use the FPU and the programmer is careless about not leaving the data registers "empty" in some of them, they can fill up very rapidly.

The "no-wait" variant may become necessary only if the programmer writes exception handling code and an endless wait is to be prevented.

Note: The FPU hardware is also used to process MMX/SSE instructions which may render its registers unusable for immediate FPU instructions. Initializing it would insure the clean-up for subsequent FPU instructions. Some of the API functions of Windows XP are also known to use MMX instructions without preserving the FPU registers and restoring them before leaving. Using API functions under Windows XP while some of the FPU registers still contain valuable data must therefore be avoided.

FSTSW / FNSTSW (Store Status Word)

Syntax:   fstsw  Dest
          fnstsw Dest

The FSTSW and FNSTSW have exactly the same coding except that the FSTSW is automatically preceded by the FWAIT code.

This instruction stores the content of the Status Word register at a 16-bit WORD memory address (Dest). All valid addressing modes are acceptable for this memory destination. Specifying the size with indirect addressing is not necessary because the WORD size is implicit in this instruction.

Ever since the 80287 co-processor, the destination can also be specified as the CPU's AX register.

This is the only FPU instruction which can transfer information directly from one of the FPU's registers to one of the CPU's general purpose registers.

The "no-wait" variant should only be used in exception handling code when it may be necessary to investigate which of the other flags of the Status Word may have been set. The "wait" variant should be used in most other cases. The remainder of this description will assume that the "wait" variant has been used.

The information in the Status Word is normally used to branch the program based on some result of the previous FPU instruction(s). A subsequent FWAIT instruction should always be included before the testing and/or branching code to insure that the information has effectively reached its specified destination.

If one or more of the exception flags need to be tested (such as for a division by zero and/or for an invalid operation), the related bits of the Status Word must then be tested individually (or in groups). Example code would be:

  stword    dw   ?

  fstsw stword      ;copy the status word to the memory variable stword
  fwait             ;wait until the instruction is completed
  test  stword,4+1  ;tests both the zero divide
                    ;and invalid exception flags together
  jnz   error_code  ;jump to the code section dealing with these exceptions
                    ;possibly testing further to define which one occurred
                    ;and issue the appropriate message or take other action

If the condition bits (C0-C3) need to be examined such as after a comparison, storing the Status Word in the AX register can be most useful. Those condition bits would then all be in the AH register which can be transferred directly into the CPU's flag register with the "sahf" instruction. With such a transfer,

the C0 bit becomes the CF (carry) flag,
the C2 bit becomes the PF(parity) flag, and
the C3 bit becomes the ZF (zero) flag.
(The C1 bit is not associated with any flag.)

A simple conditional jump can then be used (instead of testing for set bits followed by a conditional jump). Example code would be:

  fstsw ax        ;copy the status word to the AX register
  fwait           ;wait until the instruction is completed
  sahf            ;copy the condition bits in the CPU's flag register
  ja   this_code  ;jump to this code section if CF=C0=0 and ZF=C3=0

FSTCW / FNSTCW (Store Control Word)

Syntax:   fstcw  Dest
          fnstcw Dest

The FSTCW and FNSTCW have exactly the same coding except that the FSTCW is automatically preceded by the FWAIT code.

This instruction stores the content of the Control Word register at a 16-bit WORD memory address (Dest). All valid addressing modes are acceptable for this memory destination. Specifying the size with indirect addressing is not necessary because the WORD size is implicit in this instruction.

The "no-wait" variant could be used almost anytime because the content of the Control Word cannot be changed by any instruction except FINIT/FNINIT (and FLDCW which would be redundant immediately before an FSTCW instruction). Waiting for the FPU to finish executing its current instruction would thus not have any effect on the information. The "wait" variant should however be used if it immediately follows an FINIT/FNINIT.

Although this Control Word contains some information about the settings of the FPU, the main purpose for getting a copy of it is generally for temporary storage. Some of the settings can then be selectively changed for a sequence of FPU operations, and the previous settings restored afterwards.

FLDCW (Load Control Word)

Syntax:   fldcw Src

This instruction loads into the Control Word register the 16-bit WORD located at the specified memory address (Src). All valid addressing modes are acceptable for this memory source. Specifying the size with indirect addressing is not necessary because the WORD size is implicit in this instruction.

The purpose of this instruction is to modify the current settings of the FPU. The rounding control is probably the one which may need to be modified the most often. The following example code is based on the need to truncate a result before proceeding further, and the current Control Word may contain some other "rounding" mode.

  oldcw   dw   ?

  fstcw oldcw     ;get the current Control Word to retain all setting bits
                  ;not related to the rounding control (RC) bits
  fwait		  ;to insure the storage instruction is completed
  mov   ax,oldcw
; and   ax,0F3FFh ;clears only the RC bits, leaving all other bits unchanged
                  ;not necessary here because both bits will be set
  or    ax,0C00h  ;this will set both bits of the RC field to the truncating mode
                  ;without affecting any of the other field's bits
  push  eax       ;use the stack to store the modified Control Word in memory
  fldcw [sp]      ;load the modified Control Word

  fxxxx           ;other FPU instruction(s) needing the truncating mode

  fldcw oldcw     ;restore the previous Control Word
  pop   eax       ;clean-up the stack
                  ;this could also retrieve a 16-bit or 32-bit integer
                  ;possibly returned by the "other FPU instruction(s)"

FCLEX / FNCLEX (Clear exceptions)

Syntax:   fclex  (no operand)
          fnclex (no operand)

The FCLEX and FNCLEX have exactly the same coding except that the FCLEX is automatically preceded by the FWAIT code.

This instruction clears all the exception bits of the Status Word.

The "no-wait" variant must be a part of any exception handling routine written by the programmer. Otherwise, if control is returned to the FPU without clearing the exceptions, more interrupts to handle the exception would be generated in an endless loop. (In such exception handling routines, the "wait" variant could generate an endless wait situation!)

When all exception handling is left to the FPU, it is still good programming practice to test occasionally the Status Word for some specific exceptions. If one (or more) has been detected which requires some branching in the code, exceptions should then be cleared to prevent future branching due to "old" exceptions.

FSAVE / FNSAVE (Save state of FPU)

Syntax:   fsave  Dest
          fnsave Dest

The FSAVE and FNSAVE have exactly the same coding except that the FSAVE is automatically preceded by the FWAIT code.

This instruction saves the entire state of the FPU at the specified address (Dest) and then initializes it as if an FINIT instruction had been executed. The required memory size is 108 bytes and the data will have the following format in 32-bit programming.

Note: In 16-bit programming, the instruction pointer and operand address would have a WORD size. The first 28 bytes would thus be stored in only 14 bytes (7 WORDS without any unused memory) and followed immediately by the 8 TBYTE register data for a total of only 94 bytes.

The "no-wait" variant must be used if an exception handling routine written by the programmer needs the information returned by this instruction. Otherwise, the "wait" variant should be used.

This instruction would normally be used when the content of the FPU must be saved temporarily in order to use the FPU for some other reason. For example, a debugger program would be a typical application making use of this instruction if it needs to display the content of the FPU at a break point. It would then use the FPU to convert the saved floating point values and later restore the previous state of the FPU (with the FRSTOR instruction) so that the debugged program could continue to function normally.

This instruction should also be considered if a program must be executed under Windows XP and some of the API functions must be called while useful data is still in FPU registers. The registers can then be restored after the API call.

FRSTOR (Restore the state of the FPU)

Syntax:   frstor Src

This instruction restores the state of the FPU previously saved in memory (by the FSAVE instruction) in the 108 bytes starting at the specified address (Src). The format of that data must be as depicted in Fig.3.1 for 32-bit programming. (For 16-bit programming, see the Note under Fig.3.1).

Any alteration of that data in memory should generally be avoided. On rare occasions, the opportunity may be taken to modify the Control Word or clear exceptions before restoring the state of the FPU.

FSTENV / FNSTENV (Store environment)

Syntax:   fstenv  Dest
          fnstenv Dest

The FSTENV and FNSTENV have exactly the same coding except that the FSTENV is automatically preceded by the FWAIT code.

This instruction stores the environment state at the specified memory address (Dest). The required memory size is 28 bytes for 32-bit programming, having the same format as the first 28 bytes depicted in Fig.3.1. (For 16-bit programming, see the Note under Fig.3.1).

The "no-wait" variant must be used if an exception handling routine written by the programmer needs some of the information returned by this instruction. Otherwise, the "wait" variant should be used to insure that the latest information will be made available.

Although the content of the Status Word and the Control Word can be retrieved by other means (FSTSW and FSTCW respectively), the Instruction Pointer and Operand Address of the last instruction processed by the FPU may be useful information when an exception is detected. Some code using this instruction could be inserted during the development phase to better identify potential programming flaws and lead to proper correction. (For example, a sub-routine could convert the data of the environment to alphanumeric and keep a log of each occurrence for later review and analysis).

FLDENV (Load environment)

Syntax:   fldenv Src

This instruction loads the environment previously stored in memory (by the FSTENV instruction) in the 28 bytes starting at the specified address (Src). The format of that data must be as depicted in the first 28 bytes of Fig.3.1 for 32-bit programming. (For 16-bit programming, see the Note under Fig.3.1).

The environment would normally have been stored following the detection of an exception. The opportunity could then be taken to modify the 16-bit registers for various reasons and then load the modified environment.

FFREE (Free a data register)

Syntax:   free st(x)

This instruction modifies the Tag Word to indicate that the specified 80-bit data register is now considered as empty. Any data previously in that register becomes unusable. Any attempt to use that data will result in an Invalid exception being detected and an INDEFINITE value possibly being generated.

Although any of the 8 data registers can be tagged as free with this instruction, the only one which can be of any immediate value is the ST(7) register when all registers are in use and another value must be loaded to the FPU. If the data in that ST(7) is still valuable, other instructions should be used to save it before emptying that register.

FDECSTP (Decrement stack pointer)

Syntax:   fdecstp (no operand)

This instruction simply decrements the value of the TOP field of the Status Word. If that value was 0, it becomes 7. The contents of the data registers and of the Tag Word are not changed.

From the programmer's point of view, this instruction would cause the content of ST(7) to become the content of the top register and thus become ST(0). The data previously at the top in ST(0) would shift to ST(1), and so on for the other registers.

All data processing instructions for the FPU must use at least the top register as an operand. The main feature of this FDECSTP instruction (and of its counterpart FINCSTP) is to be able to "rotate the barrel" in order to bring any of the data registers to the top, without changing the content nor their relative order in those registers.

This instruction (or its counterpart FINCSTP) could also be used to bring a free register to the ST(7) position in order to load it with another value. For example, if all registers are full and the value in ST(5) has become useless, the following code could be used.

  ffree st(5)    ;tags the ST(5) register as empty
  fdecstp        ;brings the empty ST(5) register to the ST(6) position
  fdecstp        ;brings it now to the ST(7) position
                 ;where it could be loaded with some new data
                 ;(all the other data has also been shifted two positions)

FINCSTP (Increment stack pointer)

Syntax:   fincstp (no operand)

This instruction simply increments the value of the TOP field of the Status Word. If that value was 7, it becomes 0. The contents of the data registers and of the Tag Word are not changed.

From the programmer's point of view, this instruction would cause the content of ST(1) to become the content of the top register and thus become ST(0). The data previously in ST(2) would shift to ST(1), and so on for the other registers, the content of ST(0) becoming ST(7).

All data processing instructions for the FPU must use at least the top register as an operand. The main feature of this FINCSTP instruction (and of its counterpart FDECSTP) is to be able to "rotate the barrel" in order to bring any of the data registers to the top, without changing the content nor their relative order in those registers.

For example, if some operation using the content of both ST(1) and ST(3) is required, the following code could be used if the operation does not affect the location of other registers.

  fincstp         ;brings ST(1) as ST(0), and ST(3) as ST(2)
  fxxxx st,st(2)  ;perform required operation
  fdecstp         ;returns all registers to their original position

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 4

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 4
Data transfer instructions - REAL numbers

The FPU instructions covered in this chapter perform no mathematical operation on numerical data. Their main purpose is simply to transfer floating point data between the FPU's 80-bit data registers, or between those registers and memory, or to access the 7 constants hard coded into the FPU. (Other instructions must be used to transfer integer and BCD data).

The data transfer instructions are (in alphabetical order):

FCMOVcc   Conditional MOVe based on CPU flags

FLD       LoaD real number

FST       STore real number

FSTP      STore real number and Pop the top data register

FXCH      eXCHange the top data register with another data register

The instructions to transfer the 7 hard-coded constants into the top data register are (in alphabetical order):

FLD1      LoaD the value of 1

FLDL2E    LoaD the Log base 2 of e (Napierian constant)

FLDL2T    LoaD the Log base 2 of Ten

FLDLG2    LoaD the Log base 10 of 2 (common log of 2)

FLDLN2    LoaD the Log base e of 2 (natural log of 2)

FLDPI     LoaD the value of PI

FLDZ      LoaD the value of Zero

FLD (Load real number)
Syntax:    fld Src

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction decrements the TOP register pointer in the Status Word and loads the floating point value from the specified source (Src) in the new TOP data register. The source can be one of the FPU's data registers or the memory address of a REAL4, REAL8, or REAL10 value (see Chap.2 for addressing modes of real numbers).

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

If the source is a denormalized number, a Denormal exception will be detected, setting the related flag in the Status Word. The value will still be loaded and normalized if possible.

If the source is one of the FPU's data registers, its value is retrieved before the TOP register pointer is decremented. If that source register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer will still be decremented and the new value in ST(0) will be the INDEFINITE NAN.

The content of the Tag Word will be modified according to the result of the operation.

Examples of using this instruction:

fld  st(3) ;the value in the current ST(3) is copied into the new ST(0)
           ;the same value will now be in ST(0) and ST(4)

fld  st    ;duplicates the TOP value which will now be in both ST(0) and ST(1)

Note that fld st(7) will always be an Invalid operation and the outcome will always be the INDEFINITE value in the new ST(0). The ST(7) register must be empty for a valid FLD instruction but would also be an invalid source if it is empty!
fld  real4_var   ;the value of the real4_var variable
                 ;is converted to the REAL10 format and copied into ST(0)

fld  qword ptr[ebx] ;ST(0) takes the value of the QWORD located at the address
                 ;indicated by EBX which the FPU interprets as a REAL8 value

fld  tbyte ptr[edi+50] ;the FPU interprets the value at the address EDI+50
                 ;as a REAL10 and copies it to ST(0).
The content of memory is simply a series of bits. Floating point data cannot be differentiated from any other type of data. The FPU will thus interpret a series of bits as instructed. (See Rule #3).

If the source is a REAL10 value in memory, it has the same format as the FPU's data registers and is simply copied as is.

However, if the source is a REAL4 or a REAL8 value, it is converted to the 80-bit REAL10 format before loading it to ST(0). When such an operation is observed with a debugger, the displayed value in the FPU data register may often seem slightly different from the intended one. That would be due to the rounding which occurred when the data had to be shortened to fit into the REAL4 or REAL8 format, and some precision was lost.

Just as 3.33333... is never the exact value of 10÷3 regardless of the length of the fractional part, binary fractions behave in a similar manner. For example, a decimal value of 1.2 in binary would be:

1.00110011001100110011001100110011....b    0011 repeating indefinitely.
If a variable is declared as a REAL4 and initialized with that value, the assembler would most probably have used the FPU to convert it to binary with 64 bits of precision using the "0011" sequence as often as possible. To fit that value into the REAL4 format, it could only keep the first 23 fraction bits. The 24th bit being a "1", the number was rounded UP to:
1.0011 0011 0011 0011 0011 010b
Because of this rounding UP, this value is now slightly larger than the original decimal value and a debugger would show it as follows if loaded into an FPU data register:
1.2000000476837158
Similarly, some binary fractions would be rounded DOWN and the resulting value would be slightly smaller than the original one. For example, a REAL4 variable initialized with a decimal value of 1.3 would be loaded to the FPU's data register as:
1.0100 1100 1100 1100 1100 110b
which would be shown in decimal format by a debugger as:
1.2999999523162842
This rounding can also affect REAL4 variables initialized with large numbers having more than 7 integer digits. For example, if a REAL4 variable is initialized with 987654321.0 and loaded to the FPU, a debugger would show it as 987654336.0!
The FPU and floating point real numbers do not provide unlimited precision. The REAL4 format's relative precision is only equivalent to approx. 7 significant decimal digits, the REAL8 format being equivalent to approx. 15 significant decimal digits, and the REAL10 to 19 significant decimal digits.

FST (Store real number)

Syntax:    fst Dest

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Precision
This instruction stores the value currently in the TOP data register ST(0) to the specified destination (Dest). The destination can be one of the FPU's data registers or the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers).

This instruction cannot store the value of ST(0) as a REAL10 in memory; see the FSTP instruction for such an operation.

If the ST(0) data register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word, and the value of INDEFINITE would be stored at the specified destination. If the ST(0) contains a denormalized number, a Denormal exception will be detected, setting the related flag in the Status Word.

When the destination is one of the FPU's data registers, the content of ST(0) will overwrite the content of the destination register, whether that destination register is empty or not, and adjust the Tag Word to reflect any change in the status of that register.

When the destination is a REAL4 or REAL8 memory address, the REAL10 value in ST(0) is first converted to the appropriate format and rounded according to the bits in the RC field of the Control Word. A Precision exception is detected if some of the least significant bits would be lost, setting the related flag in the Status Word. A Denormal exception may also be detected, setting the related flag in the Status Word, if the value of ST(0) must be denormalized to fit the destination's format.

Examples of using this instruction:

fst  st(3)  ;the value of the current ST(0) is copied to the current ST(3)
            ;the same value will now be in ST(0) and ST(3)

fst  st     ;although this instruction is allowed, no data register is changed
            ;an exception may still be detected due to the value in ST(0)

Note that fst st(7) is valid but is not recommended because it would prevent loading other values to the FPU. If for some reason it should become necessary to use it, ST(7) should be emptied as soon as possible (such as with the FFREE instruction).
fst  dword ptr [eax]   ;stores the value of ST(0) in the REAL4 format
                       ;at the memory address indicated by EAX

fst  real_var   ;stores the value of ST(0) in the appropriate format
                ;at the address of the real_var memory variable

Although the FPU performs computations with 64 bits of precision, 40 of those bits of precision will be lost when a computed value is stored as a REAL4. Re-using such a stored value in subsequent computations will automatically limit the precision to 24 bits even though 64 bits would be used.

Similarly, 11 bits of precision will be lost when a computed value is stored as a REAL8. Re-using such a stored value in subsequent computations will automatically limit the precision to 53 bits even though 64 bits would be used.

FSTP (Store real number and Pop the top data register)

Syntax:    fstp Dest

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Precision
This instruction is the same as the FST instruction except for the following two additions:

- it allows storing the ST(0) value in memory as a REAL10 value, and
- the ST(0) register is POPped after the transfer is completed, modifying the Tag Word and incrementing the TOP register pointer of the Status Word.

This instruction would be used, for example,

- when the final result of a computation must be stored in memory, liberating the FPU's TOP data register for future use,

fstp qword ptr [edx]  ;stored as a REAL8 at the address specified by EDX

- when an intermediate result of a computation in ST(0) must be preserved without any loss of precision,

fstp temp_var  ;temp_var having been declared as a TBYTE

- when the reuse of a data register could be beneficial while liberating the TOP register,

fstp st(4)   ;after the instruction is completed, the former value of ST(4)
             ;would be overwritten by the former value of ST(0), and
             ;would become ST(3) after popping ST(0)

- when the value in ST(0) has become useless and the register needs to be liberated.

fstp st     ;copied upon itself and popped

FXCH (Exchange the top data register with another data register)

Syntax:    fxch Dest
           fxch (no operand, ST(1) being implied)

Exception flags: Stack Fault, Invalid operation
This instruction exchanges the content of the TOP data register ST(0) with the content of one of the other data registers (Dest). (Memory operands are not allowed with this instruction.)

If either ST(0) or the destination register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The value of INDEFINITE will have been assumed in the empty register and then exchanged with the value of the other register. The Tag Word will be adjusted to reflect any change in the status of the two registers involved.

With many FPU instructions, the data in ST(0) must be one of the operands or even the only operand. FXCH is useful when some operation on data other than in the ST(0) register may be required during a computation. For example, if the square root of the value in ST(3) should be needed before proceeding with a computation, the following code could be used:

fxch st(3)   ;get the value of ST(3) into the TOP register
             ;the value of ST(0) being kept in ST(3)
fsqrt        ;extract its square root
fxch st(3)   ;return it to its relative position and
             ;bring back the former value of ST(0) to its original position

FCMOVcc (Conditional move based on CPU flags)

Syntax:    fcmovcc st,st(i)

Exception flags: Stack Fault, Invalid operation
Note: This instruction is valid only for the Pentium Pro and subsequent processors. It may not be supported by some assemblers (for MASM, the .686 directive must be used). The encodings are provided to facilitate hard-coding of this instruction if it is not supported by the assembler.
This instruction overwrites the content of the TOP data register ST(0) with the content of the specified ST(i) data register if the specified condition is true. Following is the list of the conditions supported with the instruction, with the encodings and descriptions.
Encoding      Instruction       Description
 DA C0+i   FCMOVB   ST,ST(i)    Move if below (CF=1)
 DA C8+i   FCMOVE   ST,ST(i)    Move if equal (ZF=1)
 DA D0+i   FCMOVBE  ST,ST(i)    Move if below or equal (CF=1 or ZF=1)
 DA D8+i   FCMOVU   ST,ST(i)    Move if unordered (PF=1)
 DB C0+i   FCMOVNB  ST,ST(i)    Move if not below (CF=0)
 DB C8+i   FCMOVNE  ST,ST(i)    Move if not equal (ZF=0)
 DB D0+i   FCMOVNBE ST,ST(i)    Move if not below or equal (CF=0 and ZF=0)
 DB D8+i   FCMOVNU  ST,ST(i)    Move if not unordered (PF=0)
If the ST(i) data register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The value of INDEFINITE will have been assumed in the empty register and overwrite the content of the TOP data register ST(0).

Extreme caution must be used with this instruction depending on how the CPU flags have been modified. For example, when PF=1 due to an FPU instruction generating an invalid operation exception, the CF and ZF flags are also usually set to 1. If the instruction is used after the CPU flags are modified by a regular CPU instruction, the PF flag may not have any significant meaning.

FLDZ (Load the value of Zero)

Syntax:    fldz  (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of +0.0 into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

A typical use of this instruction would be to "initialize" a data register intended to be used as an accumulator. Even though a value of zero could also be easily loaded from memory, this instruction is faster and does not need the use of memory.

FLD1 (Load the value of 1)

Syntax:    fld1 (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of +1.0 in REAL10 format into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

A value of 1 may be required in some computations. One example would be when the reciprocal of a number is needed. Even though a value of 1 could also be easily loaded from memory, this instruction is faster and does not need the use of memory.

FLDPI (Load the value of PI)

Syntax:    fldpi (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of π (3.14159...) in REAL10 format with a precision equivalent to approximately 19 decimal digits into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

The value of π is often required for computations related to the circle, sphere, trigonometry, converting angles to/from degrees/radians, and numerous other applications.

FLDL2E (Load the Log base 2 of the Napierian constant e)

Syntax:    fldl2e (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of log2(e) in REAL10 format with a precision equivalent to approximately 19 decimal digits into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

This constant is most useful when an exponential of e must be computed, such as for the hyperbolic functions. From logarithmic relations:

    log2(ey) = ylog2(e)

The antilog base 2 of the ylog2(e) result can then be computed using other FPU instructions (including F2XM1) to arrive at the ey value.

FLDL2T (Load the Log base 2 of 10)

Syntax:    fldl2t (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of log2(10) in REAL10 format with a precision equivalent to approximately 19 decimal digits into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

This constant is most useful when an exponential of 10 (such as the common antilog) must be computed. From logarithmic relations:

    log2(10y) = ylog2(10)

The antilog base 2 of the ylog2(10) result can then be computed using other FPU instructions (including F2XM1) to arrive at the 10y value.

FLDLG2 (Load the log base 10 of 2)

Syntax:    fldlg2 (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of log10(2) in REAL10 format with a precision equivalent to approximately 19 decimal digits into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

Although this constant is the reciprocal of the log2(10) constant obtained with the FLDL2T instruction, it is preferred for computing the common log (base 10) of numbers because it would involve a multiplication instead of a division according to the following logarithmic relations:

log10(x) = log10(2)·log2(x)

A log2(x) value can be obtained with the FYL2X or FYL2XP1 instructions.

Computing the common log of a number may have many uses. One of them is to get information on the relative size of a real number before proceeding to convert it and display it in decimal format, whether it be in regular or scientific notation.

FLDLN2 (Load the log base e of 2)

Syntax:    fldln2 (no operand)

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the value of ln(2) in REAL10 format with a precision equivalent to approximately 19 decimal digits into the new TOP data register.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

Although this constant is the reciprocal of the log2(e) constant obtained with the FLDL2E instruction, it is preferred for computing the natural log (base e) of numbers because it would involve a multiplication instead of a division according to the following logarithmic relations:

ln(x) = ln(2)·log2(x)

A log2(x) value can be obtained with the FYL2X or FYL2XP1 instructions.

The natural log of a number is often used in some scientific computations. For example, it is necessary for computing the hyperbolic arc functions.

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 5

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 5
Data transfer instructions - Integer numbers

The FPU instructions covered in this chapter perform no mathematical operation on numerical data. Their main purpose is simply to transfer integer data between the FPU's 80-bit data registers and memory. (Other instructions must be used to transfer floating point and BCD data).

Integer data cannot be transferred directly between the CPU and FPU registers.

The integer data transfer instructions are:

FILD    Integer LoaD from memory

FIST    Integer STore to memory

FISTP   Integer STore to memory and Pop the top data register
Note that all FPU instructions using Integer data as an operand have an "I" immediately following the "F" in the mnemonic to distinguish them from instructions using other data types.

FILD (Load integer number from memory)

Syntax:    fild Src

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the integer value from the specified source (Src) in the new TOP data register ST(0) after converting it to the 80-bit REAL10 format. The source must be the memory address of a signed 16-bit WORD integer, a signed 32-bit DWORD integer, or a signed 64-bit QWORD integer.

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

The content of the Tag Word for the new TOP register will be modified according to the result of the operation.

Examples of using this instruction:

fild word ptr[ebx+8]  ;loads the 5th WORD of an integer array addressed by EBX

fild int_var          ;loads the value of the integer variable int_var
                      ;according to its declared size

fild dword ptr[ebp+12]   ;typical assembler coding to load the value of a
                      ;dword integer passed on the stack as a parameter to a
                      ;procedure (the source file may have referred to it
                      ;using the name of a variable)
(See Chap.2 for integer data types, their addressing modes, and examples of using integer values from CPU registers.)

FIST (Store an integer number to memory)

Syntax:    fist Dest

Exception flags: Stack Fault, Invalid operation, Precision
This instruction rounds the value of the TOP data register ST(0) to the nearest integer according to the rounding mode of the RC field in the Control Word, and stores it at the specified destination (Dest). The destination can be the memory address of a 16-bit WORD or of a 32-bit DWORD.
This instruction cannot store the value of ST(0) as a 64-bit QWORD integer; see the FISTP instruction for such an operation.
If the ST(0) data register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The value of 8000h would be stored at the specified destination if a WORD type, or 80000000h if a DWORD type.

If the rounded value of ST(0) would be larger than acceptable for the integer type of the destination, an Invalid operation exception is detected, setting the related flag in the Status Word. The value of 8000h would be stored at the specified destination if a WORD type, or 80000000h if a DWORD type.

Note: Although these 8000h and 80000000h integer values are stored as "error" codes, the FPU will be treat them as -215 and -231 respectively if loaded back in a data register or used in comparison and arithmetic instructions.

If the value in ST(0) would contain a binary fraction, some precision would be lost and a Precision exception would be detected, setting the related flag in the Status Word.

The actual value in ST(0) does not change with this instruction.

Examples of using this instruction:

fist  word ptr [edi+ebx] ;stores the rounded value of ST(0) as a WORD
                         ;at the memory address indicated by EDI+EBX

fist  integer_var        ;stores the rounded value of ST(0)
                         ;at the memory variable integer_var
                         ;according to its declared integer data type.
(See Chap.2 for integer data types, their addressing modes, and examples of transferring integer values to CPU registers.)

FISTP (Store an integer number to memory and Pop the top data register)

Syntax:    fistp Dest

Exception flags: Stack Fault, Invalid operation, Precision
This instruction is the same as the FIST instruction except for the following two additions:

- it allows storing the ST(0) value to memory as a 64-bit QWORD integer, and
- the ST(0) register is POPped after the transfer is completed, modifying the Tag Word and incrementing the TOP register pointer of the Status Word.

If an invalid exception is detected when attempting to store a QWORD, a value of 8000000000000000h would be stored at the specified destination.

This instruction would be used when the final result of a computation must be stored in memory as an integer, liberating the FPU's TOP data register for future use.

fistp  int_var  ;stores the rounded value of ST(0)
                ;at the memory variable int_var
                ;according to its declared integer data type
                ;and POPs the FPU's TOP data register
(See Chap.2 for integer data types, their addressing modes, and examples of transferring integer values to CPU registers.)

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 6

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 6
Data transfer instructions - Packed decimals

The FPU instructions covered in this chapter perform no mathematical operation on numerical data. Their main purpose is simply to transfer packed Binary Coded Decimal (BCD) integer data between the FPU's 80-bit data registers and memory.

There are only two FPU instructions using packed BCD data as the operand and no other data type can be used with those instructions. Both have the letter "B" immediately following the "F" in the mnemonic.

FBLD   LoaD BCD data from memory

FBSTP  STore BCD data to memory

FBLD (Load BCD data from memory)

Syntax:    fbld Src

Exception flags: Stack Fault, Invalid operation
This instruction decrements the TOP register pointer in the Status Word and loads the packed BCD integer value from the specified source (Src) in the new TOP data register ST(0) after converting it to the 80-bit REAL10 format. The source must be the memory address of an 80-bit TBYTE having the specified packed BCD format. (See Chap.2 for the packed BCD format and its addressing modes).

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

The content of the Tag Word for the new TOP register will be modified according to the result of the operation.

Although each nibble (4 bits) in a proper packed BCD format should not exceed a value of 9, the FPU will convert any value without any exception being detected. If a nibble does exceed a value of 9, the FPU simply uses its mod 10 value and carries a 1 to the next nibble. For example,

if a TBYTE contains the hex value of 00000000000000009A7Dh,

the FBLD instruction would convert it as the decimal value of 10083d

or the same as a proper packed BDC value of 00000000000000010083h.

The FBLD instruction is used primarily for converting alphanumeric strings (which usually contain a fractional part) to the REAL10 format. The process involves

- parsing the string for invalid characters to avoid processing "garbage",
- determining the number of decimal places,
- converting the digits to the packed BCD format,
- loading the formatted number to the FPU as an integer, and
- dividing by the appropriate exponent of 10 according to the number of decimal places in the original string.

Following is an example of code to convert a decimal string to the packed BCD format. This example would only allow a string in the regular decimal notation. Separate instructions would be needed to also accept a string in the scientific notation. (Code is not included for the final steps of loading it to the FPU and dividing by an exponent of 10 to arrive at the final floating point value.)

************

//The packed BCD value will be stored in a global memory variable. A local variable or some other memory location could be used as well for this purpose. The same applies for the location where the null-terminated "user input" string will be available.

This code will return the packed BCD value in memory, the total number of integer/decimal digits in AH and the number of decimal digits in AL. If an error is detected (no input digit, more than 18 digits, invalid character), EAX will return with 0, and ECX will contain the counts of digits.//

.data

temp_bcd     dt   ?
szdecimal    db   32 dup(0)

.code

a2bcd proc

; EDI will be used as the pointer for the location of the BCD value
; ESI will be used as the pointer to the source decimal string
; ECX will be used to hold the count of integer digits (in CH)
; and decimal digits (in CL)

   push esi
   push edi
   xor  ecx,ecx         ;initialize for the counts
   lea  esi,szdecimal-1 ;point to byte preceeding the source string
   lea  edi,temp_bcd    ;point to the memory location for the packed BCD

   xor  eax,eax
   stosd
   stosd
   stosw                ;initializes the TBYTE to 0, including the sign byte
   dec  edi             ;adjust for pointing to the sign byte
                        ; (most significant byte)
                        ;which now assumes the value as positive

; The following is to skip leading spaces without generating an error

space_check:
   inc  esi             ;point to next character
   mov  al,[esi]        ;get character
   cmp  al," "          ;is it a space
   jz   space_check     ;check for another space

; Check 1st non-space character for + or - sign

   .if  al == "-"       ;is there a "-" sign
      mov  byte ptr [edi],80h ;changes the sign byte for the negative code
      inc  esi          ;point to next character

   .elseif al == "+"    ;is there a "+" sign
      inc  esi          ;point to next character,
                        ; the sign byte is already coded positive
   .endif

integer_count:
   .if  al == 0         ;is it the end of the source string
      jmp count_end
   .elseif al == "."    ;is it a "period" decimal delimiter
      jmp decimal_count
   .elseif al == ","    ;is it a "coma" decimal delimiter
                        ; (also used in numerous countries)
      jmp decimal_count
   .elseif al < "0"     ;is it an invalid character below the ASCII 0
      jmp input_error
   .elseif al > "9"     ;is it an invalid character above the ASCII 9
      jmp input_error
   .elseif ch == 18     ;is digit count already at the maximum allowed
      jmp input_error
   .endif

   inc  ch              ;increment the count of integers digits
   inc  esi             ;point to next character
   mov  al,[esi]        ;get character
   jmp  integer_count   ;continue counting integer digits

decimal_count:
   inc  esi             ;point to next character
   mov  al,[esi]        ;get character
   .if  al == 0         ;is it the end of the source string
      jmp count_end
   .elseif al < "0"     ;is it an invalid character below the ASCII 0
      jmp input_error
   .elseif al > "9"     ;is it an invalid character above the ASCII 9
      jmp input_error
   .elseif ch == 18     ;is digit count already at the maximum allowed
      jmp input_error
   .endif

   inc  cl              ;increment the count of decimals digits
   inc  ch              ;increment the count of integers+decimals digits
   jmp  decimal_count   ;continue counting decimal digits

count_end:
   .if  ch == 0         ;check if input has any digit
      jmp input_error
   .endif

; At this point, the input string is valid,
; CL contains the count of decimal digits,
; CH contains the total count of decimal+integer digits, and
; ESI points to the terminating 0.
; All the digits now have to be converted to binary and
; packed two per byte from the least significant to the more significant

   push ecx            ;for temporary storage of the counts
   sub  ch,cl          ;CH now contains the count of only the integer digits
   lea  edi,temp_bcd   ;point to the least significant byte of the packed BCD

pack_decimals:
   dec  esi            ;point to the next more significant digit
   mov  al,[esi]       ;get decimal character

   .if  cl == 0        ;check if decimal digits all done
      .if  al < "0"    ;checks if current character is the decimal delimiter
         dec  esi      ;skip the decimal delimiter
      .endif
      jmp  pack_integers
   .endif

   and  al,0fh         ;keep only the binary portion
   ror  ax,4           ;transfer the 4 bits to the H.O. nibble of AH
   dec  esi            ;point to the next more significant digit
   dec  cl             ;decrement counter of decimal characters

   .if  cl == 0        ;check if decimal digits all done
      dec  esi         ;skip the decimal delimiter
      jmp  pack_integer2 ;get an integer character as 2nd nibble of this byte
   .endif

   mov  al,[esi]       ;get decimal character
   and  al,0fh         ;keep only the binary portion
   rol  ax,4           ;moves the 2nd binary value to the H.O. nibble of AL
                       ;and retrieves the 1st binary value from AH
                       ; into the L.O. nibble of AL
   stosb               ;store this packed BCD byte and increment EDI
   dec  cl             ;decrement counter of decimal characters
   jmp  pack_decimals  ;continue packing the decimal digits

pack_integers:
   .if  ch == 0        ;check if integer digits all done
      jmp  all_done    ;and terminate the packing process
   .endif

   mov  al,[esi]       ;get integer character
   and  al,0fh         ;keep only the binary portion
   ror  ax,4           ;transfer the 4 bits to the H.O. nibble of AH
   dec  esi            ;point to the next more significant digit
   dec  ch             ;decrement counter of integer characters

pack_integer2:
   .if  ch == 0        ;check if integer digits all done
      rol  ax,4        ;moves the 2nd binary value to the H.O. nibble of AL
                       ;and retrieves the 1st binary value from AH
                       ; into the L.O. nibble of AL
      mov  [edi],al    ;store this packed BCD byte
      jmp  all_done    ;and terminate the packing process
   .endif

   mov  al,[esi]       ;get integer character
   and  al,0fh         ;keep only the binary portion
   rol  ax,4           ;moves the 2nd binary value to the H.O. nibble of AL
                       ;and retrieves the 1st binary value from AH
                       ; into the L.O. nibble of AL
   stosb               ;store this packed BCD byte and increment EDI
   dec  esi            ;point to the next more significant digit
   dec  ch             ;decrement counter of integer characters
   jmp  pack_integers  ;continue packing the integer digits

all_done:
   pop  eax            ;retrieve the counts which were in ECX
   pop  edi
   pop  esi
   ret

input_error:
   xor  eax,eax        ;to return with error code
   pop  edi
   pop  esi
   ret

a2bcd endp

************

FBSTP (Store BCD data to memory)

Syntax:    fbstp Dest

Exception flags: Stack Fault, Invalid operation, Precision
This instruction rounds the value of the TOP data register ST(0) to the nearest integer (regardless of the rounding mode of the RC field in the Control Word), converts it to the packed BCD format, and stores it at the specified destination (Dest). (See Chap.2 for the packed BCD format and its addressing modes). It then POPs the TOP data register ST(0), modifying the Tag Word and incrementing the TOP register pointer of the Status Word.
If the floating point value needs to be rounded according to some other mode, the FRNDINT instruction must be used prior to the FBSTP instruction.

If the ST(0) data register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The INDEFINITE value in REAL10 format (FFFFC000000000000000h) would be stored as such at the specified destination.

If the rounded value of ST(0) would contain more than 18 integer digits, an Invalid operation exception is detected, setting the related flag in the Status Word. The INDEFINITE value in REAL10 format would be stored as such at the specified destination.

If the value in ST(0) would contain a binary fraction, some precision would be lost and a Precision exception would be detected, setting the related flag in the Status Word.

The FBSTP instruction is used primarily for converting floating point values from ST(0) to alphanumeric strings (usually with a required number of decimal places). The process involves:
- multiplying the value in ST(0) by an appropriate exponent of 10 according to the number of required decimal places, (taking precautions not to exceed 18 significant decimal digits in the resulting integer portion),
- storing it to a memory location as a packed BCD format,
- converting from the packed BCD format to a null-terminated alphanumeric string.

If the value to be converted is ≥1019 or <1, it should generally be converted to the scientific notation. The exception may be for small values close to 1 which need to be reported only to a few decimal places.

Following is an example of code to convert from the packed BCD format to a decimal string in regular notation (some modification would be needed to prepare a decimal string in scientific notation).

************

//The packed BCD value will be stored in a global memory variable. A local variable or some other memory location could be used as well for this purpose. The same applies for the location where the resulting null-terminated decimal string will be stored.

This code assumes that the floating point value is currently in the FPU's ST(0) register. It also assumes that 2 decimal places are required and that the resulting decimal value will not contain more than 18 digits. No error checking is performed.

The decimal string will also be left-justified. A "-" sign will be inserted as the first character if the value is negative, but nothing is inserted if the value is positive. Those are all additional options for the programmer.//

.data

temp_bcd     dt   ?
szdecimal    db   32 dup(0)

.code

bcd2a PROC

   pushd 100            ;use the stack as storage for this value
   fimul dword ptr[esp] ;converts 2 decimal places as 2 more integer digits
   fbstp temp_bcd       ;store the packed BCD integer value
   pop  eax             ;clean the stack of the pushed 100

; ESI will be used for pointing to the packed BCD
; EDI will be used for pointing to the decimal string

   push esi
   push edi
   lea  esi,temp_bcd+9  ;point to the most significant byte
   lea  edi,szdecimal   ;point to the start of the decimal string buffer
   xor  eax,eax
   fwait                ;to ascertain that the transfer of the
                        ;packed BCD is completed

   mov  al,[esi]        ;get the byte with the sign code
   dec  esi             ;decrement to next most significant byte
   or   al,al           ;for checking the sign bit
   jns  @F              ;jump if no sign bit
   mov  al,"-"          ;the value is negative
   stosb                ;insert the negative sign

@@:

; The next 8 bytes (in this example) will contain the integer digits
; and the least significant byte will then contain the 2 decimal digits.
; No leading 0 will be included in the integer portion
; unless it would be the only integer digit.

   mov  ecx,8           ;number of bytes to process for integer digits

@@:

   mov  al,[esi]        ;get the next byte
   dec  esi             ;adjust the pointer to the next one
   or   al,al           ;for checking if it is 0
   jnz  @F              ;the starting integer digit is now in AL
   dec  ecx             ;adjust the counter of integer bytes
   jnz  @B              ;continue searching for the first integer digit

; If the loop terminates with ECX=0, the integer portion would be 0.
; A "0" character must be inserted before processing the decimal digits

   mov  al,"0"          ;the ASCII 0
   stosb                ;insert it
   mov  al,[esi]        ;get the byte containing the decimal digits
   jmp  decimal_digits

@@:

; The first integer byte must be checked to determine
; if it contains 1 or 2 integer digits

   test al,0f0h         ;test if the H.O. nibble contains a digit
   jz   int_digit2      ;if not, process only the L.O. nibble

int_digit1:
   ror  ax,4            ;puts the H.O. nibble in the L.O. nibble position
                        ;and saves the L.O. nibble in AH
   add  al,30h          ;convert it to ASCII character
   stosb                ;store this character
   shr  ax,12           ;restores the L.O. nibble in AL
                        ;and also resets the other bits of AX

int_digit2:
   add  al,30h          ;convert it to ASCII character
   stosb                ;store this character
   mov  al,[esi]        ;get next byte
   dec  esi             ;adjust the pointer to the next one
   dec  ecx             ;adjust the counter of integer bytes
   jnz  int_digit1      ;continue processing the integer bytes

decimal_digits:
   mov  byte ptr [edi],"." ;insert the preferred decimal delimiter
   inc  edi

; If more than 2 decimal digits are required, a counter would be needed
; to process the necessary bytes.
; Also, if the number of required decimal digits is not even, the code
; would have to be altered to insert the decimal delimiter at the
; proper location.

   ror  ax,4            ;puts the H.O. nibble in the L.O. nibble position
                        ;and saves the L.O. nibble in AH
   add  al,30h          ;convert it to ASCII character
   stosb                ;store this character
   shr  ax,12           ;restores the L.O. nibble in AL
                        ;and also resets the other bits of AX
   add  al,30h          ;convert it to ASCII character
   stosw                ;store this character + the terminating 0

   pop edi
   pop esi
   ret

bcd2a ENDP

************

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 7

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 7
Comparison instructions

The FPU instructions covered in this chapter perform no mathematical operation on numerical data. Their main purpose is simply to compare the value in the FPU's top data register ST(0) to the value in another FPU data register, to floating point values in memory, to integer values in memory, to +0.0, or to examine its content.

The comparison instructions covered in this document are (in alphabetical order):

FCOM        COMpare ST(0) to a floating point value

FCOMI       COMpare ST(0) to ST(i) and set CPU flags

FCOMIP      COMpare ST(0) to ST(i) and set CPU flags and Pop ST(0)

FCOMP       COMpare ST(0) to a floating point value and Pop ST(0)

FCOMPP      COMpare ST(0) to ST(1) and Pop both registers

FICOM       COMpare ST(0) to an integer value

FICOMP      COMpare ST(0) to an integer value and Pop ST(0)

FTST        TeST ST(0) by comparing it to +0.0

FUCOM       Unordered COMpare ST(0) to a floating point value

FUCOMI      Unordered COMpare ST(0) to ST(i) and set CPU flags

FUCOMIP     Unordered COMpare ST(0) to ST(i) and set CPU flags and Pop ST(0)

FUCOMP      Unordered COMpare ST(0) to a floating point value and Pop ST(0)

FUCOMPP     Unordered COMpare ST(0) to ST(1) and Pop both registers

FXAM        eXAMine the content of ST(0)

FCOM (Compare ST(0) to a floating point value)

Syntax:    fcom (no operand)
           fcom Src

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction performs a signed comparison between the value in the TOP data register ST(0) and the floating point value from the specified source (Src). The source can be one of the FPU's data registers or the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If no operand is specified, the value in ST(0) is compared to the value in ST(1). (The FICOM instruction must be used to compare the value in ST(0) to an integer in memory).
Note that ST(0) cannot be compared to a REAL10 in memory. If that becomes necessary, the REAL10 value must first be loaded to the FPU.
The values of ST(0) and of the source are not modified by this instruction.

The result of the comparison is reported in the condition codes field of the Status Word as follows (the C1 bit is not used and the C2 bit was not used in early FPUs):

                           C3   C2   C0
     If ST(0) > source      0    0    0
     If ST(0) < source      0    0    1
     If ST(0) = source      1    0    0
     If ST(0) ? source      1    1    1
An Invalid operation exception is detected if ST(0) is empty, or if a data register specified as the source is empty, or if one of the two values is a NAN, setting the related flag in the Status Word. The result would then be indeterminate and return C3=1, C2=1 and C0=1 as indicated above. (Values of INFINITY will be treated as valid operands and yield a valid result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) (or a data register specified as the source) is empty, setting the related flag in the Status Word.

When either of the two values being compared is a denormalized number, a Denormal exception is detected, setting the related flag in the Status Word. The comparison would still yield a valid result.

The signed comparison means that positive values are larger than negative values, including the values of INFINITY which can be positive or negative. The lowest of all valid values would thus be -INFINITY and the greatest would be +INFINITY when used in comparisons.

When the source is a REAL4 or REAL8 in memory, it is first converted to the REAL10 format before the comparison. Because of the rounding being performed before storing REAL4 and REAL8 values in memory, comparing ST(0) to its stored value may rarely yield the "=" result (see the latter part of the FLD instruction for further explanation). If a computed value must be stored and used for subsequent comparisons, it should be stored as a REAL10 for maximum accuracy.

When the Status Word containing the result of the comparison is stored in AX, the C3, C2 and C0 bits can be transferred to the CPU's flag register (using the sahf instruction) as the ZF, PF and CF flags respectively. This makes it easier for branching based on the result. Following would be typical code after a comparison with a memory variable.

 fcom  real8_var   ;compare ST(0) with the value of the real8_var variable
 fstsw ax          ;copy the Status Word containing the result to AX
 fwait             ;insure the previous instruction is completed
 sahf              ;transfer the condition codes to the CPU's flag register
 jpe error_handler ;the comparison was indeterminate
                   ;this condition should be verified first
                   ;then only two of the next three conditional jumps
                   ;should become necessary, in whatever order is preferred,
                   ;the third jump being replaced by code to handle that case
 ja    st0_greater ;when all flags are 0
 jb    st0_lower   ;only the C0 bit (CF flag) would be set if no error
 jz    both_equal  ;only the C3 bit (ZF flag) would be set if no error

FCOMP (Compare ST(0) to a floating point value and POP ST(0))

Syntax:    fcomp (no operand)
           fcomp Src

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction is the same as the FCOM instruction with the only difference that the top data register is popped after the comparison is completed. This instruction is used when the value in ST(0) would no longer be needed for further computation after the comparison has been performed. See the FCOM instruction for other details. (The FICOMP instruction must be used to compare the value in ST(0) to an integer in memory and POP the TOP data register).

The popped value is not stored anywhere. The FPU simply modifies the tag of the current TOP register to "empty" in the Tag Word, and increments the TOP field in the Status Word.

An example of using the FCOMP instruction would be when a temporary value has been stored as a REAL10 for later use in comparisons. Memory values in that format are not available for direct comparison like the REAL4 and REAL8 values and must be loaded to the FPU. If a comparison between that temporary value and the value in ST(2) would be required,

 fld temp_var ;load the REAL10 value of temp_var
              ;temp_var now located in ST(0)
 fcomp st(3)  ;compare it to the value in ST(3)
              ;which was formerly ST(2) before loading temp_var
              ;and then discard the temp_var value from the FPU and
              ;re-establish the previous order of values in the FPU registers
 fstsw ax     ;retrieve result and proceed as required
              ;(see the FCOM example code)

FCOMPP (Compare ST(0) to ST(1) and POP both registers)

Syntax:    fcompp (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction is the same as the FCOM and FCOMP instructions with the following two differences:
- ST(0) can only be compared to ST(1) and
- both ST(0) and ST(1) are popped after the comparison is completed.

This instruction is used when both the values in ST(0) and in ST(1) would no longer be needed in FPU registers for further computation.

An example of using the FCOMPP instruction would be when a recursive computation is performed until the difference between the current value and the previous one must meet a given criteria. The following typical code assumes global variables for the criteria and for the temporary storage of previous values. It also assumes that the current value is in ST(0).

.data

   criteria   dt 3.3333e-15 ;could be initialized or set by the program
   temp_var   dt ?          ;could be initialized or set by the program

.code

   fld   temp_var    ;load previous value
                     ;=> ST(0)=previous value, ST(1)=current value
   fsub  st,st(1)    ;difference with current value
   fabs              ;get the absolute value of the difference
   fld   criteria    ;load the criteria
                     ;=> ST(0)=criteria, ST(1)=abs(difference), ST(2)=current value
   fcompp            ;compare the criteria to the difference
                     ;and discard both values from the FPU
                     ;=> ST(0)=current value
   fstsw ax          ;retrieve comparison result in the AX register
   fwait             ;insure the previous instruction is completed
   sahf              ;transfer the condition codes to the CPU's flag register

;In this type of code, the computed values should already have been verified
;to be valid numbers. Their difference should thus be a valid number, as well
;as the criteria. Therefore no need to check for an indeterminate comparison.

   ja    criteria_greater ;criteria was ST(0) for comparison
   jb    criteria_lower
   jz    criteria_equal

FUCOM (Unordered Compare ST(0) to a floating point value)

Syntax:    fucom (no operand)
           fucom Src

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction is identical to the FCOM instruction with the only difference that the Invalid operation exception will be detected only if one or both operands are an SNAN type of NAN. The QNANs would not result in an invalid operation but would still set the C0, C1 and C3 condition bits of the Status Word to 1 (undefined comparison). See the FCOM instruction for other details.

This specialized instruction could be of use for advanced programming only if SNANs and QNANs features were implemented. A similar advanced instruction is not available for comparing ST(0) with integer values.

FUCOMP (Unordered Compare ST(0) to a floating point value and POP ST(0))

Syntax:    fucomp (no operand)
           fucomp Src

Exception flags: Invalid operation, Denormalized value
This instruction is identical to the FCOMP instruction with the only difference that the Invalid operation exception will be detected only if one or both operands are an SNAN type of NAN. The QNANs would not result in an invalid operation but would still set the C0, C1 and C3 condition bits of the Status Word to 1 (undefined comparison). See the FCOMP instruction for other details.

This specialized instruction could be of use for advanced programming only if SNANs and QNANs features were implemented. A similar advanced instruction is not available for comparing ST(0) with integer values.

FUCOMPP (Unordered Compare ST(0) to ST(1) and POP both registers)

Syntax:    fucompp (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction is identical to the FCOMPP instruction with the only difference that the Invalid operation exception will be detected only if one or both operands are an SNAN type of NAN. The QNANs would not result in an invalid operation but would still set the C0, C1 and C3 condition bits of the Status Word to 1 (undefined comparison). See the FCOMPP instruction for other details.

This specialized instruction could be of use for advanced programming only if SNANs and QNANs features were implemented.

FCOMI (Compare ST(0) to ST(i) and set CPU flags)

Syntax:    fcomi st,st(i)

Exception flags: Stack Fault, Invalid operation

Encoding:  DB  F0+i
Note: This instruction is valid only for the Pentium Pro and subsequent processors. It may not be supported by some assemblers (for MASM, the .686 directive must be used). The encoding is provided to facilitate hard-coding of this instruction if it is not supported by the assembler.
This instruction compares the content of ST(0) and ST(i), and then sets the ZF, PF and CF flags of the CPU's flag register according to the result of the comparison as follows:
                          ZF   PF   CF
     If ST(0) > ST(i)      0    0    0
     If ST(0) < ST(i)      0    0    1
     If ST(0) = ST(i)      1    0    0
     If ST(0) ? ST(i)      1    1    1
The condition codes field of the Status Word is not modified.

An Invalid operation exception is detected if either register is empty or contains a NAN, setting the related flag in the Status Word. The result would be indeterminate and return ZF=1, PF=1 and CF=1 as indicated above. (Values of INFINITY will be treated as valid operands and yield a valid result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) (or the data register specified as the source) is empty, setting the related flag in the Status Word.

Compared to the FCOM instruction, FCOMI eliminates the need to transfer the Status Word to AX, and then transfer the condition codes from AH to the CPU's flag register prior to conditional jumps. However, FCOMI does not allow comparisons between ST(0) and values in memory.

The following example hard-codes the instruction for comparing ST(0) to ST(2).

 db   0dbh, 0f0h+2  ;encoding for fcomi st,st(2)
                    ;when not supported by the assembler
 fwait              ;insure the instruction is completed
 jpe  error_handler ;the comparison was indeterminate
                    ;this condition should be verified first
                    ;then only two of the next three conditional jumps
                    ;should become necessary, in whatever order is preferred,
                    ;the third one being replaced by code to handle that case
 ja  st0_greater    ;when all flags are 0
 jb  st0_lower      ;only the CF flag would be set if no error
 jz  both_equal     ;only the ZF flag would be set if no error
If these newer instructions are not supported by the assembler, macros could be prepared to hard-code them as above. An example of such a macro would be as follows:
   fcomist MACRO i
      db    0dbh,0f0h+i
   ENDM
which could then be used in the above example code as follows:
   fcomist 2

FCOMIP (Compare ST(0) to ST(i) and set CPU flags and Pop ST(0))

Syntax:    fcomip st,st(i)

Exception flags: Stack Fault, Invalid operation

Encoding:  DF  F0+i
Note: This instruction is valid only for the Pentium Pro and subsequent processors. It may not be supported by some assemblers (for MASM, the .686 directive must be used). The encoding is provided to facilitate hard-coding of this instruction if it is not supported by the assembler.
This instruction is the same as the FCOMI instruction with the only difference that the top data register is popped after the comparison is completed. This instruction is used when the value in ST(0) would no longer be needed for further computation after the comparison has been performed. See the FCOMI instruction for other details.

FUCOMI (Unordered Compare ST(0) to ST(i) and set CPU flags)

Syntax:    fucomi st,st(i)

Exception flags: Stack Fault, Invalid operation

Encoding:  DB E8+i
Note: This instruction is valid only for the Pentium Pro and subsequent processors. It may not be supported by some assemblers (for MASM, the .686 directive must be used). The encoding is provided to facilitate hard-coding of this instruction if it is not supported by the assembler.
This instruction is the same as the FCOMI instruction with the only difference that the Invalid operation exception will be detected only if one or both operands are an SNAN type of NAN. The QNANs would not result in an invalid operation but would still set the ZF, PF and CF flags of the CPU's flag register (undefined comparison). See the FCOMI instruction for other details.

FUCOMIP (Unordered Compare ST(0) to ST(i) and set CPU flags and Pop ST(0))

Syntax:    fucomip st,st(i)

Exception flags: Stack Fault, Invalid operation

Encoding:  DF E8+i
Note: This instruction is valid only for the Pentium Pro and subsequent processors. It may not be supported by some assemblers (for MASM, the .686 directive must be used). The encoding is provided to facilitate hard-coding of this instruction if it is not supported by the assembler.
This instruction is the same as the FUCOMI instruction with the only difference that the top data register is popped after the comparison is completed. This instruction is used when the value in ST(0) would no longer be needed for further computation after the comparison has been performed. See the FUCOMI instruction for other details.

FICOM (Compare ST(0) to an integer in memory)

Syntax:    ficom Src

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction performs a signed comparison between the value in the TOP data register ST(0) and the integer value from the specified source (Src). The source can be the memory address of either a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FCOM instruction must be used to compare the value in ST(0) to floating point values).
Note that ST(0) cannot be compared to a 64-bit QWORD integer in memory. If that becomes necessary, the QWORD integer value must first be loaded to the FPU and the FCOM or FCOMP instruction used.
The values of ST(0) and of the source are not modified by this instruction.

The result of the comparison is reported in the condition codes field of the Status Word as follows (the C1 bit is not used and the C2 bit was not used in early FPUs):

                           C3   C2   C0
     If ST(0) > source      0    0    0
     If ST(0) < source      0    0    1
     If ST(0) = source      1    0    0
     If ST(0) ? source      1    1    1
An Invalid operation exception is detected if ST(0) is empty, or if its value is a NAN, setting the related flag in the Status Word. The result would be indeterminate and return C3=1, C2=1 and C0=1 as indicated above. (Values of INFINITY in ST(0) will be treated as valid operands and yield a valid result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

When the value in ST(0) is a denormalized number, a Denormal exception is detected, setting the related flag in the Status Word. The comparison would still yield a valid result.

The signed comparison means that positive values are larger than negative values, including the values of INFINITY which can be positive or negative. The lowest of all valid values in ST(0) would thus be -INFINITY and the greatest would be +INFINITY when used in comparisons.

When the Status Word containing the result of the comparison is stored in AX, the C3, C2 and C0 bits can be transferred to the CPU's flag register (using the sahf instruction) as the ZF, PF and CF flags respectively. This makes it easier for branching based on the result. Following would be typical code after a comparison with an integer in memory. This example makes use of the possibility of pushing immediate values on the stack.

 pushd 125         ;push the immediate value as a DWORD
 ficom dword ptr[esp] ;compare ST(0) to the integer value on the stack
 fstsw [esp]       ;overwrite the pushed value with the Status Word
 fwait             ;insure the previous instruction is completed
 pop  eax          ;retrieve the Status Word in AX and clean the stack
 sahf              ;transfer the condition codes to the CPU's flag register
 jpe error_handler ;the comparison was indeterminate
                   ;this condition should be verified first
                   ;then only two of the next three conditional jumps
                   ;should become necessary, in whatever order is preferred,
                   ;the third jump being replaced by code to handle that case
 ja    st0_greater ;when all flags are 0
 jb    st0_lower   ;only the C0 bit (CF flag) would be set if no error
 jz    both_equal  ;only the C3 bit (ZF flag) would be set if no error

FICOMP (Compare ST(0) to an integer in memory and POP ST(0))

Syntax:    ficomp Src

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction is the same as the FICOM instruction with the only difference that the top data register is popped after the comparison is completed. This instruction is used when the value in ST(0) would no longer be needed for further computation after the comparison has been performed. See the FICOM instruction for other details. (The FCOMP instruction must be used to compare the value in ST(0) to a floating point value and POP the TOP data register).

The popped value is not stored anywhere. The FPU simply modifies the tag of the current TOP register to "empty" in the Tag Word, and increments the TOP field in the Status Word.

FTST (Test ST(0) by comparing it to +0.0)

Syntax:    ftst (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value
This instruction performs a signed comparison between the value in the TOP data register ST(0) and +0.0. The value of ST(0) is not modified by this instruction.

The result of the comparison is reported in the condition codes field of the Status Word as follows (the C1 bit is not used and the C2 bit was not used in early FPUs):

                      C3   C2   C0
     If ST(0) > 0      0    0    0
     If ST(0) < 0      0    0    1
     If ST(0) = 0      1    0    0
     If ST(0) ? 0      1    1    1
An Invalid operation exception is detected if ST(0) is empty, or if its value is a NAN, setting the related flag in the Status Word. The result would be indeterminate and return C3=1, C2=1 and C0=1 as indicated above. (Values of INFINITY will be treated as valid operands and yield a valid result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

When the value of ST(0) is a denormalized number, a Denormal exception is detected, setting the related flag in the Status Word. The comparison would still yield a valid result.

A value of -0.0 in ST(0)would be treated as equal to a +0.0 value.

This instruction is used primarily to determine if the value in ST(0) is positive, or negative, or equal to zero, if it is a valid floating point value. For example, such a test should be performed before attempting to compute the logarithm of a number or extract its square root. Following would be typical code with this instruction.

 ftst              ;compare the value of ST(0) to +0.0
 fstsw ax          ;copy the Status Word containing the result to AX
 fwait             ;insure the previous instruction is completed
 sahf              ;transfer the condition codes to the CPU's flag register
 jpe error_handler ;the comparison was indeterminate
                   ;this condition should be verified first
                   ;then only two of the next three conditional jumps
                   ;should become necessary, in whatever order is preferred,
                   ;the third jump being replaced by code to handle that case
 ja   st0_positive ;when all flags are 0
 jb   st0_negative ;only the C0 bit (CF flag) would be set if no error
 jz   st0_zero     ;only the C3 bit (ZF flag) would be set if no error

FXAM (Examine the content of ST(0))

Syntax:    fxam (no operand)

Exception flags: None
This instruction examines the content of ST(0) and the type of value is reported in the condition codes C3, C2 and C0 of the Status Word as indicated below. In addition, the C1 bit would be the same as the sign bit of the value in ST(0).
    Content of ST(0)       C3        C2        C0
      Unsupported           0         0         0
          NAN               0         0         1
   Normal finite number     0         1         0
        Infinity            0         1         1
          Zero              1         0         0
         Empty              1         0         1
   Denormalized number      1         1         0

The following example code would examine the content of a temporary variable to determine the action required by the ensuing code according to the result. The condition codes are transferred to the CPU's flag register for the main conditional jumps (C3=ZF, C2=PF, C0=CF). The action required for each case will then depend on the purpose of the examination and is left blank. In most cases, only a few of the cases may require special action.

   fld   temp_var ;load the temporary variable to the FPU
   fxam           ;examine it
   fstsw ax       ;copy the content of the Status Word to AX
   fwait          ;insure the last instruction is completed
   sahf           ;copy the C3/C2/C0 condition codes to the ZF/PF/CF flags
   jz    C3is1    ;either Zero, Empty or Denormalized if C3=1
   jpe   C2is1    ;either normal or infinity if C3=0 and C2=1
   jc    isNAN    ;would be NAN if C3=0, C2=0 and C0=1
   ..........     ;code for the case of Unsupported, no need to check sign

isNAN:
   ..........     ;code for the case of a NAN, no need to check the sign

C2is1:
   jc    isINFINITY ;would be Infinity if C3=0, C2=1 and C0=1
                  ;this leaves the case for a Normal finite number
   test  ah,2     ;test for the sign which is in bit1 of AH
   jnz   negNORMAL
   ..........     ;code for the case of a positive Normal finite number

negNORMAL:
   ..........     ;code for the case of a negative Normal finite number

isINFINITY:
   test  ah,2     ;test for the sign which is in bit1 of AH
   jnz   negINFINITY
   ..........     ;code for the case of a positive Infinity

negINFINITY:
   ..........     ;code for the case of a negative Infinity

C3is1:
   jc    isEMPTY  ;would be Empty if C3=1 and C0=1
   jpe   isDENORMAL ;would be a Denormalized number if C3=1, C0=0 and C2=1
                  ;this leaves the case for a Zero value
   ..........     ;code for the case of a Zero value, no need to check sign

isEMPTY:
   ..........     ;code for the case of an Empty register
                  ;which does not apply in this example because
                  ;ST(0) was loaded with a value from memory

isDENORMAL:
   test ah,2     ;test for the sign which is in bit1 of AH
   jnz   negDENORMAL
   ..........    ;code for the case of a positive Denormalized number

negDENORMAL:
   ..........    ;code for the case of a negative Denormalized number

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 8

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 8
Arithmetic instructions - with REAL numbers

The FPU instructions covered in this chapter perform arithmetic operations on floating point values. The value in the FPU's TOP data register ST(0) must always be one of the operands, whether explicitly or implicitly.

The arithmetic instructions covered in this document are (in alphabetical order):

FABS     ABSolute value of ST(0)

FADD     ADD two floating point values

FADDP    ADD two floating point values and Pop ST(0)

FCHS     CHange the Sign of ST(0)

FDIV     DIVide two floating point values

FDIVP    DIVide two floating point values and Pop ST(0)

FDIVR    DIVide in Reverse two floating point values

FDIVRP   DIVide in Reverse two floating point values and Pop ST(0)

FMUL     MULtiply two floating point values

FMULP    MULtiply two floating point values and Pop ST(0)

FRNDINT  RouND ST(0) to an INTeger

FSQRT    Square RooT of ST(0)

FSUB     SUBtract two floating point values

FSUBP    SUBtract two floating point values and Pop ST(0)

FSUBR    SUBtract in Reverse two floating point values

FSUBRP   SUBtract in Reverse two floating point values and Pop ST(0)

FADD (Add two floating point values)
Syntax:    fadd Src
           fadd Dest,Src
Note: FADD without operands can also be used with the MASM assembler but such an instruction is coded as FADDP ST(1),ST(0).
Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision

This instruction performs a signed addition of the floating point values from the source (Src) and the destination (Dest) and overwrites the content of the destination data register with the result. If only the source is specified, the TOP data register ST(0) is the implied destination and the source must be the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If both the source and destination are specified, they must both be FPU data registers and one of them must be the TOP data register ST(0). (The FIADD instruction must be used to add an integer located in memory to the value in ST(0)).

Note that a REAL10 in memory cannot be added directly to ST(0). If such an addition becomes necessary, the REAL10 value must first be loaded to the FPU.
An Invalid operation exception is detected if a specified data register is empty, or if either of the two values is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield an INFINITY result without any exception being detected, unless both values are INFINITY of opposite sign.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The addition would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

If a REAL4 or REAL8 value in memory is specified as the source, it is first converted to the REAL10 format before the addition. If that value is not an exact number in binary, the precision of the result will only be equivalent to the precision of the source.

Examples of use:

fadd real8_var       ;add the value of the real8_var variable to ST(0)
fadd dword ptr [eax] ;add the REAL4 value pointed to by EAX to ST(0)
fadd st,st           ;double the value of ST(0)
fadd st,st(3)        ;add the values of ST(0) and ST(3), result in ST(0)
fadd st(3),st        ;add the values of ST(0) and ST(3), result in ST(3)

FADDP (Add two floating point values and Pop ST(0))
Syntax:    faddp st(i),st

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision
This instruction performs a signed addition of the content from the ST(i) and ST(0) registers. The result then overwrites the content of the ST(i) register and the TOP data register is popped.

An Invalid operation exception is detected if a specified data register is empty, or if its value is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield an INFINITY result without any exception being detected, unless both values are INFINITY of opposite sign.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The addition would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow

This instruction is used when the value in ST(0) would no longer be needed for further computation. Examples of use could be as follows.

Example 1: Adding to ST(0) a temporary REAL10 value located in memory which needs to be loaded to the FPU for such an operation.

fld   real10_var  ;load the temporary value
                  ;-> ST(0)=real10_var, ST(1)=previous ST(0)
faddp st(1),st    ;add the previous ST(0) and the real10_var and discard it
                  ;-> ST(0)=(previous ST(0) + real10_var)
Note that MASM would also accept fadd (no operand) instead of the faddp st(1),st instruction.
Example 2: Computing a sum of squares from an array of REAL8 values
lea   edx,real8_array ;use EDX as a pointer to the array
mov   ecx,array_items ;use ECX as counter for the number of items to process
fldz                  ;initialize a register to accumulate the squared values
                      ;-> ST(0)=accumulator=0
@@:
fld   qword ptr [edx] ;load the next REAL8 value
                      ;-> ST(0)=REAL value, ST(1)=accumulator
fmul  st,st           ;square it
                      ;-> ST(0)=REAL value squared, ST(1)=accumulator
faddp st(1),st        ;add to the accumulator and discard the squared value
                      ;-> ST(0)=accumulator+squared REAL value
add   edx,8           ;adjust the pointer to the next REAL8 item
dec   ecx             ;decrement the counter
jnz   @B              ;continue until all items are processed

                      ;-> ST(0)=the sum of squares

FMUL (Multiply two floating point values)
Syntax:    fmul Src
           fmul Dest,Src
Note: FMUL without operands can also be used with the MASM assembler but such an instruction is coded as FMULP ST(1),ST(0).
Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision
This instruction performs a signed multiplication of the floating point values from the source (Src) and the destination (Dest) and overwrites the content of the destination data register with the result; the value of the source remains unchanged. If only the source is specified, the TOP data register ST(0) is the implied destination and the source must be the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If both the source and destination are specified, they must both be FPU data registers and one of them must be the TOP data register ST(0). (The FIMUL instruction must be used to multiply an integer located in memory with the value in ST(0)).
Note that a REAL10 in memory cannot be multiplied directly with ST(0). If such an multiplication becomes necessary, the REAL10 value must first be loaded to the FPU.
An Invalid operation exception is detected if a specified data register is empty, or if either of the two values is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield an INFINITY result without any exception being detected, unless the other operand has a value of zero.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The multiplication would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

If a REAL4 or REAL8 value in memory is specified as the source, it is first converted to the REAL10 format before the addition. If that value is not an exact number in binary, the precision of the result will only be equivalent to the precision of the source.

Examples of use:

fmul real8_var         ;multiply ST(0) by the real8_var variable
fmul dword ptr [eax]   ;multiply ST(0) by the REAL4 pointed to by EAX
fmul qword ptr [esi+8] ;multiply ST(0) by the REAL8 pointed to by ESI+8
fmul st,st             ;square the value of ST(0)
fmul st,st(3)          ;multiply ST(3) and ST(0), result in ST(0)
fmul st(3),st          ;multiply ST(3) and ST(0), result in ST(3)

FMULP (Multiply two floating point values and Pop ST(0))
Syntax:    fmulp st(i),st

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision
This instruction performs a signed multiplication of the contents from the ST(i) and ST(0) data registers. The result then overwrites the content of the ST(i) register and the TOP data register is popped.

An Invalid operation exception is detected if a specified data register is empty, or if its value is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield an INFINITY result without any exception being detected, unless the other operand has a value of zero.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The multiplication would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

This instruction is used when the value in ST(0) would no longer be needed for further computation. An example would be when the value of a data register needs to be multiplied by a temporary REAL10 located in memory (or by one of the constants hard-coded on the FPU). The following code assumes that the value in ST(2) needs to be multiplied by the value of PI.

fldpi          ;load the hard-coded PI constant with 64 bits of precision
               ;-> ST(0)=PI, ST(3)=previous ST(2)
fmulp st(3),st ;multiply, overwrite ST(3) with result, then discard PI
               ;-> ST(2)=previous ST(2) multiplied by PI
Note that MASM would also accept fmul (no operand) instead of an fmulp st(1),st instruction.

FSUB (Subtract two floating point values)
Syntax:    fsub Src
           fsub Dest,Src
Note: FSUB without operands can also be used with the MASM assembler but such an instruction is coded as FSUBP ST(1),ST(0).
Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision
This instruction performs a signed subtraction of the floating point value of the source (Src) operand from the value of the destination (Dest) register operand and overwrites the content of the destination data register with the result; the value of the source remains unchanged. If only the source is specified, the TOP data register ST(0) is the implied destination and the source must be the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If both the source and destination are specified, they must both be FPU data registers and one of them must be the TOP data register ST(0). (The FISUB instruction must be used to subtract an integer located in memory from the value in ST(0)).
Note that a REAL10 in memory cannot be subtracted directly from ST(0). If such a subtraction becomes necessary, the REAL10 value must first be loaded to the FPU.
An Invalid operation exception is detected if a specified data register is empty, or if either of the two values is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield a signed INFINITY result without any exception being detected, unless both values are INFINITY of the same sign.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The subtraction would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

If a REAL4 or REAL8 value in memory is specified as the source, it is first converted to the REAL10 format before the subtraction. If that value is not an exact number in binary, the precision of the result will only be equivalent to the precision of the source.

Examples of use:

fsub real8_var      ;subtract the value of the real8_var variable from ST(0)
fsub dword ptr[eax] ;subtract the REAL4 value pointed to by EAX from ST(0)
fsub st,st          ;would result in a value of 0.0 in ST(0)
fsub st,st(3)       ;subtract the value of ST(3) from ST(0), result in ST(0)
fsub st(3),st       ;subtract the value of ST(0) from ST(3), result in ST(3)

FSUBP (Subtract two floating point values and pop ST(0))
Syntax:    fsubp st(i),st

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision

This instruction performs a signed subtraction of the content of the ST(0) data register from the content of the ST(i) data register. The result then overwrites the content of the ST(i) register and the TOP data register is popped.

An Invalid operation exception is detected if a specified data register is empty, or if either of the two values is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield a signed INFINITY result without any exception being detected, unless both values are INFINITY of the same sign.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The subtraction would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

This instruction is used when the value in ST(0) would no longer be needed for further computation. An example of use could be when subtracting from ST(0) a temporary REAL10 value located in memory which needs to be loaded to the FPU for such an operation.

fld   real10_var ;load the temporary value
                 ;-> ST(0)=real10_var, ST(1)=original ST(0)
fsubp st(1),st   ;subtract real10_var from the original ST(0) and discard it,
                 ;-> ST(0)=(original ST(0) - real10_var)
Note that MASM would also accept fsub (no operand) instead of the fsubp st(1),st instruction.

FSUBR (Subtract in reverse two floating point values)
Syntax:    fsubr Src
           fsubr Dest,Src
Note: FSUBR without operands can also be used with the MASM assembler but such an instruction is coded as FSUBRP ST(1),ST(0).
Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision
This instruction performs a signed subtraction of the content of the destination (Dest) register operand from the content of the source (Src) operand and overwrites the content of the destination data register with the result; the value of the source remains unchanged. If only the source is specified, the TOP data register ST(0) is the implied destination and the source must be the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If both the source and destination are specified, they must both be FPU data registers and one of them must be the TOP data register ST(0). (The FISUBR instruction must be used to subtract ST(0) from the value of an integer located in memory).
Note that a REAL10 located in memory cannot be used with this instruction. If such an subtraction becomes necessary, the REAL10 value must first be loaded to the FPU.
An Invalid operation exception is detected if a specified data register is empty, or if either of the two values is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield a signed INFINITY result without any exception being detected, unless both values are INFINITY of the same sign.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The subtraction would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

If a REAL4 or REAL8 value in memory is specified as the source, it is first converted to the REAL10 format before the subtraction. If that value is not an exact number in binary, the precision of the result will only be equivalent to the precision of the source.

Examples of use:

fsubr real8_var      ;subtract ST(0) from the value of the real8_var variable
                     ;and store the result in ST(0)
fsubr dword ptr[eax] ;subtract ST(0) from the REAL4 value pointed to by EAX
                     ;and store the result in ST(0)
fsubr st,st          ;would result in a value of 0.0 in ST(0)
fsubr st,st(3)       ;subtract the value of ST(0) from ST(3), result in ST(0)
                     ;ST(3) remains unchanged
fsubr st(3),st       ;subtract the value of ST(3) from ST(0), result in ST(3)
                     ;ST(0) remains unchanged

FSUBRP (Subtract in reverse two floating point values and pop ST(0))
Syntax:    fsubrp st(i),st

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision

This instruction performs a signed subtraction of the content of the ST(i) data register from the content of ST(0) and overwrites the content of the ST(i) register with the result. The TOP data register is then popped.

An Invalid operation exception is detected if a specified data register is empty, or if either of the two values is a NAN, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (Values of INFINITY will be treated as valid numbers and yield a signed INFINITY result without any exception being detected, unless both values are INFINITY of the same sign.)

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The subtraction would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

This instruction is used when the value in ST(0) would no longer be needed for further computation. An example of use could be when a subtraction of ST(0) from a temporary REAL10 value located in memory is needed, which requires loading it to the FPU for such an operation.

fld   real10_var ;load the temporary value
                 ;-> ST(0)=real10_var, ST(1)=original ST(0)
fsubrp st(1),st  ;subtract the original ST(0) from real10_var and discard it
                 ;-> ST(0)=(temporary value - original ST(0))
Note that MASM would also accept fsubr (no operand) instead of the fsubrp st(1),st instruction.

FDIV (Divide two floating point values)
Syntax:    fdiv Src
           fdiv Dest,Src
Note: FDIV without operands can also be used with the MASM assembler but such an instruction is coded as FDIVP ST(1),ST(0).
Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision, Zero divide
This instruction performs a signed division of the content of the destination (Dest) register operand by the content of the source (Src) operand and overwrites the content of the destination data register with the result; the value of the source remains unchanged. If only the source is specified, the TOP data register ST(0) is the implied destination and the source must be the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If both the source and destination are specified, they must both be FPU data registers and one of them must be the TOP data register ST(0). (The FIDIV instruction must be used to divide the value in ST(0) by an integer located in memory).
Note that ST(0) cannot be divided directly by a REAL10 in memory. If such a division becomes necessary, the REAL10 value must first be loaded to the FPU.
An Invalid operation exception is detected if a specified data register is empty, if either of the two values is a NAN, if the content of the destination register is INFINITY, or if both values are zero, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (A value of INFINITY will be treated as a valid number for the source and yield a signed zero result without any exception being detected, unless both values are INFINITY regardless of sign).

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The division would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

A Zero divide exception will be detected if the value of the source operand is zero. It would yield a signed INFINITY result (unless the value of the destination is also zero which would be an invalid operation).

If a REAL4 or REAL8 value in memory is specified as the source, it is first converted to the REAL10 format before the subtraction. If that value is not an exact number in binary, the precision of the result will only be equivalent to the precision of the source.

Examples of use:

fdiv real8_var       ;divide ST(0) by the value of the real8_var variable
fdiv dword ptr [eax] ;divide ST(0) by the REAL4 value pointed to by EAX
fdiv st,st           ;would result in a value of 1.0 in ST(0)
fdiv st,st(3)        ;divide ST(0) by the value of ST(3), result in ST(0)
fdiv st(3),st        ;divide ST(3) by the value of ST(0), result in ST(3)

FDIVP (Divide two floating point values and pop ST(0))
Syntax:    fdivp st(i),st

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision, Zero divide
This instruction performs a signed division of the content of the ST(i) data register by the content of the ST(0) data register. The result then overwrites the content of the ST(i) register and the TOP data register is popped.

An Invalid operation exception is detected if a specified data register is empty, if either of the two values is a NAN, if the content of the ST(i) register is INFINITY, or if both values are zero, setting the related flag in the Status Word. The ST(i) register would be overwritten with the INDEFINITE value. (A value of INFINITY will be treated as a valid number for ST(0) and yield a signed zero result without any exception being detected, unless both values are INFINITY regardless of sign).

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The division would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

A Zero divide exception will be detected if the value of ST(0) is zero. It would yield a signed INFINITY result (unless the value of ST(i) is also zero which would be an invalid operation).

This instruction is used when the value in ST(0) would no longer be needed for further computation. An example of use could be when a division of ST(0) by a temporary REAL10 value located in memory is needed, which requires loading it to the FPU for such an operation.

fld   real10_var ;load the temporary value
                 ;-> ST(0)=real10_var, ST(1)=original ST(0)
fdivp st(1),st   ;divide the original ST(0) by real10_var and discard it
                 ;-> ST(0)=(original ST(0) ÷ temporary value)
Note that MASM would also accept fdiv (no operand) instead of the fdivp st(1),st instruction.

FDIVR (Reverse divide two floating point values)

Syntax:    fdivr Src
           fdivr Dest,Src
Note: FDIVR without operands can also be used with the MASM assembler but such an instruction is coded as FDIVRP ST(1),ST(0).
Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision, Zero divide
This instruction performs a signed division of the content of the source (Src) operand by the content of the destination (Dest) register operand and overwrites the content of the destination data register with the result; the value of the source remains unchanged. If only the source is specified, the TOP data register ST(0) is the implied destination and the source must be the memory address of a REAL4 or REAL8 value (see Chap.2 for addressing modes of real numbers). If both the source and destination are specified, they must both be FPU data registers and one of them must be the TOP data register ST(0). (The FIDIVR instruction must be used to divide the value of an integer located in memory by ST(0)).
Note that a REAL10 in memory cannot be used directly as the source operand. If such a division becomes necessary, the REAL10 value must first be loaded to the FPU.
An Invalid operation exception is detected if a specified data register is empty, if either of the two values is a NAN, if the content of the source operand is INFINITY, or if both values are zero, setting the related flag in the Status Word. The destination register would be overwritten with the INDEFINITE value. (A value of INFINITY will be treated as a valid number for the destination register and yield a signed zero result without any exception being detected, unless both values are INFINITY regardless of sign).

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The division would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

A Zero divide exception will be detected if the value of the destination register is zero. It would yield a signed INFINITY result (unless the value of the source operand is also zero which would be an invalid operation).

If a REAL4 or REAL8 value in memory is specified as the source, it is first converted to the REAL10 format before the subtraction. If that value is not an exact number in binary, the precision of the result will only be equivalent to the precision of the source.

Examples of use:

fdivr real8_var       ;divide the value of the real8_var variable by ST(0)
                      ;and store the result in ST(0)
fdivr dword ptr [eax] ;divide the REAL4 value pointed to by EAX by ST(0)
                      ;and store the result in ST(0)
fdivr st,st           ;would result in a value of 1.0 in ST(0)
fdivr st,st(3)        ;divide ST(3) by the value of ST(0), result in ST(0)
                      ;ST(3) remains unchanged
fdivr st(3),st        ;divide ST(0) by the value of ST(3), result in ST(3)
                      ;ST(0) remains unchanged

FDIVRP (Reverse divide two floating point values and pop ST(0))
Syntax:    fdivrp st(i),st

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Underflow, Precision, Zero divide
This instruction performs a signed division of the content of the ST(0) data register by the content of the ST(i) data register. The result then overwrites the content of the ST(i) register and the TOP data register is popped.

An Invalid operation exception is detected if a specified data register is empty, if one of the two values is a NAN, if the content of the ST(0) register is INFINITY, or if both values are zero, setting the related flag in the Status Word. The ST(i) register would be overwritten with the INDEFINITE value. (A value of INFINITY will be treated as a valid number for ST(i) and yield a signed zero result without any exception being detected, unless both values are INFINITY regardless of sign).

A Stack Fault exception is also detected if a data register specified as an operand is empty, setting the related flag in the Status Word.

A Denormal exception is detected when either of the two operands is a denormalized number, setting the related flag in the Status Word. The division would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow or Underflow exception will be detected if the result exceeds the range limits of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of the destination register in case of overflow.

A Zero divide exception will be detected if the value of ST(i) is zero. It would yield a signed INFINITY result (unless the value of ST(0) is also zero which would be an invalid operation).

This instruction is used when the value in ST(0) would no longer be needed for further computation. An example of use could be when the content of one of the data registers needs to be replaced by its reciprocal. ST(2) is used for the following code example.

fld1             ;load the hard-coded FPU constant 1.0
                 ;-> ST(0)=1, ST(3)=original ST(2)
fdivrp st(3),st  ;divide 1 by the original ST(2) and discard the constant
                 ;-> ST(2)=(1 ÷ original ST(2))
Note that MASM would also accept fdivr (no operand) instead of the fdivrp st(1),st instruction.

FABS (Absolute value of ST(0))
Syntax:    fabs (No operand)

Exception flags: Stack Fault, Invalid operation
This instruction simply clears the sign bit of the value in ST(0), insuring a positive value, i.e. the absolute value.

An Invalid operation exception and a Stack Fault exception are detected if ST(0) is empty, setting the related flag in the Status Word, and inserting the INDEFINITE NAN into ST(0).

There are numerous reasons for insuring that a value be positive. Examples are when it is necessary to extract its square root or compute its logarithm, which can only be performed on positive values by the FPU.

FCHS (Change the sign of ST(0))

Syntax:    fchs (No operand)

Exception flags: Stack Fault, Invalid operation

This instruction simply reverses the sign bit of the value in ST(0), changing it from positive to negative or vice versa.

An Invalid operation exception and a Stack Fault exception are detected if ST(0) is empty, setting the related flags in the Status Word, and inserting the INDEFINITE NAN into ST(0).

There are numerous reasons for changing the sign of a value. For examples, changing the sign of the logarithm of a number effectively yields the logarithm of the reciprocal of that number.

If the sign of the value in a register other than ST(0) is required, it is a simple matter to exchange the contents of that register and of ST(0), change the sign, and exchange once more. The following code changes the sign of the value in ST(2)

fxch st(2)  ;exchange the contents of ST(0) and ST(2)
            ;-> ST(0)=original ST(2), ST(2)=original ST(0)
fchs        ;change the sign of ST(0) which is the original ST(2)
fxch st(2)  ;exchange the contents of ST(0) and ST(2)
            ;-> ST(0)=original ST(0), ST(2)=original ST(2) with sign changed
If the sign of the value in ST(1) needed to be changed, the following code could also be used. However, any advantage would be very minimal, code size being the same and speed gain possibly only a fraction of a nanosecond.
fincstp   ;increment the TOP data register field in the Status Word
          ;ST(0)=>ST(7), ST(1)=>ST(0)
fchs      ;change the sign of ST(0) (formerly the ST(1) value)
fdecstp   ;decrement the TOP data register in the Status Word
          ;ST(7)=>ST(0), ST(0)=>ST(1)
          ;-> ST(1)=original ST(1) with sign changed

FSQRT (Square root of ST(0))
Syntax:    fsqrt (No operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Precision
This instruction extracts the square root of the value in ST(0) and overwrites the content of ST(0) with the result.

An Invalid operation exception is detected if ST(0) is empty or contains a negative value, setting the related flag in the Status Word, and overwriting the content of ST(0) with the INDEFINITE NAN.

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected if the content of ST(0) is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

The values of NANs, +INFINITY and -0 remain unchanged without any exception being detected.

FRNDINT (Round ST(0) to an integer)

Syntax:    frndint (No operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Precision
This instruction rounds the value of ST(0) to the nearest integral value according to the rounding mode of the RC field in the Control Word and overwrites the content of ST(0) with the result.

An Invalid operation exception and a Stack Fault exception are detected if ST(0) is empty, setting the related flag in the Status Word, and inserting the INDEFINITE NAN into ST(0).

A Denormal exception is detected if the content of ST(0) is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if the content of ST(0) is not an integral value, setting the related flag in the Status Word.

Values of INFINITY and NANs remain unchanged without any exception being detected.

Although a similar result could be obtained by storing to memory the value of ST(0) as an integer, it would have to be loaded back to the FPU if that rounded value is required for further computation. The FRNDINT is much faster and more efficient. The size of the value is also immaterial with this instruction while the range of an integer variable could possibly be exceeded.

This instruction is also useful prior to storing a value in the packed BCD format with the FBSTP instruction if a different rounding mode must be used.

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 9

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 9
Arithmetic instructions - with integer numbers

The FPU instructions covered in this chapter perform arithmetic operations with the value in the TOP data register ST(0) and integer numbers located in memory.

The arithmetic instructions covered in this document are (in alphabetical order):

FIADD     ADD an Integer located in memory to ST(0)

FIDIV     DIVide ST(0) by an Integer located in memory

FIDIVR    DIVide an Integer located in memory by ST(0)

FIMUL     MULtiply ST(0) by an Integer located in memory

FISUB     SUBtract an Integer located in memory from ST(0)

FISUBR    SUBtract ST(0) from an Integer located in memory

FIADD (Add an integer located in memory to ST(0)

Syntax:    fiadd Src

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Precision
This instruction performs a signed addition of the source (Src) integer value and the value of ST(0) and overwrites the content of ST(0) with the result. The source must be the memory address of a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FADD instruction must be used to add a REAL number to the value in ST(0)).
Note that a QWORD integer in memory cannot be added directly to ST(0). If such an addition becomes necessary, the QWORD value must first be loaded to the FPU and then added with the FADD or FADDP instruction.
An Invalid operation exception is detected if the TOP data register ST(0) is empty or is a NAN, setting the related flag in the Status Word. The INDEFINITE value would then be inserted in ST(0). (A value of INFINITY in ST(0) will be treated as a valid number and yield an INFINITY result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number, setting the related flag in the Status Word. The addition would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of ST(0).

Examples of use:

fiadd dword_var      ;add the dword_var integer variable to ST(0)
fiadd word ptr [eax] ;add the WORD value pointed to by EAX to ST(0)
If an integer in one of the CPU registers, or an immediate integer value, needs to be added to ST(0), it can be transferred to the stack and used from there. The following code is suggested for adding an immediate value of 100 to ST(0).
pushd 100  ;push the value of 100 on the stack
fiadd dword ptr[esp] ;add the DWORD located at memory address [esp] to ST(0)
fxxxx      ;another FPU instruction to insure that the addition instruction
           ;is completed before removing the integer value from the stack
pop   reg  ;pop any CPU register which can be trashed to clean the stack
           ;or use that reserved space to store a DWORD value from the FPU
           ;for retrieval into one of the CPU registers or even into memory
           ;or the stack pointer can be restored with add esp,4

FISUB (Subtract an integer located in memory from ST(0))
Syntax:    fisub Src

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Precision
This instruction performs a signed subtraction of the source (Src) integer value from the value of ST(0) and overwrites the content of ST(0) with the result. The source must be the memory address of a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FSUB instruction must be used to subtract a REAL number from the value in ST(0)).
Note that a QWORD integer in memory cannot be subtracted directly from ST(0). If such a subtraction becomes necessary, the QWORD value must first be loaded to the FPU and then subtracted with the FSUB or FSUBP instruction.
An Invalid operation exception is detected if the TOP data register ST(0) is empty or is a NAN, setting the related flag in the Status Word. The INDEFINITE value would then be inserted in ST(0). (A value of INFINITY in ST(0) will be treated as a valid number and yield an INFINITY result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number, setting the related flag in the Status Word. The subtraction would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of ST(0).

Examples of use:

fisub word_var         ;subtract the word_var integer variable from ST(0)
fisub dword ptr[esi+8] ;subtract the DWORD pointed to by [ESI+8] from ST(0)
If an integer in one of the CPU registers, or an immediate integer value, needs to be subtracted from ST(0), it can be transferred to the stack and used from there. The following code is suggested for subtracting the content of EAX from ST(0).
push  eax  ;push the content of EAX on the stack
fisub dword ptr[esp] ;subtract the DWORD at memory address [esp] from ST(0)
fxxxx      ;another FPU instruction to insure that the subtraction
           ;is completed before removing the integer value from the stack
pop   eax  ;or pop any CPU register which can be trashed to clean the stack
           ;or use that reserved space to store a DWORD value from the FPU
           ;for retrieval into one of the CPU registers or even into memory

FISUBR (Reverse subtraction of ST(0) from an integer located in memory)
Syntax:    fisubr Src

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Precision
This instruction performs a signed subtraction of the value of ST(0) from the source (Src) integer value and overwrites the content of ST(0) with the result; the value of the source remains unchanged. The source must be the memory address of a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FSUBR instruction must be used to subtract the value in ST(0) from a REAL number).
Note that ST(0) cannot be subtracted directly from a QWORD integer in memory. If such a subtraction becomes necessary, the QWORD value must first be loaded to the FPU and then the subtraction performed with the FSUBR or FSUBRP instruction.
An Invalid operation exception is detected if the TOP data register ST(0) is empty or is a NAN, setting the related flag in the Status Word. The INDEFINITE value would then be inserted in ST(0). (A value of INFINITY in ST(0) will be treated as a valid number and yield an INFINITY result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number, setting the related flag in the Status Word. The subtraction would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of ST(0).

Examples of use:

fisubr word_var     ;subtract ST(0) from the word_var integer variable
                    ;and replace the content of ST(0) with the result
fisubr dword ptr[esp]  ;subtract ST(0) from the DWORD pointed to by [ESP]
                    ;and replace the content of ST(0) with the result
                    ;(see the FISUB example for a typical use with [ESP])

FIMUL (Multiply ST(0) by an integer located in memory)
Syntax:    fimul Src

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Precision
This instruction performs a signed multiplication of the content of ST(0) by the source (Src) integer value and overwrites the content of ST(0) with the result. The source must be the memory address of a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FMUL instruction must be used to multiply the value in ST(0) by a REAL number).
Note that ST(0) cannot be multiplied directly by a QWORD integer in memory. If such a multiplication becomes necessary, the QWORD value must first be loaded to the FPU and then multiplied with the FMUL or FMULP instruction.
An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or its value is INFINITY and the source integer has a value of zero, setting the related flag in the Status Word. The INDEFINITE value would then be inserted in ST(0). (If the source integer has a non-zero value, a value of INFINITY in ST(0) will be treated as a valid number and yield an INFINITY result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number, setting the related flag in the Status Word. The multiplication would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of ST(0).

Examples of use:

fimul dword_var  ;multiply ST(0) by the dword_var integer variable
fimul dword ptr[ebp+12] ;multiply ST(0) by the DWORD pointed to by [EBP+12]
                 ;this would be typical code generated by the assembler
                 ;for multiplying ST(0) by a parameter passed to a procedure

FIDIV (Divide ST(0) by an integer located in memory)
Syntax:    fidiv Src

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision, Zero divide
This instruction performs a signed division of the content of ST(0) by the source (Src) integer value and overwrites the content of ST(0) with the result. The source must be the memory address of a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FDIV instruction must be used to divide the value in ST(0) by a REAL number).
Note that ST(0) cannot be divided directly by a QWORD integer in memory. If such a division becomes necessary, the QWORD value must first be loaded to the FPU and then the division performed with the FDIV or FDIVP instruction.
An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or if the value of both the source integer and ST(0) is zero, setting the related flag in the Status Word. The INDEFINITE value would then be inserted in ST(0). (A value of INFINITY in ST(0) will be treated as a valid number and yield an INFINITY result without any exception being detected, even if the source integer has a value of zero.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or the result is a denormalized number, setting the related flag in the Status Word. The division would still yield a valid result.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

A Zero divide exception will be detected if the source integer has a value of zero, and the value of INFINITY will overwrite the content of ST(0), unless the value of ST(0) is also zero which would be an invalid operation as indicated above. (This exception is not detected if the value of ST(0) is already INFINITY.)

Examples of use:

fidiv word_var     ;divide ST(0) by the word_var integer variable
fidiv dword ptr [ebp-8]   ;divide ST(0) by the DWORD pointed to by [EBP-8]
                   ;this would be typical code generated by the assembler
                   ;for dividing ST(0) by a LOCAL dword integer variable

FIDIVR (Reverse division of an integer located in memory by ST(0))
Syntax:    fidivr Src

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Overflow, Precision, Zero divide
This instruction performs a signed division of the source (Src) integer value by the content of ST(0) and overwrites the content of ST(0) with the result; the value of the source remains unchanged. The source must be the memory address of a 16-bit WORD or a 32-bit DWORD integer value (see Chap.2 for addressing modes of integers). (The FDIVR instruction must be used to divide a REAL number by the value in ST(0)).
Note that a QWORD integer in memory cannot be divided directly by ST(0). If such a division becomes necessary, the QWORD value must first be loaded to the FPU and then the division performed with the FDIVR or FDIVRP instruction.
An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or if the value of both the source integer and ST(0) is zero, setting the related flag in the Status Word. The INDEFINITE value would then be inserted in ST(0). (A value of INFINITY in ST(0) will be treated as a valid number and yield a zero result without any exception being detected.)

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number, setting the related flag in the Status Word. The division would yield a value of INFINITY, unless the source integer is zero which would yield a value of zero.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word and the value of INFINITY will overwrite the content of ST(0).

A Zero divide exception will be detected if ST(0) has a value of zero and the value of INFINITY will overwrite the content of ST(0), unless the integer value is also zero which would be an invalid operation as indicated above.

Examples of use:

fidivr dword_var   ;divide the dword_var integer variable by ST(0)
                   ;and replace the content of ST(0) with the result
fidivr word ptr[esi+ebx] ;divide the WORD located at [ESI+EBX] by ST(0)
                   ;and replace the content of ST(0) with the result

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 10

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 10
Trigonometric instructions

The FPU instructions covered in this chapter perform trigonometric operations with the value in the TOP data register ST(0), or with both ST(0) and ST(1).

The trigonometric instructions covered in this document are (in alphabetical order):

FCOS      COSine of the angle value in ST(0)

FPATAN    Partial ArcTANgent of the ratio ST(1)/ST(0)

FPTAN     Partial TANgent of the angle value in ST(0)

FSIN      SINe of the angle value in ST(0)

FSINCOS   SINe and COSine of the angle value in ST(0)

FSIN (Sine of the angle value in ST(0))
Syntax:    fsin (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision
This instruction computes the sine of the source angle value in ST(0) and overwrites the content of ST(0) with the result. The angle must be expressed in radians and be within the -263 to +263 range.

If the source angle value is outside the acceptable range (but not INFINITY), the C2 flag of the Status Word is set to 1 and the content of ST(0) remains unchanged, no exception being detected. (The source value can be reduced to within the acceptable range with the FPREM instruction using a divisor of 2π.)

An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or has a value of INFINITY, setting the related flag in the Status Word. The content of ST(0) would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or the result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

The relation between degrees and radians is as follows:

180 degrees = π radians

To compute the sine of an angle expressed in degrees in ST(0), the following code could be used to first convert the value in ST(0) from degrees to radians and check for errors before the FSIN instruction.

   fclex        ;clear all previous exceptions
   pushd 180    ;store the integer value of 180 on the stack
   fidiv dword ptr[esp]   ;divide the angle in degrees by 180
                ;-> ST(0)=angle in degrees/180
   fldpi        ;load the hard-coded value of π
                ;-> ST(0)=π, ST(1)=angle in degrees/180
   fmul         ;-> ST(0)=angle in degrees*π/180, => angle in radians
   fsin         ;compute the sine of the angle
                ;-> ST(0)=sin(angle) if no error
   fstsw [esp]  ;store the Status Word on the stack overwriting the 180
   fwait        ;to insure the last instruction is completed
   pop   eax    ;get the Status Word in AX (which also cleans the stack)
   shr   al,1   ;transfer the Invalid op flag (bit0 of AL) to the CF flag
   jnc   @F     ;jump if no invalid operation detected

   ........     ;insert code to handle an invalid operation
                ;-> ST(0)=INDEFINITE (angle has been trashed)

@@:
   sahf         ;transfer AH to the CPU flag register
   jpo   @F     ;jump if PF=C2=0 meaning angle value is in acceptable range

   ........     ;insert code to handle angle ouside acceptable range
                ;-> ST(0)=angle in radians unchanged

@@:             ;-> ST(0)=sin(angle) -- no error

FCOS (Cosine of the angle value in ST(0))
Syntax:    fcos (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision
This instruction computes the cosine of the source angle value in ST(0) and overwrites the content of ST(0) with the result. The angle must be expressed in radians and be within the -263 to +263 range.

If the source angle value is outside the acceptable range (but not INFINITY), the C2 flag of the Status Word is set to 1 and the content of ST(0) remains unchanged, no exception being detected. (The source value can be reduced to within the acceptable range with the FPREM instruction using a divisor of 2π.)

An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or has a value of INFINITY, setting the related flag in the Status Word. The content of ST(0) would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or the result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

The relation between degrees and radians is as follows:

180 degrees = π radians

To compute the cosine of an angle expressed in degrees in ST(0), the following code could be used to first convert the value in ST(0) from degrees to radians before the FCOS instruction. The code assumes that ST(0) contains a valid value within the acceptable range, such that no error checking is necessary.

   pushd 180    ;store the integer value of 180 on the stack
   fidiv dword ptr[esp]   ;divide the angle in degrees by 180
                ;-> ST(0)=angle in degrees/180
   fldpi        ;load the hard-coded value of π
                ;-> ST(0)=π, ST(1)=angle in degrees/180
   fmul         ;-> ST(0)=angle in degrees*π/180, => angle in radians
   add   esp,4  ;adjust the stack pointer to clean-up the pushed 180
                ;pop reg32 would also clean the stack but trash a register
   fcos         ;compute the cosine of the angle
                ;-> ST(0)=cos(angle)

FSINCOS (Sine and cosine of the angle value in ST(0))
Syntax:    fsincos (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision
This instruction computes the sine and the cosine of the source angle value in ST(0). The sine value replaces the content of ST(0), the TOP register field of the Status Word is decremented, and the cosine value is inserted into the new ST(0). The angle must be expressed in radians and be within the -263 to +263 range. (This instruction is faster than computing the sine and cosine separately with the FSIN and FCOS instructions.)

If the source angle value is outside the acceptable range (but not INFINITY), the C2 flag of the Status Word is set to 1 and the content of all data registers remains unchanged; the TOP register field of the Status Word is not modified and no exception is detected. (The source value can be reduced to within the acceptable range with the FPREM instruction using a divisor of 2π.)

An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or has a value of INFINITY, or the ST(7) data register is not empty, setting the related flag in the Status Word. The content of ST(0) would be overwritten with the INDEFINITE value, the TOP register field of the Status Word would be decremented, and the content of the new ST(0) would also be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if ST(0) is empty or if ST(7) is not empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or a result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

The relation between degrees and radians is as follows:

180 degrees = π radians

To compute the sine and cosine of an angle expressed in degrees in ST(0), the following code could be used to first convert the value in ST(0) from degrees to radians and verify that it is within the acceptable range before the FSINCOS instruction. This code does not differentiate invalid operations from a value simply out of range.

   pushd 180    ;store the integer value of 180 on the stack
   fidiv dword ptr[esp]   ;divide the angle in degrees by 180
                ;-> ST(0)=angle in degrees/180
   fldpi        ;load the hard-coded value of π
                ;-> ST(0)=π, ST(1)=angle in degrees/180
   fmul         ;-> ST(0)=angle in degrees*π/180, => angle in radians
   fst   dword ptr[esp] ;use the already reserved space on the stack
                ;to get a REAL4 version of the value in ST(0)
   fwait        ;to insure the last instruction is completed
   pop   eax    ;retrieve the REAL4 in EAX (which also cleans the stack)
   rol   eax,9  ;the 8 biased exponent bits => to the lower 8 bits of EAX
                ;(the sign bit would become the least significant bit of AH)
   cmp   al,127+64    ;the exponent bias for the REAL4 format is 127
   jb    @F     ;the value in ST(0) would be within range if AL < 127+64

   .......      ;insert code to handle the unacceptable source value
                ;if AL=255, source value was >2127 or was NAN

@@:
   fsincos      ;compute both the sine and cosine of the angle
                ;-> ST(0)=cos(angle), ST(1)=sin(angle)
;all other values in data registers would be in the ST(i+1) register

FPTAN (Partial tangent of the angle value in ST(0))

Syntax:    fptan (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision
This instruction computes the tangent of the source angle value in ST(0). The tangent value replaces the content of ST(0), the TOP register field of the Status Word is decremented, and a value of 1.0 is inserted into the new ST(0). The angle must be expressed in radians and be within the -263 to +263 range.
The extra value of 1.0 is primarily for compatibility with the early co-processors prior to the 387. The FSIN and FCOS instructions were not then available and had to be computed from the tangent value (and the acceptable range for the angle was only 0 to +π/4). It is more a nuisance than a feature with the more modern FPUs, requiring the need for two registers instead of one and an extra instruction to discard it.
If the source angle value is outside the acceptable range (but not INFINITY), the C2 flag of the Status Word is set to 1 and the content of all data registers remains unchanged; the TOP register field of the Status Word is not modified and no exception is detected. (The source value can be reduced to within the acceptable range with the FPREM instruction using a divisor of 2π.)

An Invalid operation exception is detected if the TOP data register ST(0) is empty, or is a NAN, or has a value of INFINITY, or the ST(7) data register is not empty, setting the related flag in the Status Word. The content of ST(0) would be overwritten with the INDEFINITE value, the TOP register field of the Status Word would be decremented, and the content of the new ST(0) would also be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if ST(0) is empty or if ST(7) is not empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or a result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

Although the tangent would have a value of infinity when the angle would be exactly equal to π/2, the maximum value returned by the FPTAN instruction will only be approximately 3.69*1019. This properly reflects the limit of precision of the FPU.

Because an additional value is loaded to a data register unless this instruction is executed on a value outside the acceptable range, all other values in data registers would then be in the ST(i+1) register.

The tangent of an angle expressed in radians and located in ST(0) is obtained as follows.

              ;ST(0)=angle in radians, ST(1)=xx
fptan         ;ST(0)=1.0, ST(1)=tan(angle), ST(2)=xx
fstp st       ;this pops the TOP register
              ;ST(0)=tan(angle), ST(1)=xx

FPATAN (Partial arctangent of the ratio ST(1)/ST(0))
Syntax:    fpatan (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision
This instruction computes the arctangent of the ST(1)/ST(0) ratio, overwrites the content of ST(1) with the angle value (in radians) and then POPs the TOP data register. The result will be a value in the range of -π to +π (-180 to +180 if converted to degrees) depending on the signs of the contents of ST(0) and ST(1).

The following tabulation gives the resulting values (or range of values) converted to degrees based on the contents of ST(0) and ST(1). "F" means a finite value between 0 and INFINITY.

ST(1) ST(0) | +∞ | +F | +0 | -0 | -F | -∞
+∞ | +45o | +0o | +0o | -0o | -0o | -45o
+F | +90o | +0o to +90o | +0o | -0o | -90o to -0o | -90o
+0 | +90o | +90o | +0o | -0o | -90o | -90o
-0 | +90o | +90o | +180o | -180o | -90o | -90o
-F | +90o | +90o to +180o | +180o | -180o | -180o to -90o | -90o
-∞ | +135o | +180o | +180o | -180o | -180o | -135o

An Invalid operation exception is detected if either ST(0) or ST(1) is empty or is a NAN, setting the related flag in the Status Word. The TOP data register would be POPed and the content of ST(0) (formerly ST(1)) would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if either ST(0) or ST(1) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of either ST(0) or ST(1) is a denormalized number or the result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

Because the TOP data register is POPed with this instruction, all other values in data registers now would be in the ST(i-1) register.

The arctangent can be computed in two different manners. One of them is when the actual value of the tangent is in ST(0), either from prior computations or loaded from memory. By loading a value of +1.0 followed by the FPATAN instruction, the arctangent result would be in the -90° to 0° or 0° to +90° ranges depending on the sign of the tangent value. Loading a value of -1.0 would give results in the +90° to +180° or -180° to -90° ranges. Some prior knowledge of the required ranges will be necessary in order to load the 1.0 with the proper sign. Typical code would be:

                ;ST(0)=value of tangent, ST(1)=xxx
fld1            ;ST(0)=+1.0, ST(1)=value of tangent, ST(2)=xxx
fchs            ;change to -1.0 for angle in the +90° to 180° to -90° ranges
                ;ST(0)=-1.0, ST(1)=value of tangent, ST(2)=xxx
fpatan          ;ST(0)=angle in radians, ST(1)=xxx
pushd 180       ;for converting to degrees
fimul dword ptr[esp] ;ST(0)=180*angle in radians, ST(1)=xxx
fldpi           ;ST(0)=π, ST(1)=180*angle in radians, ST(2)=xxx
fmul            ;ST(0)=180/π*angle in radians=angle in degrees, ST(1)=xxx
add   esp,4     ;adjust the stack pointer to clean-up the pushed 180
                ;pop reg32 would also clean the stack but trash a register
The other manner is when the signed dimensions of the sides of a right triangle (or Cartesian coordinates) are available. The dimension of the side opposite the angle is loaded first followed by the dimension of the side adjacent to the angle. The result will then cover the entire circle.

FPU instructions are not available to directly compute the arcsine and the arccosine. The FPATAN instruction must be used for that purpose based on the usual trigonometric equivalents:

            tan(x) = sin(x)/cos(x)
and         sin2(x) + cos2(x) = 1
Typical code to compute arcsin[sin(x)] from the sine value in ST(0) would be as follows.
                ;ST(0)=sin(x), ST(1)=zzz
fld   st        ;ST(0)=sin(x), ST(1)=sin(x), ST(2)=zzz
fmul  st,st     ;ST(0)=sin2(x), ST(1)=sin(x), ST(2)=zzz
fld1            ;ST(0)=1.0, ST(1)=sin2(x), ST(2)=sin(x), ST(3)=zzz
fsubr           ;ST(0)=1-sin2(x)=cos2(x), ST(1)=sin(x), ST(2)=zzz
fsqrt           ;ST(0)=cos(x), ST(1)=sin(x), ST(2)=zzz
fpatan          ;ST(0)=arcsin[sin(x)]=x (in radians), ST(1)=zzz
Since the derived cos(x) value would always be positive, the resulting angle would be in the -90° to 0° to +90° ranges. The sign of the derived cos(x) value would need to be changed to get an angle in the +90° to 180° to -90° ranges.

The arccosine can be computed in a similar manner. The only difference would be that the cos(x) value and the derived sin(x) value would have to be exchanged before the FPATAN instruction. If the positive sign of the sin(x) value is retained, the resulting arccosine value would be in the 0° to +90° to +180° ranges. The sign of the derived sin(x) value would need to be changed to get an angle in the -0° to -90° to -180° ranges.

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 11

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 11
Logarithmic and exponential instructions

The FPU instructions covered in this chapter perform logarithmic or exponential operations with the value in the TOP data register ST(0), or with both ST(0) and ST(1).

The logarithmic and exponential instructions covered in this document are (in alphabetical order):

F2XM1     2 to the X power Minus 1

FSCALE    SCALE ST(0) by ST(1)

FYL2X     Y*Log2X

FYL2XP1   Y*Log2(X+1)

FYL2X (Y*Log2X)
Syntax:    fyl2x (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Overflow, Zero divide, Precision
This instruction computes the logarithm base 2 of the value in the TOP data register ST(0) and multiplies it by the value in the ST(1) register. The content of ST(0) must be a non-zero positive number. The content of ST(1) is overwritten with the result and the TOP data register is POPed.

The following tabulation gives the resulting values based on the contents of ST(0) and ST(1). "F" means a finite value between 0 and INFINITY, and "Inv" means an Invalid operation.

ST(0) ST(1) | +∞ | F > +1 | +1 | +1 > F > 0 | ±0 | -F | -∞
+∞ | +∞ | +∞ | Inv | -∞ | -∞ | Inv | Inv
+F | +∞ | +F | +0 | -F | -∞ | Inv | Inv
+0 | Inv | +0 | +0 | -0 | Inv | Inv | Inv
-0 | Inv | -0 | -0 | +0 | Inv | Inv | Inv
-F | -∞ | -F | -0 | +F | +∞ | Inv | Inv
-∞ | -∞ | -∞ | Inv | +∞ | +∞ | Inv | Inv

To explain the Invalid results in the above tabulation,
log(+∞)=+∞, therefore ∞*0 is Invalid
log(+1)=0, therefore 0*∞ is Invalid
log(+0)=-∞, therefore -∞*0 is Invalid
log(negative number) is always Invalid (except -0 which is treated as +0)

An Invalid operation exception is detected if either ST(0) or ST(1) is empty, or is a NAN, or the combination of operands is indicated as "Inv" in the above tabulation, setting the related flag in the Status Word. The TOP data register would be POPed and the content of ST(0) (formerly ST(1)) would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if either ST(0) or ST(1) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of either ST(0) or ST(1) is a denormalized number or a result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow or Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

A Zero divide exception will be detected if the content of ST(0) is ±0 and the content of ST(1) is a non-zero finite number, setting the related flag in the Status Word.

Because the TOP data register is POPed with this instruction, all other values in data registers would now be in the ST(i-1) register.

This instruction is used for two primary reasons. One of them is for the first step in computing the power of any positive number, i.e. xy. This must be performed using logarithms according to the following relation:

xy = antilog[y*log(x)]

Although any logarithm base can be used for the above equation, the obvious base for computing logarithms on the FPU is 2, since it operates strictly with binary numbers. The second step in obtaining the xy value would then be to compute the antilog base 2 of the resulting y*log2(x), the above relationship becoming:

xy =2[y*log2(x)]

See the F2XM1 instruction for more details on computing powers of 2.

Typical code to compute the y*log2(x) portion if both operands are REAL numbers in memory and no error checking is required would be as follows.

                   ;ST(0)=zzz
fld   exponent_y   ;load the exponent first
                   ;->ST(0)=exponent, ST(1)=zzz
fld   number_x     ;then load the x value
                   ;->ST(0)=x value, ST(1)=exponent, ST(2)=zzz
fyl2x              ;ST(0)=y*log2(x), ST(1)=zzz

The other primary reason for using the FYL2X instruction is to compute the logarithm of a number with a base other than 2. The two most universal bases for logarithms are:

10 which are usually called common logarithms and represented simply as log(x) without specifying the base, and
e (the Napierian constant = 2.71828...) usually called natural logarithms and represented as ln(x).

The relationship between a logarithm base 2 and a logarithm using some other base b is as follows:

logbx = log2x*(log2b)-1 = log2x*logb2

The FPU has both the log(2) and ln(2) as hard-coded constants which can be loaded with the FLDLG2 and FLDLN2 instructions respectively. As an example, the following code could be used to compute the common logarithm of a positive REAL number located in memory.

fldlg2         ;ST(0)=log10(2), ST(1)=zzz
fld  real_var  ;ST(0)=real_var, ST(1)=log10(2), ST(2)=zzz
fyl2x          ;log2(real_var)*log10(2)=>ST(1) and ST(0) is POPed
               ;ST(0)=log10(real_var), ST(1)=zzz

FYL2XP1 (Y*Log2(X+1))
Syntax:    fyl2xp1 (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Overflow, Precision
This instruction computes the logarithm base 2 of the value in the TOP data register ST(0)+1 and multiplies it by the value in the ST(1) register. The content of ST(1) is overwritten with the result and the TOP data register is POPed. The content of ST(0) must be within the range of -(1-½√2) to +(1-½√2), i.e. -0.29289... to +0.29289....The value in ST(1) can range from -INFINITY to +INFINITY.
Note: The stated range for ST(0) is according to Intel's Software Developer's Manual dated 2003. If the value in ST(0) is outside the acceptable range, an exception may not be detected and the result may be undefined. Although an exception may be detected under some conditions, it should not be relied upon for all conditions. The range limits for ST(0) may have been expanded or even eliminated for newer processors.
An Invalid operation exception is detected if either ST(0) or ST(1) is empty or is a NAN, setting the related flag in the Status Word. The TOP data register would be POPed and the content of ST(0) (formerly ST(1)) would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if either ST(0) or ST(1) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of either ST(0) or ST(1) is a denormalized number or a result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow or Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

This instruction provides improved accuracy over the FYL2X instruction when the logarithm of a number very close to 1.0 is required. As an extreme example, if the logarithm of a 1+10-20 value was needed, that value is beyond the precision of the FPU and it would be rounded to a value of 1.0 resulting in a logarithm of 0. The FYL2XP1 gives a result of 1.44*10-20 , which is very small but finite.

This instruction is useful for the computation of compound interest and annuities. Most equations related to those subjects contain a factor in the form of

(1+R)n

where R is the interest rate expressed in decimal (which is usually small),
and n is the number of periods.

F2XM1 (2 to the X power Minus 1)

Syntax:    f2xm1 (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Precision
This instruction computes the exponential value of 2 to the power of the value of ST(0) and subtracts 1. The content of ST(0) is overwritten with the result. The content of ST(0) must be within the range of -1.0 to +1.0; if it is outside the acceptable range, the result is undefined but no exception is reported.

An Invalid operation exception is detected if ST(0) is empty or is a NAN, setting the related flag in the Status Word. The TOP data would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or the result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

This instruction is part of the second step in computing the power of any positive number, according to the relationship

xy =2[y*log2(x)]

However, because the exponent used for this instruction cannot have an absolute value greater than 1.0, another property of exponents must be used.

2(a+b) = 2a * 2b

where a could be the integer portion of the (a+b) value, and b would be the fractional portion. The FRNDINT instruction can be used to provide an integer portion which is then used to derive a fractional portion. The FSCALE instruction is finally used to combine the two powers of 2. Typical code to complete the computation of xy (started with the FYL2X instruction) would be as follows.

              ;ST(0)=y*log2(x), ST(1)=zzz
fld  st       ;make a second copy
              ;ST(0)=y*log2(x), ST(1)=y*log2(x), ST(2)=zzz
frndint       ;round it to an integer
              ;ST(0)=int[y*log2(x)], ST(1)=y*log2(x), ST(2)=zzz
fsub st(1),st ;this will leave only a fractional portion in ST(1)
              ;ST(0)=int[y*log2(x)], ST(1)=y*log2(x)-int[y*log2(x)], ST(2)=zzz
fxch st(1)    ;ST(0)=y*log2(x)-int[y*log2(x)], ST(1)=int[y*log2(x)], ST(2)=zzz
f2xm1         ;get the fractional power of 2 (minus 1)
              ;ST(0)=2ST(0)-1, ST(1)=int[y*log2(x)], ST(2)=zzz
fld1          ;ST(0)=1, ST(1)=2ST(0)-1, ST(2)=int[y*log2(x)], ST(3)=zzz
fadd          ;add the 1 to ST(1) and POP ST(0)
              ;ST(0)=2ST(0), ST(1)=int[y*log2(x)], ST(2)=zzz
fscale        ;add the integer in ST(1) to the exponent of ST(0)
              ;effectively multiplying the content of ST(0) by 2int
              ;and yielding the final result of xy
              ;ST(0)=xy, ST(1)=int[y*log2(x)], ST(2)=zzz
fstp st(1)    ;the content of ST(1) has become useless
              ;overwrite the content of ST(1) with the result and POP ST(0)
              ;ST(0)=xy, ST(1)=zzz

FSCALE (Scale ST(0) by ST(1))
Syntax:    fscale (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow, Overflow, Precision
This instruction adds the truncated integral value of ST(1) to the exponent portion of the value in ST(0). The content of ST(1) remains unchanged. This is effectively a multiplication (or division) of the value in ST(0) by an integral power of 2.

The following tabulation gives the resulting values based on the contents of ST(0) and ST(1). "F" means a finite value between 0 and INFINITY, and "Inv" means an Invalid operation.

ST(0) ST(1) | +∞ | +F | +0 | -0 | -F | -∞
+∞ | +∞ | +∞ | Inv | Inv | -∞ | -∞
+F | +∞ | +F | +0 | -0 | -F | -∞
+0 | +∞ | +F | +0 | -0 | -F | -∞
-0 | +∞ | +F | +0 | -0 | -F | -∞
-F | +∞ | +F | +0 | -0 | -F | -∞
-∞ | Inv | +0 | +0 | -0 | -0 | Inv

To explain the Invalid results in the above tabulation,
2+∞ = +∞, therefore ∞*0 is Invalid
2-∞ = 0, therefore 0*∞ is Invalid

An Invalid operation exception is detected if either ST(0) or ST(1) is empty, or is a NAN, or the combination of operands is indicated as "Inv" in the above tabulation, setting the related flag in the Status Word. The TOP data register would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if either ST(0) or ST(1) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number or the result is a denormalized number, setting the related flag in the Status Word.

A Precision exception will be detected if some fraction bit is lost due to rounding, setting the related flag in the Status Word.

An Underflow or Overflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

This instruction provides rapid scaling of values, up or down, by multiples of 2. A positive exponent will scale up while a negative exponent will scale down. For example, to reduce a value by a factor of 32 (i.e. 25 ), a value of -5 would be used. To apply that to an array of REAL4 numbers, the following code is suggested.

   lea  eax,array_var    ;use EAX as a pointer
   mov  ecx,array_size   ;use ECX as a counter
   pushd  -5             ;store an integer value of -5 on the stack
   fild dword ptr[esp]   ;ST(0)=-5

@@:
   fld  dword ptr[eax]   ;ST(0)=array value, ST(1)=-5
   fscale                ;ST(0)=(array value)/32, ST(1)=-5
   fstp dword ptr[eax]   ;replace with the scaled value and pop ST(0)
                         ;ST(0)=-5
   fwait                 ;insure previous instruction is completed
                         ;before modifying EAX
   add  eax,4            ;adjust pointer to next REAL4 value
   dec  ecx              ;adjust counter
   jnz  @B               ;continue scaling all the array values

;clean-up
   fstp st               ;free the FPU register containing the -5
   add  esp,4            ;readjust the stack pointer for the pushed -5

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 12

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 12
Other instructions

The instructions covered in this chapter are those which could not be included in other more specific categories. They are (in alphabetical order):

FNOP      No OPeration

FPREM     Partial REMainder

FPREM1    Partial REMainder 1

FXTRACT   eXTRACT exponent and significand

FNOP (No Operation)
Syntax:    fnop (no operand)

Exception flags: None
This instruction performs no operation. It only takes up space and consumes a few clock cycles.
It may be used as a filler for alignment purposes or timing delays.

FPREM (Partial remainder)

Syntax:    fprem (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow
This instruction performs the equivalent of a ST(0) modulo ST(1) with REAL numbers, overwriting the content of ST(0) with the remainder which will have the same sign as the original value in ST(0). The content of ST(1) remains unchanged.

The remainder represents the following value:

Remainder = ST(0) - (Q * ST(1))

where Q is an integer obtained by truncating the quotient of ST(0)÷ST(1).

However, the exponent in ST(0) cannot be reduced by more than 63 with each execution of the FPREM instruction. If the difference between the exponents of ST(0) and ST(1) is larger than 63, ST(0) will then be only partially reduced and that partial remainder will replace the content of ST(0). The software will have to repeat the FPREM instruction as often as necessary to obtain a final remainder which will be smaller than the modulus ST(1). When the reduction is complete, the C2 bit of the Status Word is cleared, otherwise it is set to 1.

An Invalid operation exception is detected if ST(0) or ST(1) is empty, or is a NAN, or if ST(0) is INFINITY, or if ST(1) is zero, setting the related flag in the Status Word. The content of ST(0) would be overwritten with the INDEFINITE value.

A Stack Fault exception is also detected if either ST(0) or ST(1) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of either ST(0) or ST(1) is a denormalized number or a result is a denormalized number, setting the related flag in the Status Word.

An Underflow exception will be detected if the result exceeds the range limit of REAL10 numbers, setting the related flag in the Status Word.

When the operation is successful, the 3 least significant bits of the quotient (Q) are inserted in the C0, C1 and C3 bit fields of the Status Word in the following manner:

C0 = bit2 of the quotient (Q2)
C1 = bit0 of the quotient (Q0)
C3 = bit1 of the quotient (Q1)

If necessary, the following code is suggested to unscramble the bits of the Status Word to their original order in the quotient.

                ;          AH                                AL
fstsw ax        ;x  Q1 x  x  x C2 Q0 Q2            x  x  x  x  x  x  x  x
fwait
sahf            ;copy AH to the CPU flag register
jpe  more_fprem ;C2 became the parity flag PF
                ;if set, the FPREM instruction needs to be repeated
                ;Q2 became the carry flag CF
rcl   al,1      ;x  Q1 x  x  x  x  Q0 Q2           x  x  x  x  x  x  x  Q2
shl   ah,2      ;x  x  x  x  Q0 Q2 x  x (Q1 -> CF) x  x  x  x  x  x  x  Q2
rcl   al,1      ;x  x  x  x  Q0 Q2 x  x            x  x  x  x  x  x  Q2 Q1
shr   ah,4      ;x  x  x  x  x  x  x  x (Q0 -> CF) x  x  x  x  x  x  Q2 Q1
rcl   al,1      ;x  x  x  x  x  x  x  x            x  x  x  x  x  Q2 Q1 Q0
and   eax,7     ;keep only the 3 least significant bits
The FPREM instruction was almost an absolute necessity with the earlier FPUs (prior to the 387) when the source angle for the FPTAN instruction could only be in the range of 0 to π/4 radian. The lower 3 bits of the quotient Q were also necessary to locate the angle in the proper sector in order to determine if the reciprocal of the result needed to be computed and which sign was required.

With the more modern FPUs (387+), the FPREM instruction may still be required before using one of the trigonometric instructions if there is a risk that the source angle exceeds the allowable range of ±263 radians. However, knowing the lower 3 bits of the quotient has become almost useless. It is still being made available primarily for backward compatibility.

The following code is suggested for reducing an angle within the allowable range of ±263 radians. It assumes that the angle value is already in ST(0) and already expressed in radians.

                    ; ST(0)=angle in radians to be reduced
   fldpi            ;->ST(0)=π, ST(1)=angle
   fadd   st,st     ;->ST(0)=2π, ST(1)=angle
   fxch             ;->ST(0)=angle, ST(1)=2π
@@:
   fprem            ;->ST(0)=reduced angle, ST(1)=2π
   fstsw  ax        ;Status Word into AX
   fwait            ;insure transfer is completed
   sahf             ;copy to the CPU flags
   jpe    @B        ;continue reducing if C2=PF=1 (reduction incomplete)

   fstp   st(1)     ;overwrite the 2π with the ST(0) value and POP ST(0)
                    ;->ST(0)=reduced angle smaller than 2π

FPREM1 (Partial remainder 1)

Syntax:    fprem1 (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Underflow
This instruction is a modification of the FPREM instruction to compute the remainder as specified in the IEEE Std 754. According to that Standard, the magnitude of the remainder must be less than or equal to half of the modulus ST(1). The sign of the remainder can thus be different than the sign of the original value in ST(0).

For example, using integers, 11 modulo7 with the FPREM instruction would be +4. With the FPREM1 instruction, 4 being more than half the modulus 7, the resulting remainder would be -3. (10 modulo7 would be +3 with either instruction.)

(See the FPREM instruction for other details.)

FXTRACT (Extract exponent and significand)

Syntax:    fxtract (no operand)

Exception flags: Stack Fault, Invalid operation, Denormalized value,
                 Zero divide
This instruction splits the content of ST(0) into two parts, the true unbiased exponent and the significand. The content of ST(0) is overwritten with the true unbiased exponent in floating point format, the TOP field of the Status Word is decremented, and the significand is inserted in the new ST(0) with a biased exponent of 0 (3FFFh for the REAL10 format of the data registers).

If ST(0) originally had a value of ±0, the true unbiased exponent would be set at -INFINITY and the significand would be set to a value of 0 with the same sign as the original one.

If ST(0) originally had a value of ±INFINITY, the true unbiased exponent would be set at +INFINITY and the significand would be set at INFINITY with the same sign as the original one.

An Invalid operation exception is detected if ST(0) is empty, or is a NAN, setting the related flag in the Status Word. The content of ST(0) would be overwritten with the INDEFINITE value, the TOP field of the Status Word decremented, and the INDEFINITE value inserted in the new ST(0).

A Stack Fault exception is also detected if ST(0) is empty, setting the related flag in the Status Word.

A Denormal exception is detected when the content of ST(0) is a denormalized number, setting the related flag in the Status Word.

A Zero divide exception is detected when the content of ST(0) is 0.

This instruction could be used for logarithmic scaling operations. The extracted exponent would be multiplied by the scaling factor, its antilog computed with the F2XM1 instruction, and that result multiplied by the extracted significand.

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Chap. 13

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 13
Commented example

The example presented in this chapter is based on a simple dialog box to compute the monthly payments on a mortgage based on the following formula:

where:
P = Mortgage Principle
R = Monthly Interest Rate (expressed in decimal)
N = Number of months
PMT = Computed monthly payments

The dialog box itself was designed with the "WYSIWYG" feature of the Symantec Resource Editor Rs32.exe provided with the MASM32 package in the rc sub-folder. The only manual modification to the generated script was to add the ES_NUMBER style to two of the edit controls, that style not being an option with Rs32. The resource script is reproduced at the end of this document.

Three separate edit controls are provided for the user to input the mortgage principal, the annual interest rate (expressed as a %) and the number of years. Two radio buttons are also provided to choose between two procedures for computing monthly payments.

One procedure is based on the generally allowed practice in the USA of compounding interest on a monthly basis. For example, a 12% stated annual rate becomes a 1% monthly rate which, when compounded, is equivalent to a true 12.6825% annual rate.

The other procedure is based on a restriction imposed on Canadian chartered banks for mortgage loans where the stated annual rate cannot be compounded more than twice per year. For example, a 12% stated annual rate becomes a 6% semi-annual rate which, when compounded, is equivalent to a true 12.36% annual rate. The monthly rate must thus be computed according to the following formula where RSA is the semi-annual rate:

R = (1+RSA)1/6 - 1

When this computed monthly rate is compounded on a monthly basis, it would be equivalent to the semi-annual rate.

In order to simplify the program as much as possible, some limits have been imposed on the user input but may not even be noticed.

-- The input for the mortgage principal is restricted to numbers only (with the ES_NUMBER style for its edit control) and limited to a maximum of 9 characters (with the EM_SETLIMITTEXT message during the WM_INITDIALOG phase). This still allows for a mortgage input of up to 1 billion dollars which should generally be sufficient, but disallows the inclusion of pennies (which would be very rarely specified anyway) as part of the input. The main advantage of those restrictions is that it guarantees a positive binary 32-bit integer can be retrieved directly from the edit control without any need to parse the input for invalid characters and perform the conversion from ASCII with additional code.

-- The input for the number of years is also restricted to numbers only and limited to a maximum of 2 characters. This still allows for a mortgage life of up to 99 years, but disallows partial years (which is also rarely specified) as part of the input. Advantages are the same as above.

-- The input for the annual rate is restricted to 9 characters (rates are rarely specified with more than 3 decimals). This, however, guarantees that the value of the numerical digits excluding the decimal delimiter would not exceed the maximum possible value of a positive 32-bit integer.

While parsing the annual rate, a few more restrictions generate an error message:

-- An annual rate exceeding 100% (which would be considered illegal loan-sharking). This guarantees that the log2 of any (1+R) term will always be less than 1.

-- A "-" sign. The rate must be positive.

A lack of input or an input equal to 0 in any of the three edit controls also generates an error message and no computation is performed.

With pre-validated data, the computation can thus proceed without any risk of error. Some code has nevertheless been added before displaying the result to ascertain that no major problem has been encountered due to unforeseeable circumstances.

The code has been kept as simple as possible, without any attempt to optimize it for speed or size (speed optimization would be a waste of time and effort for such an application). It is also specific for the application. Even the code to convert the annual rate from a string to a floating point should not be generalized without modifying the parts which rely on the designed purpose and restrictions.

The provided code is fully tested. It can be assembled without modification with MASM32 if copied into a file with the .asm extension. If the resource script is copied into a file named rsrc.rc and placed in the same directory as the .asm file, the .exe file can be generated in a single step with the Project->Build All menu option of the QEditor in MASM32. Modifications to the code and/or assembly procedures will be required with other assemblers and/or IDEs.

The FPU instructions used with this example application are (in alphabetical order):

F2XM1     2 to the X power minus 1
FADD      Add two floating point values
FBSTP     Store BCD data to memory
FCHS      Change the sign of ST(0)
FDIV      Divide two floating point values
FIDIV     Divide ST(0) by an Integer located in memory
FILD      Load integer from memory
FIMUL     Multiply ST(0) by an Integer located in memory
FINIT     Initialize the FPU
FLD       Load real number
FLD1      Load the value of 1
FMUL      Multiply two floating point values
FRNDINT   Round ST(0) to an integer
FSCALE    Scale ST(0) by ST(1)
FSTP      Store real number and pop ST(0)
FSTSW     Store status word
FSUB      Subtract two floating point values
FWAIT     Wait while FPU is busy
FXCH      Exchange the top data register with another data register
FYL2XP1   Y*Log2(X+1)

; #######################################################################
;
;                    Mortgage payment calculator
;            Written with MASM32 by Raymond Filiatreault
;                           August 2003
;
; #######################################################################

      .386                   ; minimum processor needed for 32 bit
      .model flat, stdcall   ; FLAT memory model & STDCALL calling
      option casemap :none   ; set code to case sensitive

; #######################################################################

      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\comdlg32.inc
      include \masm32\include\comctl32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
      includelib \masm32\lib\comdlg32.lib
      includelib \masm32\lib\comctl32.lib

; #########################################################################

      return MACRO arg
        mov eax, arg
        ret
      ENDM

    WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

; #########################################################################

PRINCIPLE   EQU   711
RATEPCT     EQU   712
YEARS       EQU   713
PAYMENT     EQU   714
COMPUTE     EQU   720
QUITS       EQU   721
USABUT      EQU   730
CANADABUT   EQU   731
AMERICAN    EQU   0

.data
      hDlg        dd    0
      hInstance   dd    0

      mortgage    dd    0
      months      dd    0

      factor6     dd    6
      factor10    dd    10
      factor12    dd    12

      bcdtemp     dt    0

      radiobutton db    0           ;0=USA, 1=Canada

      badinput    db    "Input error",0
      princerr    db    "Unacceptable input for principle",0
      raterr      db    "Unacceptable input for rate",0
      yearerr     db    "Unacceptable input for years",0
      invalid     db    "Invalid FPU operation detected",0

      buffer1     db    16 dup(0)

; #########################################################################

.code

start:

      invoke GetModuleHandle,NULL
      mov    hInstance,eax
      invoke InitCommonControls

      invoke DialogBoxParam,hInstance,700,NULL,ADDR WndProc,NULL

      invoke ExitProcess,eax

; #########################################################################

WndProc proc hWin   :DWORD,
             uMsg   :DWORD,
             wParam :DWORD,
             lParam :DWORD

      .if uMsg == WM_INITDIALOG
            push  hWin
            pop   hDlg        ;save handle in a global variable
                              ;this avoids having to pass it as a
                              ;parameter whenever needed outside this proc

            invoke SendDlgItemMessage,hDlg,PRINCIPLE,EM_SETLIMITTEXT,9,0
            invoke SendDlgItemMessage,hDlg,RATEPCT,EM_SETLIMITTEXT,9,0
            invoke SendDlgItemMessage,hDlg,YEARS,EM_SETLIMITTEXT,2,0
            invoke CheckRadioButton,hDlg,USABUT,CANADABUT,USABUT
            return TRUE

      .elseif uMsg == WM_COMMAND
            mov     eax,wParam
            and     eax,0ffffh

            .if   eax == QUITS            ;Exit button clicked
                  invoke  EndDialog,hDlg,0

            .elseif eax == COMPUTE        ;Compute button clicked
                  call  maincalc          ;process input and display result
                  return  TRUE

            .elseif eax == USABUT         ;USA radiobutton clicked
                  mov   radiobutton,0     ;store info
                  return  TRUE

            .elseif eax == CANADABUT      ;Canada radiobutton clicked
                  mov   radiobutton,1     ;store info
                  return  TRUE
            .endif

      .elseif uMsg == WM_CLOSE
            invoke  EndDialog,hDlg,0

      .endif

      return FALSE        ;use Windows defaults to handle other messages

WndProc endp

; ########################################################################

maincalc:

;*********************************************************
;*****     Retrieve THE MORTGAGE PRINCIPAL input     *****
;*********************************************************

      invoke GetDlgItemInt,hDlg,PRINCIPLE,0,0       ;retrieve as an integer
      mov   mortgage,eax      ;store it
      or    eax,eax           ;check if input > 0
      ja    @F                ;jump if OK
      lea   eax,princerr      ;error message for input of principal

inputerror:
      invoke  MessageBox,0,eax,ADDR badinput,MB_OK  ;display error message
      ret

;******************************************************
;*****     Retrieve the NUMBER OF YEARS input     *****
;******************************************************

   @@:
      invoke GetDlgItemInt,hDlg,YEARS,0,0           ;retrieve as an integer
      mul   factor12          ;convert it to months
      mov   months,eax        ;store it
      or    eax,eax           ;check if input > 0
      ja    @F                ;jump if OK
      lea   eax,yearerr       ;error message for input of years
      jmp   inputerror

;**************************************************
;*****     Retrieve the ANNUAL RATE input     *****
;**************************************************

   @@:
      invoke GetDlgItemText,hDlg,RATEPCT,ADDR buffer1,16 ;retrieve as string
      .if   buffer1 == 0      ;buffer1 being a global BYTE variable
                              ;if there was no input in the EDIT control,
                              ;the first byte of buffer1 would be 0

badrate:
          lea   eax,raterr    ;error message for annual rate input
          jmp   inputerror
      .endif

      call  atofl             ;convert ASCII string (%) to REAL10 decimal
                              ;->st(0)=decimal annual rate (if no error)

            ; Note: The "atofl" sub-routine is called only once
            ; and its code could have been inserted directly
            ; without the need for the "call" instruction. This
            ; was done for the purpose of clarity in this section.

      or    eax,eax           ;check if error was detected
      jz    badrate           ;abort and display message if error

;*************************************************************
;*****     Convert the annual rate to a MONTHLY rate     *****
;*************************************************************

      .if   radiobutton == AMERICAN
          fidiv factor12      ;divide the annual rate by 12
                        ;-> st(0)=monthly rate

      .else
          fld1          ;-> st(0)=1, st(1)=annual rate
          fchs          ;-> st(0)=-1, st(1)=annual rate
          fxch          ;-> st(0)=annual rate, st(1)=-1 scaling factor
          fscale        ;divide the annual rate by 2^1
                        ;to get the semi-annual rate
                        ;(a positive scaling factor will multiply
                        ;a negative scaling factor will divide)
          fstp  st(1)   ;overwrite the scaling factor with st(0)
                        ;and pop the top register
                        ;-> st(0)=semi-annual rate
          fld1          ;-> st(0)=1, st(1)=semi-annual rate
          fidiv factor6 ;-> st(0)=1/6, st(1)=semi-annual rate
          fxch          ;-> st(0)=semi-annual rate, st(1)=1/6
          fyl2xp1       ;-> st(0)=[log2(semi-annual rate+1)]*1/6

          ;Note: Because of limitations imposed on input for size and sign,
                ;the (semi-annual rate+1) term will be between 1 and 1.5
                ;and its log2 will be positive and less than 1. That log2
                ;is further divided by 6 for a value definitely between
                ;0 and +1. That value can thus be used directly with
                ;the next instruction without any need for scaling before
                ;or after.

          f2xm1         ;-> st(0)=monthly rate (obtained directly
                        ;because the instruction provides the minus 1)
                        ;When this monthly rate is compounded 6 times
                        ; it would be equal to the semi-annual rate
      .endif

;************************************************
;*****     Compute the monthly payments     *****
;************************************************

      fild  months      ;-> st(0)=months, st(1)=monthly rate (R)
      fld   st(1)       ;-> st(0)=R, st(1)=months, st(2)=R
      fyl2xp1           ;-> st(0)=log2(1+R)*months, st(1)=R
      fld   st          ;-> st(0)=log, st(1)=log, st(2)=R
      frndint           ;-> st(0)=int(log), st(1)=log, st(2)=R
      fsub  st(1),st    ;-> st(0)=int(log), st(1)=log-int(log), st(2)=R
      fxch  st(1)       ;-> st(0)=log-int(log), st(1)=int(log), st(2)=R
      f2xm1             ;-> st(0)=2[log-int(log)]-1, st(1)=int(log), st(2)=R
      fld1
      fadd              ;-> st(0)=2[log-int(log)], st(1)=int(log), st(2)=R
      fscale            ;-> st(0)=(1+R)N, st(1)=int(log), st(2)=R
      fstp  st(1)       ;-> st(0)=(1+R)N, st(1)=R
      fld   st          ;-> st(0)=(1+R)N, st(1)=(1+R)N, st(2)=R
      fld1              ;-> st(0)=1, st(1)=(1+R)N, st(2)=(1+R)N, st(3)=R
      fsub              ;-> st(0)=(1+R)N-1, st(1)=(1+R)N, st(2)=R
      fdiv              ;-> st(0)=(1+R)N/[(1+R)N-1], st(1)=R
      fmul              ;-> st(0)=R*(1+R)N/[(1+R)N-1]
      fimul mortgage    ;-> st(0)=P*R*(1+R)N/[(1+R)N-1]=Monthly payments
      fimul factor10    ;multiply by 100 to have 2 decimal places as integer
      fimul factor10    ;-> st(0)=Monthly payments*100
      fbstp bcdtemp     ;store in memory in BCD format
                        ;rounded to the closest penny

;*************************************************************************
; The folowing section of code is not necessary for this application
; because every precaution was taken to examine the input to insure that
; the data used in the FPU computations is valid and would not result in
; any major error. It is merely included to indicate how to check the
; validity of the end result whenever there may be a risk of invalid data.
; It also insures that the FBSTP instruction is completed before starting
; to access the stored packed BCD data.
;*************************************************************************

      fstsw ax          ;copy to AX the FPU's Status Word
                        ;containing the exception flags
      fwait             ;insure the execution is completed
      shr   eax,1       ;transfer bit0 to the CPU's carry flag
                        ;That bit would be set if an invalid operation
                        ;was detected with any of the FPU instructions
                        ;The final result would then be invalid
      jnc   @F          ;continue if no invalid operation flag

      lea   eax,invalid
      jmp   inputerror  ;display error message and return to WndProc

;****************************************************************
;*****         Unpack the BCD result and display it         *****
;
; The coding used is not suitable as a general purpose unpacking
; algorithm for at least two reasons: the sign is known to be
; positive and thus disregarded, and the result was designed to
; always contain 2 decimal places.
;***************************************************************

   @@:
      push  esi               ;preserve ESI and EDI
      push  edi
      lea   esi,bcdtemp+8     ;use ESI to point initially
                              ;to the 2nd most significant byte
      lea   edi,buffer1       ;use EDI to point to the buffer
                              ;where the ASCII string will be stored
      mov   ecx,8             ;use ECX as counter for the number of bytes
                              ;possibly containing integer digits

            ;Note: (The BCD format has 10 bytes. The most significant
                  ;byte contains the sign which is disregarded in this
                  ;application, and the least significant byte is known
                  ;to contain two decimal digits.)

;**********************************************************************
; Search for the most significant byte containing the 1st integer digit
;**********************************************************************

   @@:
      movzx eax,byte ptr[esi] ;get next byte in AL zero extended in EAX
      dec   esi         ;adjust pointer to next byte
      or    eax,eax     ;check if it contains the 1st integer
      jnz   @F          ;jump if found
      dec   ecx         ;decrement counter
      jnz   @B          ;continue search until all integer bytes checked

;***********************************************
; If no integer digit is present,
; place a "0" digit before the decimal delimiter
;***********************************************

      mov   al,"0"
      stosb             ;insert the "0" digit
      jmp   decimals    ;go insert the decimal delimiter and digits

;*******************************************************
; If that 1st byte contains only 1 integer digit,
; it has to be processed separately to avoid a leading 0
;*******************************************************

   @@:
      test  al,0f0h     ;check the high nibble of AL
      jnz   @F          ;jump if there are 2 integer digits
      add   al,"0"      ;convert the digit to ASCII
      stosb             ;insert that digit
      jmp   nextdigit   ;process next byte

;*******************************************************************
; Other bytes contain 2 integer digits each which must be unpacked
; The high nibble digit must be followed in memory by the low nibble
;*******************************************************************

   @@:
      ror   ax,4              ;transfer the high nibble to low nibble of AL
                              ;and low nibble of AL to high nibble of AH
      ror   ah,4              ;transfer it to the low nibble of AH
      add   ax,3030h          ;convert both to ASCII
      stosw                   ;insert both, AL followed by AH in memory

nextdigit:
      movzx eax,byte ptr[esi] ;get next byte
      dec   esi               ;adjust pointer
      dec   ecx               ;decrement integer byte counter
      jnz   @B                ;continue with integer digits until completed

decimals:
      mov   byte ptr[edi],"." ;insert the decimal delimiter
      inc   edi               ;adjust string pointer
      ror   ax,4              ;unpack the decimal byte as above
      ror   ah,4
      add   ax,3030h
      stosw
      mov   byte ptr[edi],0   ;insert the terminating 0

      pop   edi               ;restore the EDI and ESI registers
      pop   esi

;***********************************************************************
; Display the result in the dialog box and return control to the WndProc
;***********************************************************************

      invoke SetDlgItemText,hDlg,PAYMENT,ADDR buffer1
      ret

;***************************************************************
;                            atofl
;
; This sub-routine is partly general purpose and partly specific
; for the task. It parses the string for unacceptable characters
; but also returns an error if the integral portion of the input
; exceeds 100. It also returns an error for a negative sign.
;
; The conversion approach of treating all the numerical digits as
; being integer digits is sound only because the size of the
; string was limited to 9 characters in the EDIT control. This
; guarantees that, in a worse case scenario, the maximum integer
; value of the input would still fit in a 32-bit register. The
; final integer value obtained is then corrected according to the
; count of decimal digits in the input string.
;
; Returns with EAX = 0 if error detected.
;
; If EAX != 0, the converted annual rate is returned in st(0)
; already divided by 100.
;****************************************************************

atofl:
      push  ebx         ;preserve EBX and ESI
      push  esi

      lea   esi,buffer1 ;use ESI as pointer to text buffer
      xor   eax,eax
      xor   ebx,ebx     ;will be used as an accumulator
      xor   ecx,ecx     ;will be used as a counter

;************************************************
; Skip leading spaces without generating an error
;************************************************

   @@:
      lodsb             ;get next character
      cmp   al," "      ;check if a space character
      jz    @B          ;repeat until a non-space character is found

;*********************************************
; Check 1st non-space character for a +/- sign
;*********************************************

      cmp   al,"-"      ;is it a "-" sign
      jnz   @F

atoflerr:
      xor   eax,eax     ;set EAX to error code
      pop   esi         ;restore the EBX and ESI registers
      pop   ebx
      ret               ;return with error code

   @@:
      cmp   al,"+"      ;is it a "+" sign
      jnz   nextchar
      lodsb             ;disregard a "+" sign and get next character

;***********************************************************
; From this point, space and sign characters will be invalid
;***********************************************************

nextchar:
      cmp   al,0        ;check for end-of-string character
      jz    endinput    ;exit the string parsing section

      cmp   al,"."      ;is it the "." decimal delimiter
                        ;other delimiters such as the "," used in some
                        ;countries could also be allowed but would need
                        ;additional coding to make it more generalized
      jnz   @F

;******************************************************************
; Only one decimal delimiter can be acceptable. The sign bit of ECX
; is used to keep a record of the first delimiter identified.
;******************************************************************

      or    ecx,ecx     ;check if a delimiter has already been identified
      js    atoflerr    ;exit with error code if more than 1 delimiter

      stc               ;set the carry flag
      rcr   ecx,1       ;set bit31 of ECX (the sign bit) when
                        ;the 1st delimiter is identified
      lodsb             ;get next character
      jmp   nextchar    ;continue parsing

;***********************************************************************
; All ASCII characters other than the numerical ones will now be invalid
;***********************************************************************

   @@:
      cmp   al,"0"
      jb    atoflerr
      cmp   al,"9"
      ja    atoflerr

      sub   al,"0"      ;convert valid ASCII numerical character to binary
      xchg  eax,ebx     ;get the accumulated integer value in EAX
                        ;holding the new digit in EBX
      mul   factor10    ;multiply the accumulated value by 10
      add   eax,ebx     ; and add the new digit
      xchg  eax,ebx     ;store this new accumulated value back in EBX

      or    ecx,ecx     ;check if a decimal delimiter detected yet
      js    @F          ;jump if decimal digits are being processed

;*************************************
; Integer digits still being processed
;*************************************

      cmp   ebx,100     ;verify current value of integer portion
      ja    atoflerr    ;abort if input for annual rate is > 100%
      lodsb             ;get next string character
      jmp   nextchar    ;continue processing string characters

;*******************************************************
; The CL register is used as a counter of decimal digits
; after the decimal delimiter has been identified
;*******************************************************

   @@:
      inc   cl          ;increment count of decimal digits
      lodsb             ;get next string character
      jmp   nextchar    ;continue processing string characters

;***********************************
; Parsing of the string is completed
;***********************************

endinput:
      or    ebx,ebx     ;check if total input was equal to 0
      jz    atoflerr    ;abort if annual rate input is 0%

      finit             ;initialize FPU
      push  ebx         ;store value of EBX on stack
      fild  dword ptr[esp]    ;-> st(0)=EBX
      add   cl,2        ;increment the number of decimal digits
                        ;to convert from % rate to a decimal rate
      shl   ecx,1       ;get rid of the potential sign "flag"
      shr   ecx,1       ;restore the count of decimal digits
      fild  factor10    ;-> st(0)=10, st(1)=EBX
   @@:
      fdiv  st(1),st    ;-> st(0)=10, st(1)=EBX/10
      dec   ecx         ;decrement counter of decimal digits
      jnz   @B          ;continue dividing by 10 until count exhausted
      fstp  st          ;get rid of the dividing 10 in st(0)
                        ;-> st(0)=annual rate (as a decimal rate)
      pop   ebx         ;clean CPU stack

      pop   esi         ;restore the EBX and ESI registers
      pop   ebx
      or    al,1        ;insure EAX != 0 (i.e. no error detected)
      ret

;*******************************

end start

;#######################################################################
;
;                    Resource script for the Dialog Box
;                 to be used with the Mortgage Calculator
;                               August 2003
;
;#######################################################################

#include "\masm32\include\resource.h"

700 DIALOGEX MOVEABLE IMPURE LOADONCALL DISCARDABLE	100, 80, 131, 132, 0
STYLE DS_MODALFRAME | 0x0004 | WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_POPUP
CAPTION "Mortgage Payments"
FONT 8, "MS Sans Serif", 700, 0 /*FALSE*/
BEGIN
    LTEXT     "Mortgage principle", 701, 4,6,66,10, SS_LEFT, , 0
    LTEXT     "Annual rate, %", 702, 	4,22,66,10, SS_LEFT, , 0
    LTEXT     "Number of years", 703, 	4,38,66,10, SS_LEFT, , 0
    LTEXT     "Monthly payments", 704, 	4,84,66,10, SS_LEFT, , 0
    EDITTEXT  711,  78, 6,49,11, ES_RIGHT | ES_NUMBER, , 0
    EDITTEXT  712,  78,22,49,11, ES_RIGHT, , 0
    EDITTEXT  713, 102,38,25,11, ES_RIGHT | ES_NUMBER, , 0
    CONTROL   "", 714, "Edit", ES_READONLY | ES_RIGHT, 	78,84,49,11, , 0
    CONTROL   "Compute", 720, "Button", BS_DEFPUSHBUTTON, 16,111,41,13, , 0
    CONTROL   "Exit", 721, "Button", 0, 	74,111,41,13, , 0
    CONTROL   "U.S.A.", 730, "Button", BS_AUTORADIOBUTTON | WS_GROUP, 18,62,44,10, ,0
    CONTROL   "Canada", 731, "Button", BS_AUTORADIOBUTTON, 75,62,44,10, , 0
END

RETURN TO
SIMPLY FPU
CONTENTS


Simply FPU Appendix 1

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Appendix 1
Alphabetical list of FPU instructions
F2XM1     2 to the X power minus 1

FABS      Absolute value of ST(0)

FADD      Add two floating point values

FADDP     Add two floating point values and pop ST(0)

FBLD      Load BCD data from memory

FBSTP     Store BCD data to memory

FCHS      Change the sign of ST(0)

FCLEX     Clear exceptions

FCMOVcc   Conditional move based on CPU flags

FCOM      Compare ST(0) to a floating point value

FCOMI     Compare ST(0) to ST(i) and set CPU flags

FCOMIP    Compare ST(0) to ST(i) and set CPU flags and pop ST(0)

FCOMP     Compare ST(0) to a floating point value and pop ST(0)

FCOMPP    Compare ST(0) to ST(1) and pop both registers

FCOS      Cosine of the angle value in ST(0)

FDECSTP   Decrease stack pointer

FDIV      Divide two floating point values

FDIVP     Divide two floating point values and pop ST(0)

FDIVR     Divide in reverse two floating point values

FDIVRP    Divide in reverse two floating point values and pop ST(0)

FFREE     Free a data register

FIADD     Add an Integer located in memory to ST(0)

FICOM     Compare ST(0) to an integer value

FICOMP    Compare ST(0) to an integer value and pop ST(0)

FIDIV     Divide ST(0) by an Integer located in memory

FIDIVR    Divide an Integer located in memory by ST(0)

FILD      Load integer from memory

FIMUL     Multiply ST(0) by an Integer located in memory

FINCSTP   Increase stack pointer

FINIT     Initialize the FPU

FIST      Store integer to memory

FISTP     Store integer to memory and pop ST(0)

FISUB     Subtract an Integer located in memory from ST(0)

FISUBR    Subtract ST(0) from an Integer located in memory

FLD       Load real number

FLD1      Load the value of 1

FLDCW     Load control word

FLDENV    LoaD environment

FLDL2E    Load the log base 2 of e (Napierian constant)

FLDL2T    Load the log base 2 of Ten

FLDLG2    Load the log base 10 of 2 (common log of 2)

FLDLN2    Load the log base e of 2 (natural log of 2)

FLDPI     Load the value of PI

FLDZ      Load the value of Zero

FMUL      Multiply two floating point values

FMULP     Multiply two floating point values and pop ST(0)

FNCLEX    Clear exceptions (no wait)

FNINIT    Initialize the FPU (no wait)

FNOP      No operation

FNSAVE    Save state of FPU (no wait)

FNSTCW    Store control word (no wait)

FNSTENV   Store environment (no wait)

FNSTSW    Store status word (no wait)

FPATAN    Partial arctangent of the ratio ST(1)/ST(0)

FPREM     Partial remainder

FPREM1    Partial remainder 1

FPTAN     Partial tangent of the angle value in ST(0)

FRNDINT   Round ST(0) to an integer

FRSTOR    Restore all registers

FSAVE     Save state of FPU

FSCALE    Scale ST(0) by ST(1)

FSIN      Sine of the angle value in ST(0)

FSINCOS   Sine and cosine of the angle value in ST(0)

FSQRT     Square root of ST(0)

FST       Store real number

FSTCW     Store control word

FSTENV    Store environment

FSTP      Store real number and pop ST(0)

FSTSW     Store status word

FSUB      Subtract two floating point values

FSUBP     Subtract two floating point values and pop ST(0)

FSUBR     Subtract in reverse two floating point values

FSUBRP    Subtract in reverse two floating point values and Pop ST(0)

FTST      Test ST(0) by comparing it to +0.0

FUCOM     Unordered Compare ST(0) to a floating point value

FUCOMI    Unordered Compare ST(0) to ST(i) and set CPU flags

FUCOMIP   Unordered Compare ST(0) to ST(i) and set CPU flags and pop ST(0)

FUCOMP    Unordered Compare ST(0) to a floating point value and pop ST(0)

FUCOMPP   Unordered Compare ST(0) to ST(1) and pop both registers

FWAIT     Wait while FPU is busy

FXAM      Examine the content of ST(0)

FXCH      Exchange the top data register with another data register

FXTRACT   Extract exponent and significand

FYL2X     Y*Log2X

FYL2XP1   Y*Log2(X+1)

RETURN TO
SIMPLY FPU
CONTENTS


Intermediate Assembly Language

Put your logo here!

Thank You

Acknowledgements

Thanks goes to Jim Carlock at MicroCosmoTalk for compiling this information into a Compiled Help Metafile format.


Chapter Four More Data Representation

4.1 Chapter Overview

Although the basic machine data objects (bytes, words, and double words) appear to represent nothing more than signed or unsigned numeric values, we can employ these data types to represent many other types of objects. This chapter discusses some of the other objects and their internal computer representation.

This chapter begins by discussing the floating point (real) numeric format. After integer representation, floating point representation is the second most popular numeric format in use on modern computer systems1. Although the floating point format is somewhat complex, the necessity to handle non-integer calculations in modern programs requires that you understand this numeric format and its limitations.

Binary Coded Decimal (BCD) is another numeric data representation that is useful in certain contexts. Although BCD is not suitable for general purpose arithmetic, it is useful in some embedded applications. The principle benefit of the BCD format is the ease with which you can convert between string and BCD format. When we look at the BCD format a little later in this chapter, you'll see why this is the case.

Computers can represent all kinds of different objects, not just numeric values. Characters are, unquestionably, one of the more popular data types a computer manipulates. In this chapter you will take a look at a couple of different ways we can represent individual characters on a computer system. This chapter discusses two of the more common character sets in use today: the ASCII character set and the Unicode character set.

This chapter concludes by discussing some common non-numeric data types like pixel colors on a video display, audio data, video data, and so on. Of course, there are lots of different representations for any kind of standard data you could envision; there is no way two chapters in a textbook can cover them all. (And that's not even considering specialized data types you could create). Nevertheless, this chapter (and the last) should give you the basic idea behind representing data on a computer system.

4.2 An Introduction to Floating Point Arithmetic

Integer arithmetic does not let you represent fractional numeric values. Therefore, modern CPUs support an approximation of real arithmetic: floating point arithmetic. A big problem with floating point arithmetic is that it does not follow the standard rules of algebra. Nevertheless, many programmers apply normal algebraic rules when using floating point arithmetic. This is a source of defects in many programs. One of the primary goals of this section is to describe the limitations of floating point arithmetic so you will understand how to use it properly.

Normal algebraic rules apply only to infinite precision arithmetic. Consider the simple statement "x:=x+1," x is an integer. On any modern computer this statement follows the normal rules of algebra as long as overflow does not occur. That is, this statement is valid only for certain values of x (minint <= x < maxint). Most programmers do not have a problem with this because they are well aware of the fact that integers in a program do not follow the standard algebraic rules (e.g., 5/2 ¦ 2.5).

Integers do not follow the standard rules of algebra because the computer represents them with a finite number of bits. You cannot represent any of the (integer) values above the maximum integer or below the minimum integer. Floating point values suffer from this same problem, only worse. After all, the integers are a subset of the real numbers. Therefore, the floating point values must represent the same infinite set of integers. However, there are an infinite number of values between any two real values, so this problem is infinitely worse. Therefore, as well as having to limit your values between a maximum and minimum range, you cannot represent all the values between those two ranges, either.

To represent real numbers, most floating point formats employ scientific notation and use some number of bits to represent a mantissa and a smaller number of bits to represent an exponent. The end result is that floating point numbers can only represent numbers with a specific number of significant digits. This has a big impact on how floating point arithmetic operates. To easily see the impact of limited precision arithmetic, we will adopt a simplified decimal floating point format for our examples. Our floating point format will provide a mantissa with three significant digits and a decimal exponent with two digits. The mantissa and exponents are both signed values as shown in Figure 4.1

Figure 4.1 Simple Floating Point Format

When adding and subtracting two numbers in scientific notation, you must adjust the two values so that their exponents are the same. For example, when adding 1.23e1 and 4.56e0, you must adjust the values so they have the same exponent. One way to do this is to convert 4.56e0 to 0.456e1 and then add. This produces 1.686e1. Unfortunately, the result does not fit into three significant digits, so we must either round or truncate the result to three significant digits. Rounding generally produces the most accurate result, so let's round the result to obtain 1.69e1. As you can see, the lack of precision (the number of digits or bits we maintain in a computation) affects the accuracy (the correctness of the computation).

In the previous example, we were able to round the result because we maintained four significant digits during the calculation. If our floating point calculation is limited to three significant digits during computation, we would have had to truncate the last digit of the smaller number, obtaining 1.68e1 which is even less correct. To improve the accuracy of floating point calculations, it is necessary to add extra digits for use during the calculation. Extra digits available during a computation are known as guard digits (or guard bits in the case of a binary format). They greatly enhance accuracy during a long chain of computations.

The accuracy loss during a single computation usually isn't enough to worry about unless you are greatly concerned about the accuracy of your computations. However, if you compute a value which is the result of a sequence of floating point operations, the error can accumulate and greatly affect the computation itself. For example, suppose we were to add 1.23e3 with 1.00e0. Adjusting the numbers so their exponents are the same before the addition produces 1.23e3 + 0.001e3. The sum of these two values, even after rounding, is 1.23e3. This might seem perfectly reasonable to you; after all, we can only maintain three significant digits, adding in a small value shouldn't affect the result at all. However, suppose we were to add 1.00e0 to 1.23e3 ten times. The first time we add 1.00e0 to 1.23e3 we get 1.23e3. Likewise, we get this same result the second, third, fourth, ..., and tenth time we add 1.00e0 to 1.23e3. On the other hand, had we added 1.00e0 to itself ten times, then added the result (1.00e1) to 1.23e3, we would have gotten a different result, 1.24e3. This is an important thing to know about limited precision arithmetic:

      • The order of evaluation can effect the accuracy of the result.

You will get more accurate results if the relative magnitudes (that is, the exponents) are close to one another. If you are performing a chain calculation involving addition and subtraction, you should attempt to group the values appropriately.

Another problem with addition and subtraction is that you can wind up with false precision. Consider the computation 1.23e0 - 1.22 e0. This produces 0.01e0. Although this is mathematically equivalent to 1.00e-2, this latter form suggests that the last two digits are exactly zero. Unfortunately, we've only got a single significant digit at this time. Indeed, some FPUs or floating point software packages might actually insert random digits (or bits) into the L.O. positions. This brings up a second important rule concerning limited precision arithmetic:

      • Whenever subtracting two numbers with the same signs or adding two numbers with different signs, the accuracy of the result may be less than the precision available in the floating point format.

Multiplication and division do not suffer from the same problems as addition and subtraction since you do not have to adjust the exponents before the operation; all you need to do is add the exponents and multiply the mantissas (or subtract the exponents and divide the mantissas). By themselves, multiplication and division do not produce particularly poor results. However, they tend to multiply any error that already exists in a value. For example, if you multiply 1.23e0 by two, when you should be multiplying 1.24e0 by two, the result is even less accurate. This brings up a third important rule when working with limited precision arithmetic:

      • When performing a chain of calculations involving addition, subtraction, multiplication, and division, try to perform the multiplication and division operations first.

Often, by applying normal algebraic transformations, you can arrange a calculation so the multiply and divide operations occur first. For example, suppose you want to compute x*(y+z). Normally you would add y and z together and multiply their sum by x. However, you will get a little more accuracy if you transform x*(y+z) to get x*y+x*z and compute the result by performing the multiplications first.

Multiplication and division are not without their own problems. When multiplying two very large or very small numbers, it is quite possible for overflow or underflow to occur. The same situation occurs when dividing a small number by a large number or dividing a large number by a small number. This brings up a fourth rule you should attempt to follow when multiplying or dividing values:

      • When multiplying and dividing sets of numbers, try to arrange the multiplications so that they multiply large and small numbers together; likewise, try to divide numbers that have the same relative magnitudes.

Comparing floating point numbers is very dangerous. Given the inaccuracies present in any computation (including converting an input string to a floating point value), you should never compare two floating point values to see if they are equal. In a binary floating point format, different computations which produce the same (mathematical) result may differ in their least significant bits. For example, adding 1.31e0+1.69e0 should produce 3.00e0. Likewise, adding 1.50e0+1.50e0 should produce 3.00e0. However, were you to compare (1.31e0+1.69e0) against (1.50e0+1.50e0) you might find out that these sums are not equal to one another. The test for equality succeeds if and only if all bits (or digits) in the two operands are exactly the same. Since this is not necessarily true after two different floating point computations which should produce the same result, a straight test for equality may not work.

The standard way to test for equality between floating point numbers is to determine how much error (or tolerance) you will allow in a comparison and check to see if one value is within this error range of the other. The straight-forward way to do this is to use a test like the following:

    if Value1 >= (Value2-error) and Value1 <= (Value2+error) then ...

Another common way to handle this same comparison is to use a statement of the form:

    if abs(Value1-Value2) <= error then ...

Most texts, when discussing floating point comparisons, stop immediately after discussing the problem with floating point equality, assuming that other forms of comparison are perfectly okay with floating point numbers. This isn't true! If we are assuming that x=y if x is within y±error, then a simple bitwise comparison of x and y will claim that x<y if y is greater than x but less than y+error. However, in such a case x should really be treated as equal to y, not less than y. Therefore, we must always compare two floating point numbers using ranges, regardless of the actual comparison we want to perform. Trying to compare two floating point numbers directly can lead to an error. To compare two floating point numbers, x and y, against one another, you should use one of the following forms:

    =	if abs(x-y) <= error then ...

    ¦	if abs(x-y) > error then ...

    <	if (x-y) < -error then ...

    ð	if (x-y) <= error then ...

    >	if (x-y) > error then ...

        if (x-y) >= -error then ...

You must exercise care when choosing the value for error. This should be a value slightly greater than the largest amount of error which will creep into your computations. The exact value will depend upon the particular floating point format you use, but more on that a little later. The final rule we will state in this section is

      • When comparing two floating point numbers, always compare one value to see if it is in the range given by the second value plus or minus some small error value.

There are many other little problems that can occur when using floating point values. This text can only point out some of the major problems and make you aware of the fact that you cannot treat floating point arithmetic like real arithmetic - the inaccuracies present in limited precision arithmetic can get you into trouble if you are not careful. A good text on numerical analysis or even scientific computing can help fill in the details that are beyond the scope of this text. If you are going to be working with floating point arithmetic, in any language, you should take the time to study the effects of limited precision arithmetic on your computations.

HLA's IF statement does not support boolean expressions involving floating point operands. Therefore, you cannot use statements like "IF( x < 3.141) THEN..." in your programs. In a later chapter that discusses floating point operations on the 80x86 you'll learn how to do floating point comparisons.

4.2.1 IEEE Floating Point Formats

When Intel planned to introduce a floating point coprocessor for their new 8086 microprocessor, they were smart enough to realize that the electrical engineers and solid-state physicists who design chips were, perhaps, not the best people to do the necessary numerical analysis to pick the best possible binary representation for a floating point format. So Intel went out and hired the best numerical analyst they could find to design a floating point format for their 8087 FPU. That person then hired two other experts in the field and the three of them (Kahn, Coonan, and Stone) designed Intel's floating point format. They did such a good job designing the KCS Floating Point Standard that the IEEE organization adopted this format for the IEEE floating point format2.

To handle a wide range of performance and accuracy requirements, Intel actually introduced three floating point formats: single precision, double precision, and extended precision. The single and double precision formats corresponded to C's float and double types or FORTRAN's real and double precision types. Intel intended to use extended precision for long chains of computations. Extended precision contains 16 extra bits that the calculations could use as guard bits before rounding down to a double precision value when storing the result.

The single precision format uses a one's complement 24 bit mantissa and an eight bit excess-127 exponent. The mantissa usually represents a value between 1.0 to just under 2.0. The H.O. bit of the mantissa is always assumed to be one and represents a value just to the left of the binary point3. The remaining 23 mantissa bits appear to the right of the binary point. Therefore, the mantissa represents the value:

1.mmmmmmm mmmmmmmm mmmmmmmm

The "mmmm..." characters represent the 23 bits of the mantissa. Keep in mind that we are working with binary numbers here. Therefore, each position to the right of the binary point represents a value (zero or one) times a successive negative power of two. The implied one bit is always multiplied by 20, which is one. This is why the mantissa is always greater than or equal to one. Even if the other mantissa bits are all zero, the implied one bit always gives us the value one4. Of course, even if we had an almost infinite number of one bits after the binary point, they still would not add up to two. This is why the mantissa can represent values in the range one to just under two.

Although there are an infinite number of values between one and two, we can only represent eight million of them because we use a 23 bit mantissa (the 24th bit is always one). This is the reason for inaccuracy in floating point arithmetic - we are limited to 23 bits of precision in computations involving single precision floating point values.

The mantissa uses a one's complement format rather than two's complement. This means that the 24 bit value of the mantissa is simply an unsigned binary number and the sign bit determines whether that value is positive or negative. One's complement numbers have the unusual property that there are two representations for zero (with the sign bit set or clear). Generally, this is important only to the person designing the floating point software or hardware system. We will assume that the value zero always has the sign bit clear.

To represent values outside the range 1.0 to just under 2.0, the exponent portion of the floating point format comes into play. The floating point format raises two to the power specified by the exponent and then multiplies the mantissa by this value. The exponent is eight bits and is stored in an excess-127 format. In excess-127 format, the exponent 20 is represented by the value 127 ($7f). Therefore, to convert an exponent to excess-127 format simply add 127 to the exponent value. The use of excess-127 format makes it easier to compare floating point values. The single precision floating point format takes the form shown in Figure 4.2.

Figure 4.2 Single Precision (32-bit) Floating Point Format

With a 24 bit mantissa, you will get approximately 6-1/2 digits of precision (one half digit of precision means that the first six digits can all be in the range 0..9 but the seventh digit can only be in the range 0..x where x<9 and is generally close to five). With an eight bit excess-127 exponent, the dynamic range of single precision floating point numbers is approximately 2±128 or about 10±38.

Although single precision floating point numbers are perfectly suitable for many applications, the dynamic range is somewhat limited for many scientific applications and the very limited precision is unsuitable for many financial, scientific, and other applications. Furthermore, in long chains of computations, the limited precision of the single precision format may introduce serious error.

The double precision format helps overcome the problems of single precision floating point. Using twice the space, the double precision format has an 11-bit excess-1023 exponent and a 53 bit mantissa (with an implied H.O. bit of one) plus a sign bit. This provides a dynamic range of about 10±308and 14-1/2 digits of precision, sufficient for most applications. Double precision floating point values take the form shown in Figure 4.3.

Figure 4.3 64-Bit Double Precision Floating Point Format

In order to help ensure accuracy during long chains of computations involving double precision floating point numbers, Intel designed the extended precision format. The extended precision format uses 80 bits. Twelve of the additional 16 bits are appended to the mantissa, four of the additional bits are appended to the end of the exponent. Unlike the single and double precision values, the extended precision format's mantissa does not have an implied H.O. bit which is always one. Therefore, the extended precision format provides a 64 bit mantissa, a 15 bit excess-16383 exponent, and a one bit sign. The format for the extended precision floating point value is shown in Figure 4.4:

Figure 4.4 80-bit Extended Precision Floating Point Format

On the FPUs all computations are done using the extended precision form. Whenever you load a single or double precision value, the FPU automatically converts it to an extended precision value. Likewise, when you store a single or double precision value to memory, the FPU automatically rounds the value down to the appropriate size before storing it. By always working with the extended precision format, Intel guarantees a large number of guard bits are present to ensure the accuracy of your computations. Some texts erroneously claim that you should never use the extended precision format in your own programs, because Intel only guarantees accurate computations when using the single or double precision formats. This is foolish. By performing all computations using 80 bits, Intel helps ensure (but not guarantee) that you will get full 32 or 64 bit accuracy in your computations. Since the FPUs do not provide a large number of guard bits in 80 bit computations, some error will inevitably creep into the L.O. bits of an extended precision computation. However, if your computation is correct to 64 bits, the 80 bit computation will always provide at least 64 accurate bits. Most of the time you will get even more. While you cannot assume that you get an accurate 80 bit computation, you can usually do better than 64 when using the extended precision format.

To maintain maximum precision during computation, most computations use normalized values. A normalized floating point value is one whose H.O. mantissa bit contains one. Almost any non-normalized value can be normalized by shifting the mantissa bits to the left and decrementing the exponent until a one appears in the H.O. bit of the mantissa. Remember, the exponent is a binary exponent. Each time you increment the exponent, you multiply the floating point value by two. Likewise, whenever you decrement the exponent, you divide the floating point value by two. By the same token, shifting the mantissa to the left one bit position multiplies the floating point value by two; likewise, shifting the mantissa to the right divides the floating point value by two. Therefore, shifting the mantissa to the left one position and decrementing the exponent does not change the value of the floating point number at all.

Keeping floating point numbers normalized is beneficial because it maintains the maximum number of bits of precision for a computation. If the H.O. bits of the mantissa are all zero, the mantissa has that many fewer bits of precision available for computation. Therefore, a floating point computation will be more accurate if it involves only normalized values.

There are two important cases where a floating point number cannot be normalized. The value 0.0 is a special case. Obviously it cannot be normalized because the floating point representation for zero has no one bits in the mantissa. This, however, is not a problem since we can exactly represent the value zero with only a single bit.

The second case is when we have some H.O. bits in the mantissa which are zero but the biased exponent is also zero (and we cannot decrement it to normalize the mantissa). Rather than disallow certain small values, whose H.O. mantissa bits and biased exponent are zero (the most negative exponent possible), the IEEE standard allows special denormalized values to represent these smaller values5. Although the use of denormalized values allows IEEE floating point computations to produce better results than if underflow occurred, keep in mind that denormalized values offer less bits of precision.

Since the FPU always converts single and double precision values to extended precision, extended precision arithmetic is actually faster than single or double precision. Therefore, the expected performance benefit of using the smaller formats is not present on these chips. However, when designing the Pentium/586 CPU, Intel redesigned the built-in floating point unit to better compete with RISC chips. Most RISC chips support a native 64 bit double precision format which is faster than Intel's extended precision format. Therefore, Intel provided native 64 bit operations on the Pentium to better compete against the RISC chips. Therefore, the double precision format is the fastest on the Pentium and later chips.

4.2.2 HLA Support for Floating Point Values

HLA provides several data types and library routines to support the use of floating point data in your assembly language programs. These include built-in types to declare floating point variables as well as routines that provide floating point input, output, and conversion.

Perhaps the best place to start when discussing HLA's floating point facilities is with a description of floating point literal constants. HLA floating point constants allow the following syntax:

  • An optional "+" or "-" symbol, denoting the sign of the mantissa (if this is not present, HLA assumes that the mantissa is positive),
  • Followed by one or more decimal digits,
  • Optionally followed by a decimal point and one or more decimal digits,
  • Optionally followed by an "e" or "E", optionally followed by a sign ("+" or "-") and one or more decimal digits.

Note: the decimal point or the "e"/"E" must be present in order to differentiate this value from an integer or unsigned literal constant. Here are some examples of legal literal floating point constants:

1.234			3.75e2			-1.0		1.1e-1			1e+4		0.1		-123.456e+789					+25e0

Notice that a floating point literal constant cannot begin with a decimal point; it must begin with a decimal digit so you must use "0.1" to represent ".1" in your programs.

HLA also allows you to place an underscore character ("_") between any two consecutive decimal digits in a floating point literal constant. You may use the underscore character in place of a comma (or other language-specific separator character) to help make your large floating point numbers easier to read. Here are some examples:

        1_234_837.25					1_000.00				789_934.99					9_999.99

To declare a floating point variable you use the real32, real64, or real80 data types. Like their integer and unsigned brethren, the number at the end of these data type declarations specifies the number of bits used for each type's binary representation. Therefore, you use real32 to declare single precision real values, real64 to declare double precision floating point values, and real80 to declare extended precision floating point values. Other than the fact that you use these types to declare floating point variables rather than integers, their use is nearly identical to that for int8, int16, int32, etc. The following examples demonstrate these declarations and their syntax:

static

        fltVar1:			real32;

        fltVar1a:			real32 := 2.7;

        pi:			real32 := 3.14159;

        DblVar:			real64;

        DblVar2:			real64 := 1.23456789e+10;

        XPVar:			real80;

        XPVar2:			real80 := -1.0e-104;

To output a floating point variable in ASCII form, you would use one of the stdout.putr32, stdout.putr64, or stdout.putr80 routines. These procedures display a number in decimal notation, that is, a string of digits, an optional decimal point and a closing string of digits. Other than their names, these three routines use exactly the same calling sequence. Here are the calls and parameters for each of these routines:

stdout.putr80( r:real80; width:uns32; decpts:uns32 );

stdout.putr64( r:real64; width:uns32; decpts:uns32 );

stdout.putr32( r:real32; width:uns32; decpts:uns32 );

The first parameter to these procedures is the floating point value you wish to print. The size of this parameter must match the procedure's name (e.g., the r parameter must be an 80-bit extended precision floating point variable when calling the stdout.putr80 routine). The second parameter specifies the field width for the output text; this is the number of print positions the number will require when the procedure displays it. Note that this width must include print positions for the sign of the number and the decimal point. The third parameter specifies the number of print positions after the decimal point. For example,

stdout.putr32( pi, 10, 4 );

displays the value

                                _ _ _ _ 3.1416

(the underscores represent leading spaces in this example).

Of course, if the number is very large or very small, you will want to use scientific notation rather than decimal notation for your floating point numeric output. The HLA Standard Library stdout.pute32, stdout.pute64, and stdout.pute80 routines provide this facility. These routines use the following procedure prototypes:

stdout.pute80( r:real80; width:uns32 );

stdout.pute64( r:real64; width:uns32 );

stdout.pute32( r:real32; width:uns32 );

Unlike the decimal output routines, these scientific notation output routines do not require a third parameter specifying the number of digits after the decimal point to display. The width parameter, indirectly, specifies this value since all but one of the mantissa digits always appears to the right of the decimal point. These routines output their values in decimal notation, similar to the following:

1.23456789e+10					-1.0e-104				1e+2

You can also output floating point values using the HLA Standard Library stdout.put routine. If you specify the name of a floating point variable in the stdout.put parameter list, the stdout.put code will output the value using scientific notation. The actual field width varies depending on the size of the floating point variable (the stdout.put routine attempts to output as many significant digits as possible, in this case). Example:

stdout.put( "XPVar2 = ", XPVar2 );

If you specify a field width specification, by using a colon followed by a signed integer value, then the stdout.put routine will use the appropriate stdout.puteXX routine to display the value. That is, the number will still appear in scientific notation, but you get to control the field width of the output value. Like the field width for integer and unsigned values, a positive field width right justifies the number in the specified field, a negative number left justifies the value. Here is an example that prints the XPVar2 variable using ten print positions:

stdout.put( "XPVar2 = ", XPVar2:10 );

If you wish to use stdout.put to print a floating point value in decimal notation, you need to use the following syntax:

Variable_Name : Width : DecPts

Note that the DecPts field must be a non-negative integer value.

When stdout.put contains a parameter of this form, it calls the corresponding stdout.putrXX routine to display the specified floating point value. As an example, consider the following call:

stdout.put( "Pi = ", pi:5:3 );

The corresponding output is

3.142

The HLA Standard Library provides several other useful routines you can use when outputting floating point values. Consult the HLA Standard Library reference manual for more information on these routines.

The HLA Standard Library provides several routines to let you display floating point values in a wide variety of formats. In contrast, the HLA Standard Library only provides two routines to support floating point input: stdin.getf() and stdin.get(). The stdin.getf() routine requires the use of the 80x86 FPU stack, a hardware component that this chapter is not going to cover. Therefore, this chapter will defer the discussion of the stdin.getf() routine until the chapter on arithmetic, later in this text. Since the stdin.get() routine provides all the capabilities of the stdin.getf() routine, this deference will not prove to be a problem.

You've already seen the syntax for the stdin.get() routine; its parameter list simply contains a list of variable names. Stdin.get() reads appropriate values for the user for each of the variables appearing in the parameter list. If you specify the name of a floating point variable, the stdin.get() routine automatically reads a floating point value from the user and stores the result into the specified variable. The following example demonstrates the use of this routine:

    stdout.put( "Input a double precision floating point value: " );

    stdin.get( DblVar );

  Warning: This section has discussed how you would declare floating point variables and how you would input and output them.  It did not discuss arithmetic.  Floating point arithmetic is different than integer arithmetic;  you cannot use the 80x86 ADD and SUB instructions to operate on floating point values.  Floating point arithmetic will be the subject of a later chapter in this text.

4.3 Binary Coded Decimal (BCD) Representation

Although the integer and floating point formats cover most of the numeric needs of an average program, there are some special cases where other numeric representations are convenient. In this section we'll discuss the Binary Coded Decimal (BCD) format since the 80x86 CPU provides a small amount of hardware support for this data representation.

BCD values are a sequence of nibbles with each nibble representing a value in the range zero through nine. Of course you can represent values in the range 0..15 using a nibble; the BCD format, however, uses only 10 of the possible 16 different values for each nibble.

Each nibble in a BCD value represents a single decimal digit. Therefore, with a single byte (i.e., two digits) we can represent values containing two decimal digits, or values in the range 0..99. With a word, we can represent values having four decimal digits, or values in the range 0..9999. Likewise, with a double word we can represent values with up to eight decimal digits (since there are eight nibbles in a double word value).

Figure 4.5 BCD Data Representation in Memory

As you can see, BCD storage isn't particularly memory efficient. For example, an eight-bit BCD variable can represent values in the range 0..99 while that same eight bits, when holding a binary value, can represent values in the range 0..255. Likewise, a 16-bit binary value can represent values in the range 0..65535 while a 16-bit BCD value can only represent about 1/6 of those values (0..9999). Inefficient storage isn't the only problem. BCD calculations tend to be slower than binary calculations.

At this point, you're probably wondering why anyone would ever use the BCD format. The BCD format does have two saving graces: it's very easy to convert BCD values between the internal numeric representation and their string representation; also, its very easy to encode multi-digit decimal values in hardware (e.g., using a "thumb wheel" or dial) using BCD than it is using binary. For these two reasons, you're likely to see people using BCD in embedded systems (e.g., toaster ovens and alarm clocks) but rarely in general purpose computer software.

A few decades ago people mistakenly thought that calculations involving BCD (or just `decimal') arithmetic was more accurate than binary calculations. Therefore, they would often perform `important' calculations, like those involving dollars and cents (or other monetary units) using decimal-based arithmetic. While it is true that certain calculations can produce more accurate results in BCD, this statement is not true in general. Indeed, for most calculations (even those involving fixed point decimal arithmetic), the binary representation is more accurate. For this reason, most modern computer programs represent all values in a binary form. For example, the Intel x86 floating point unit (FPU) supports a pair of instructions for loading and storing BCD values. Internally, however, the FPU converts these BCD values to binary and performs all calculations in binary. It only uses BCD as an external data format (external to the FPU, that is). This generally produces more accurate results and requires far less silicon than having a separate coprocessor that supports decimal arithmetic.

This text will take up the subject of BCD arithmetic in a later chapter. Until then, you can safely ignore BCD unless you find yourself converting a COBOL program to assembly language (which is quite unlikely).

1There are other numeric formats, such as fixed point formats and binary coded decimal format.

2There were some minor changes to the way certain degenerate operations were handled, but the bit representation remained essentially unchanged.

3The binary point is the same thing as the decimal point except it appears in binary numbers rather than decimal numbers.

4Actually, this isn't necessarily true. The IEEE floating point format supports denormalized values where the H.O. bit is not zero. However, we will ignore denormalized values in our discussion.

5The alternative would be to underflow the values to zero.


Chapter Eleven Real Arithmetic

11.1 Chapter Overview

This chapter discusses the implementation of floating point arithmetic computation in assembly language. By the conclusion of this chapter you should be able to translate arithmetic expressions and assignment statements involving floating point operands from high level languages like Pascal and C/C++ into 80x86 assembly language.

11.2 Floating Point Arithmetic

When the 8086 CPU first appeared in the late 1970's, semiconductor technology was not to the point where Intel could put floating point instructions directly on the 8086 CPU. Therefore, they devised a scheme whereby they could use a second chip to perform the floating point calculations - the floating point unit (or FPU)1. They released their original floating point chip, the 8087, in 1980. This particular FPU worked with the 8086, 8088, 80186, and 80188 CPUs. When Intel introduced the 80286 CPU, they released a redesigned 80287 FPU chip to accompany it. Although the 80287 was compatible with the 80386 CPU, Intel designed a better FPU, the 80387, for use in 80386 systems. The 80486 CPU was the first Intel CPU to include an on-chip floating point unit. Shortly after the release of the 80486, Intel introduced the 80486sx CPU that was an 80486 without the built-in FPU. To get floating point capabilities on this chip, you had to add an 80487 chip, although the 80487 was really nothing more than a full-blown 80486 which took over for the "sx" chip in the system. Intel's Pentium chips provide a high-performance floating point unit directly on the CPU. There is no (Intel) floating point coprocessor available for the Pentium chip.

Collectively, we will refer to all these chips as the 80x87 FPU. Given the obsolescence of the 8086, 80286, 8087, 80287, 80387, and 80487 chips, this text will concentrate on the Pentium and later chips. There are some differences between the Pentium floating point units and the earlier FPUs. If you need to write code that will execute on those earlier machines, you should consult the appropriate Intel documentation for those devices.

11.2.1 FPU Registers

The 80x86 FPUs add 13 registers to the 80x86 and later processors: eight floating point data registers, a control register, a status register, a tag register, an instruction pointer, and a data pointer. The data registers are similar to the 80x86's general purpose register set insofar as all floating point calculations take place in these registers. The control register contains bits that let you decide how the FPU handles certain degenerate cases like rounding of inaccurate computations, it contains bits that control precision, and so on. The status register is similar to the 80x86's flags register; it contains the condition code bits and several other floating point flags that describe the state of the FPU. The tag register contains several groups of bits that determine the state of the value in each of the eight general purpose registers. The instruction and data pointer registers contain certain state information about the last floating point instruction executed. We will not consider the last three registers in this text, see the Intel documentation for more details.

11.2.1.1 FPU Data Registers

The FPUs provide eight 80 bit data registers organized as a stack. This is a significant departure from the organization of the general purpose registers on the 80x86 CPU that comprise a standard general-purpose register set. HLA refers to these registers as ST0, ST1, ..., ST7.

The biggest difference between the FPU register set and the 80x86 register set is the stack organization. On the 80x86 CPU, the AX register is always the AX register, no matter what happens. On the FPU, however, the register set is an eight element stack of 80 bit floating point values (see Figure 11.1).

Figure 11.1 FPU Floating Point Register Stack

ST0 refers to the item on the top of the stack, ST1 refers to the next item on the stack, and so on. Many floating point instructions push and pop items on the stack; therefore, ST1 will refer to the previous contents of ST0 after you push something onto the stack. It will take some thought and practice to get used to the fact that the registers are changing under you, but this is an easy problem to overcome.

11.2.1.2 The FPU Control Register

When Intel designed the 80x87 (and, essentially, the IEEE floating point standard), there were no standards in floating point hardware. Different (mainframe and mini) computer manufacturers all had different and incompatible floating point formats. Unfortunately, much application software had been written taking into account the idiosyncrasies of these different floating point formats. Intel wanted to design an FPU that could work with the majority of the software out there (keep in mind, the IBM PC was three to four years away when Intel began designing the 8087, they couldn't rely on that "mountain" of software available for the PC to make their chip popular). Unfortunately, many of the features found in these older floating point formats were mutually incompatible. For example, in some floating point systems rounding would occur when there was insufficient precision; in others, truncation would occur. Some applications would work with one floating point system but not with the other. Intel wanted as many applications as possible to work with as few changes as possible on their 80x87 FPUs, so they added a special register, the FPU control register, that lets the user choose one of several possible operating modes for their FPU.

The 80x87 control register contains 16 bits organized as shown in Figure 11.2.

Figure 11.2 FPU Control Register

Bits 10 and 11 provide rounding control according to the following values:
Table 1: Rounding Control

Bits 10 & 11

Function

00

To nearest or even

01

Round down

10

Round up

11

Truncate

The "00" setting is the default. The FPU rounds values above one-half of the least significant bit up. It rounds values below one-half of the least significant bit down. If the value below the least significant bit is exactly one-half of the least significant bit, the FPU rounds the value towards the value whose least significant bit is zero. For long strings of computations, this provides a reasonable, automatic, way to maintain maximum precision.

The round up and round down options are present for those computations where it is important to keep track of the accuracy during a computation. By setting the rounding control to round down and performing the operation, then repeating the operation with the rounding control set to round up, you can determine the minimum and maximum ranges between which the true result will fall.

The truncate option forces all computations to truncate any excess bits during the computation. You will rarely use this option if accuracy is important to you. However, if you are porting older software to the FPU, you might use this option to help when porting the software. One place where this option is extremely useful is when converting a floating point value to an integer. Since most software expects floating point to integer conversions to truncate the result, you will need to use the truncation rounding mode to achieve this.

Bits eight and nine of the control register specify the precision during computation. This capability is provided to allow compatibility with older software as required by the IEEE 754 standard. The precision control bits use the following values:

Table 2: Mantissa Precision Control Bits
Bits 8 & 9
Precision Control
00
24 bits
01
Reserved
10
53 bits
11
64 bits

Some CPUs may operate faster with floating point values whose precision is 53 bits (i.e., 64-bit floating point format) rather than 64 bits (i.e., 80-bit floating point format). Please see the documentation for your specific processor for details. Generally, the CPU defaults these bits to %11 to select the 64-bit mantissa precision.

Bits zero through five are the exception masks. These are similar to the interrupt enable bit in the 80x86's flags register. If these bits contain a one, the corresponding condition is ignored by the FPU. However, if any bit contains zero, and the corresponding condition occurs, then the FPU immediately generates an interrupt so the program can handle the degenerate condition.

Bit zero corresponds to an invalid operation error. This generally occurs as the result of a programming error. Problems which raise the invalid operation exception include pushing more than eight items onto the stack or attempting to pop an item off an empty stack, taking the square root of a negative number, or loading a non-empty register.

Bit one masks the denormalized interrupt that occurs whenever you try to manipulate denormalized values. Denormalized exceptions occur when you load arbitrary extended precision values into the FPU or work with very small numbers just beyond the range of the FPU's capabilities. Normally, you would probably not enable this exception. If you enable this exception and the FPU generates this interrupt, the HLA run-time system raises the ex.fDenormal exception.

Bit two masks the zero divide exception. If this bit contains zero, the FPU will generate an interrupt if you attempt to divide a nonzero value by zero. If you do not enable the zero division exception, the FPU will produce NaN (not a number) whenever you perform a zero division. It's probably a good idea to enable this exception by programming a zero into this bit. Note that if your program generates this interrupt, the HLA run-time system will raise the ex.fDivByZero exception.

Bit three masks the overflow exception. The FPU will raise the overflow exception if a calculation overflows or if you attempt to store a value which is too large to fit into a destination operand (e.g., storing a large extended precision value into a single precision variable). If you enable this exception and the FPU generates this interrupt, the HLA run-time system raises the ex.fOverflow exception.

Bit four, if set, masks the underflow exception. Underflow occurs when the result is too small to fit in the destination operand. Like overflow, this exception can occur whenever you store a small extended precision value into a smaller variable (single or double precision) or when the result of a computation is too small for extended precision. If you enable this exception and the FPU generates this interrupt, the HLA run-time system raises the ex.fUnderflow exception.

Bit five controls whether the precision exception can occur. A precision exception occurs whenever the FPU produces an imprecise result, generally the result of an internal rounding operation. Although many operations will produce an exact result, many more will not. For example, dividing one by ten will produce an inexact result. Therefore, this bit is usually one since inexact results are very common. If you enable this exception and the FPU generates this interrupt, the HLA run-time system raises the ex.InexactResult exception.

Bits six and thirteen through fifteen in the control register are currently undefined and reserved for future use. Bit seven is the interrupt enable mask, but it is only active on the 8087 FPU; a zero in this bit enables 8087 interrupts and a one disables FPU interrupts.

The FPU provides two instructions, FLDCW (load control word) and FSTCW (store control word), that let you load and store the contents of the control register. The single operand to these instructions must be a 16 bit memory location. The FLDCW instruction loads the control register from the specified memory location, FSTCW stores the control register into the specified memory location. The syntax for these instructions is

fldcw( mem16 );

fstcw( mem16 );

Here's some example code that sets the rounding control to "truncate result" and sets the rounding precision to 24 bits:

static

    fcw16: word;

        .

        .

        .

        fstcw( fcw16 );

        mov( fcw16, ax );

        and( $f0ff, ax );      // Clears bits 8-11.

        or( $0c00, ax );       // Rounding control=%11, Precision = %00.

        mov( ax, fcw16 );

        fldcw( fcw16 );

11.2.1.3 The FPU Status Register

The FPU status register provides the status of the coprocessor at the instant you read it. The FSTSW instruction stores the16 bit floating point status register into a word variable. The status register is a 16 bit register, its layout appears in Figure 11.3.

Figure 11.3 The FPU Status Register

Bits zero through five are the exception flags. These bits are appear in the same order as the exception masks in the control register. If the corresponding condition exists, then the bit is set. These bits are independent of the exception masks in the control register. The FPU sets and clears these bits regardless of the corresponding mask setting.

Bit six indicates a stack fault. A stack fault occurs whenever there is a stack overflow or underflow. When this bit is set, the C1 condition code bit determines whether there was a stack overflow (C1=1) or stack underflow (C1=0) condition.

Bit seven of the status register is set if any error condition bit is set. It is the logical OR of bits zero through five. A program can test this bit to quickly determine if an error condition exists.

Bits eight, nine, ten, and fourteen are the coprocessor condition code bits. Various instructions set the condition code bits as shown in the following table:

Table 3: FPU Condition Code Bits
Instruction
Condition Code Bits
Condition
C3
C2
C1
C0
fcom,
fcomp,
fcompp,
ficom,
ficomp
0
0
1
1
0
0
0
1
X
X
X
X
0
1
0
1
ST > source
ST < source
ST = source
ST or source undefined
ftst
0
0
1
1
0
0
0
1
X
X
X
X
0
1
0
1
ST is positive
ST is negative
ST is zero (+ or -)
ST is uncomparable
fxam
0
0
0
0
1
1
1
1
0
0
0
0
1
0
0
1
1
0
0
1
1
0
0
1
1
X
0
1
0
1
0
1
0
1
0
1
0
1
X

0
0
0
0
0
0
0
1
1
1
1
1
+ Unnormalized
-Unnormalized
+Normalized
-Normalized
+0
-0
+Denormalized
-Denormalized
+NaN
-NaN
+Infinity
-Infinity
Empty register
fucom,
fucomp,
fucompp
0
0
1
1
0
0
0
1
X
X
X
X
0
1
0
1
ST > source
ST < source
ST = source
Unordered

X = Don't care

Table 4: Condition Code Interpretations
Instruction(s)
Condition Code Bits
C0
C3
C2
C1
fcom, fcomp, fcmpp, ftst, fucom, fucomp, fucompp, ficom, ficomp
Result of comparison. See previous table.
Result of comparison. See previous table.
Operands are not comparable
Result of comparison. See previous table.
Also denotes stack overflow/underflow if stack exception bit is set.
fxam
See previous table.
See previous table.
See previous table.
Sign of result, or stack overflow/underflow (if stack exception bit is set).
fprem, fprem1
Bit 2 of remainder
Bit 0 of remainder
0- reduction done.
1- reduction incomplete.
Bit 1 of remainder or stack overflow/underflow (if stack exception bit is set).
fist, fbstp, frndint, fst, fstp, fadd, fmul, fdiv, fdivr, fsub, fsubr, fscale, fsqrt, fpatan, f2xm1, fyl2x, fyl2xp1

Undefined

Undefined

Undefined
Round up occurred or stack overflow/underflow (if stack exception bit is set).
fptan, fsin, fcos, fsincos

Undefined

Undefined
0- reduction done.
1- reduction incomplete.
Round up occurred or stack overflow/underflow (if stack exception bit is set).
fchs, fabs, fxch, fincstp, fdecstp, constant loads, fxtract, fld, fild, fbld, fstp (80 bit)

Undefined

Undefined

Undefined
Zero result or stack overflow/underflow (if stack exception bit is set).
fldenv, fstor
Restored from memory operand.
Restored from memory operand.
Restored from memory operand.
Restored from memory operand.
fldcw, fstenv, fstcw, fstsw, fclex

Undefined

Undefined

Undefined

Undefined
finit, fsave
Cleared to zero.
Cleared to zero.
Cleared to zero.
Cleared to zero.

Bits 11-13 of the FPU status register provide the register number of the top of stack. During computations, the FPU adds (modulo eight) the logical register numbers supplied by the programmer to these three bits to determine the physical register number at run time.

Bit 15 of the status register is the busy bit. It is set whenever the FPU is busy. Most programs will have little reason to access this bit.

11.2.2 FPU Data Types

The FPU supports seven different data types: three integer types, a packed decimal type, and three floating point types. The integer type provides for 64-bit integers, although it is often faster to do the 64-bit arithmetic using the integer unit of the CPU (see the chapter on Advanced Arithmetic). Certainly it is often faster to do 16-bit and 32-bit integer arithmetic using the standard integer registers. The packed decimal type provides a 17 digit signed decimal (BCD) integer. The primary purpose of the BCD format is to convert between strings and floating point values. The remaining three data types are the 32 bit, 64 bit, and 80 bit floating point data types we've looked at so far. The 80x87 data types appear in Figure 11.4, Figure 11.5, and Figure 11.6.

Figure 11.4 FPU Floating Point Formats

Figure 11.5 FPU Integer Formats

Figure 11.6 FPU Packed Decimal Format

The FPU generally stores values in a normalized format. When a floating point number is normalized, the H.O. bit of the mantissa is always one. In the 32 and 64 bit floating point formats, the FPU does not actually store this bit, the FPU always assumes that it is one. Therefore, 32 and 64 bit floating point numbers are always normalized. In the extended precision 80 bit floating point format, the FPU does not assume that the H.O. bit of the mantissa is one, the H.O. bit of the mantissa appears as part of the string of bits.

Normalized values provide the greatest precision for a given number of bits. However, there are a large number of non-normalized values which we cannot represent with the 80-bit format. These values are very close to zero and represent the set of values whose mantissa H.O. bit is not zero. The FPUs support a special 80-bit form known as denormalized values. Denormalized values allow the FPU to encode very small values it cannot encode using normalized values, but at a price. Denormalized values offer fewer bits of precision than normalized values. Therefore, using denormalized values in a computation may introduce some slight inaccuracy into a computation. Of course, this is always better than underflowing the denormalized value to zero (which could make the computation even less accurate), but you must keep in mind that if you work with very small values you may lose some accuracy in your computations. Note that the FPU status register contains a bit you can use to detect when the FPU uses a denormalized value in a computation.

1Intel has also referred to this device as the Numeric Data Processor (NDP), Numeric Processor Extension (NPX), and math coprocessor.


11.2.3 The FPU Instruction Set

The FPU adds over 80 new instructions to the 80x86 instruction set. We can classify these instructions as data movement instructions, conversions, arithmetic instructions, comparisons, constant instructions, transcendental instructions, and miscellaneous instructions. The following sections describe each of the instructions in these categories.

11.2.4 FPU Data Movement Instructions

The data movement instructions transfer data between the internal FPU registers and memory. The instructions in this category are FLD, FST, FSTP, and FXCH. The FLD instruction always pushes its operand onto the floating point stack. The FSTP instruction always pops the top of stack after storing the top of stack (tos). The remaining instructions do not affect the number of items on the stack.

11.2.4.1 The FLD Instruction

The FLD instruction loads a 32 bit, 64 bit, or 80 bit floating point value onto the stack. This instruction converts 32 and 64 bit operands to an 80 bit extended precision value before pushing the value onto the floating point stack.

The FLD instruction first decrements the top of stack (TOS) pointer (bits 11-13 of the status register) and then stores the 80 bit value in the physical register specified by the new TOS pointer. If the source operand of the FLD instruction is a floating point data register, STi, then the actual register the FPU uses for the load operation is the register number before decrementing the tos pointer. Therefore, "fld( st0 );" duplicates the value on the top of the stack.

The FLD instruction sets the stack fault bit if stack overflow occurs. It sets the denormalized exception bit if you load an 80-bit denormalized value. It sets the invalid operation bit if you attempt to load an empty floating point register onto the stop of stack (or perform some other invalid operation).

Examples:

        fld( st1 );

        fld( real32_variable );

        fld( real64_variable );

        fld( real80_variable );

        fld( real_constant );

Note that there is no way to directly load a 32-bit integer register onto the floating point stack, even if that register contains a REAL32 value. To accomplish this, you must first store the integer register into a memory location then you can push that memory location onto the FPU stack using the FLD instruction. E.g.,

        mov( eax, tempReal32 );								// Save REAL32 value in EAX to memory.

        fld( tempReal32 );								// Push that real value onto the FPU stack.

Note: loading a constant via FLD is actually an HLA extension. The FPU doesn't support this instruction type. HLA creates a REAL80 object in the "constants" segment and uses the address of this memory object as the true operand for FLD.

11.2.4.2 The FST and FSTP Instructions

The FST and FSTP instructions copy the value on the top of the floating point register stack to another floating point register or to a 32, 64, or 80 bit memory variable. When copying data to a 32 or 64 bit memory variable, the 80 bit extended precision value on the top of stack is rounded to the smaller format as specified by the rounding control bits in the FPU control register.

The FSTP instruction pops the value off the top of stack when moving it to the destination location. It does this by incrementing the top of stack pointer in the status register after accessing the data in ST0. If the destination operand is a floating point register, the FPU stores the value at the specified register number before popping the data off the top of the stack.

Executing an "fstp( st0 );" instruction effectively pops the data off the top of stack with no data transfer. Examples:

        fst( real32_variable );

        fst( real64_variable );

        fst( realArray[ ebx*8 ] );

        fst( real80_variable );

        fst( st2 );

        fstp( st1 );

The last example above effectively pops ST1 while leaving ST0 on the top of the stack.

The FST and FSTP instructions will set the stack exception bit if a stack underflow occurs (attempting to store a value from an empty register stack). They will set the precision bit if there is a loss of precision during the store operation (this will occur, for example, when storing an 80 bit extended precision value into a 32 or 64 bit memory variable and there are some bits lost during conversion). They will set the underflow exception bit when storing an 80 bit value into a 32 or 64 bit memory variable, but the value is too small to fit into the destination operand. Likewise, these instructions will set the overflow exception bit if the value on the top of stack is too big to fit into a 32 or 64 bit memory variable. The FST and FSTP instructions set the denormalized flag when you try to store a denormalized value into an 80 bit register or variable1. They set the invalid operation flag if an invalid operation (such as storing into an empty register) occurs. Finally, these instructions set the C1 condition bit if rounding occurs during the store operation (this only occurs when storing into a 32 or 64 bit memory variable and you have to round the mantissa to fit into the destination).

Note: Because of an idiosyncrasy in the FPU instruction set related to the encoding of the instructions, you cannot use the FST instruction to store data into a real80 memory variable. You may, however, store 80-bit data using the FSTP instruction.

11.2.4.3 The FXCH Instruction

The FXCH instruction exchanges the value on the top of stack with one of the other FPU registers. This instruction takes two forms: one with a single FPU register as an operand, the second without any operands. The first form exchanges the top of stack (tos) with the specified register. The second form of FXCH swaps the top of stack with ST1.

Many FPU instructions, e.g., FSQRT, operate only on the top of the register stack. If you want to perform such an operation on a value that is not on the top of stack, you can use the FXCH instruction to swap that register with tos, perform the desired operation, and then use the FXCH to swap the tos with the original register. The following example takes the square root of ST2:

        fxch( st2 );

        fsqrt();

        fxch( st2 );

The FXCH instruction sets the stack exception bit if the stack is empty. It sets the invalid operation bit if you specify an empty register as the operand. This instruction always clears the C1 condition code bit.

11.2.5 Conversions

The FPU performs all arithmetic operations on 80 bit real quantities. In a sense, the FLD and FST/FSTP instructions are conversion instructions as well as data movement instructions because they automatically convert between the internal 80 bit real format and the 32 and 64 bit memory formats. Nonetheless, we'll simply classify them as data movement operations, rather than conversions, because they are moving real values to and from memory. The FPU provides five other instructions that convert to or from integer or binary coded decimal (BCD) format when moving data. These instructions are FILD, FIST, FISTP, FBLD, and FBSTP.

11.2.5.1 The FILD Instruction

The FILD (integer load) instruction converts a 16, 32, or 64 bit two's complement integer to the 80 bit extended precision format and pushes the result onto the stack. This instruction always expects a single operand. This operand must be the address of a word, double word, or quad word integer variable. You cannot specify one of the 80x86's 16 or 32 bit general purpose registers. If you want to push an 80x86 general purpose register onto the FPU stack, you must first store it into a memory variable and then use FILD to push that value of that memory variable.

The FILD instruction sets the stack exception bit and C1 (accordingly) if stack overflow occurs while pushing the converted value. Examples:

        fild( word_variable );

        fild( dword_val[ ecx*4 ] );

        fild( qword_variable );

11.2.5.2 The FIST and FISTP Instructions

The FIST and FISTP instructions convert the 80 bit extended precision variable on the top of stack to a 16, 32, or 64 bit integer and store the result away into the memory variable specified by the single operand. These instructions convert the value on tos to an integer according to the rounding setting in the FPU control register (bits 10 and 11). As for the FILD instruction, the FIST and FISTP instructions will not let you specify one of the 80x86's general purpose 16 or 32 bit registers as the destination operand.

The FIST instruction converts the value on the top of stack to an integer and then stores the result; it does not otherwise affect the floating point register stack. The FISTP instruction pops the value off the floating point register stack after storing the converted value.

These instructions set the stack exception bit if the floating point register stack is empty (this will also clear C1). They set the precision (imprecise operation) and C1 bits if rounding occurs (that is, if there is any fractional component to the value in ST0). These instructions set the underflow exception bit if the result is too small (i.e., less than one but greater than zero or less than zero but greater than -1). Examples:

        fist( word_var[ ebx*2 ] );

        fist( qword_var );

        fistp( dword_var );

Don't forget that these instructions use the rounding control settings to determine how they will convert the floating point data to an integer during the store operation. Be default, the rounding control is usually set to "round" mode; yet most programmers expect FIST/FISTP to truncate the decimal portion during conversion. If you want FIST/FISTP to truncate floating point values when converting them to an integer, you will need to set the rounding control bits appropriately in the floating point control register, e.g.,

static

    fcw16:				word;

    fcw16_2:				word;

    IntResult:				int32;

        .

        .

        .

        fstcw( fcw16 );

        mov( fcw16, ax );

        or( $0c00, ax );       // Rounding control=%11 (truncate).

        mov( ax, fcw16_2 );    // Store into memory and reload the ctrl word.

        fldcw( fcw16_2 );

        fistp( IntResult );							// Truncate ST0 and store as int32 object.

        fldcw( fcw16 );							// Restore original rounding control

11.2.5.3 The FBLD and FBSTP Instructions

The FBLD and FBSTP instructions load and store 80 bit BCD values. The FBLD instruction converts a BCD value to its 80 bit extended precision equivalent and pushes the result onto the stack. The FBSTP instruction pops the extended precision real value on TOS, converts it to an 80 bit BCD value (rounding according to the bits in the floating point control register), and stores the converted result at the address specified by the destination memory operand. Note that there is no FBST instruction which stores the value on tos without popping it.

The FBLD instruction sets the stack exception bit and C1 if stack overflow occurs. It sets the invalid operation bit if you attempt to load an invalid BCD value. The FBSTP instruction sets the stack exception bit and clears C1 if stack underflow occurs (the stack is empty). It sets the underflow flag under the same conditions as FIST and FISTP. Examples:

// Assuming fewer than eight items on the stack, the following

// code sequence is equivalent to an fbst instruction:

        fld( st0 );

        fbstp( tbyte_var );

// The following example easily converts an 80 bit BCD value to

// a 64 bit integer:

        fbld( tbyte_var );

        fist( qword_var );

11.2.6 Arithmetic Instructions

The arithmetic instructions make up a small, but important, subset of the FPU's instruction set. These instructions fall into two general categories - those which operate on real values and those which operate on a real and an integer value.

11.2.6.1 The FADD and FADDP Instructions

These two instructions take the following forms:

        fadd()

        faddp()

        fadd( st0, sti );

        fadd( sti, st0 );

        faddp( st0, sti );

        fadd( mem_32_64 );

        fadd( real_constant );

The first two forms are equivalent. They pop the two values on the top of stack, add them, and push their sum back onto the stack.

The next two forms of the FADD instruction, those with two FPU register operands, behave like the 80x86's ADD instruction. They add the value in the source register operand to the value in the destination register operand. Note that one of the register operands must be ST0.

The FADDP instruction with two operands adds ST0 (which must always be the source operand) to the destination operand and then pops ST0. The destination operand must be one of the other FPU registers.

The last form above, FADD with a memory operand, adds a 32 or 64 bit floating point variable to the value in ST0. This instruction will convert the 32 or 64 bit operands to an 80 bit extended precision value before performing the addition. Note that this instruction does not allow an 80 bit memory operand.

These instructions can raise the stack, precision, underflow, overflow, denormalized, and illegal operation exceptions, as appropriate. If a stack fault exception occurs, C1 denotes stack overflow or underflow.

Like FLD( real_constant), the FADD( real_constant ) instruction is an HLA extension. Note that it creates a 64-bit variable holding the constant value and emits the FADD( mem64 ) instruction, specifying the read-only object it creates in the constants segment.

11.2.6.2 The FSUB, FSUBP, FSUBR, and FSUBRP Instructions

These four instructions take the following forms:

        fsub()

        fsubp()

        fsubr()

        fsubrp()

        fsub( st0, sti )

        fsub( sti, st0 );

        fsubp( st0, sti );

        fsub( mem_32_64 );

        fsub( real_constant );

        fsubr( st0, sti )

        fsubr( sti, st0 );

        fsubrp( st0, sti );

        fsubr( mem_32_64 );

        fsubr( real_constant );

With no operands, the FSUB and FSUBP instructions operate identically. They pop ST0 and ST1 from the register stack, compute ST1-ST0, and the push the difference back onto the stack. The FSUBR and FSUBRP instructions (reverse subtraction) operate in an almost identical fashion except they compute ST0-ST1 and push that difference.

With two register operands ( source, destination ) the FSUB instruction computes destination := destination - source. One of the two registers must be ST0. With two registers as operands, the FSUBP also computes destination := destination - source and then it pops ST0 off the stack after computing the difference. For the FSUBP instruction, the source operand must be ST0.

With two register operands, the FSUBR and FSUBRP instruction work in a similar fashion to FSUB and FSUBP, except they compute destination := source - destination.

The FSUB(mem) and FSUBR(mem) instructions accept a 32 or 64 bit memory operand. They convert the memory operand to an 80 bit extended precision value and subtract this from ST0 (FSUB) or subtract ST0 from this value (FSUBR) and store the result back into ST0.

These instructions can raise the stack, precision, underflow, overflow, denormalized, and illegal operation exceptions, as appropriate. If a stack fault exception occurs, C1 denotes stack overflow or underflow.

Note: the instructions that have real constants as operands aren't true FPU instructions. These are extensions provided by HLA. HLA generates a constant segment memory object initialized with the constant's value.

11.2.6.3 The FMUL and FMULP Instructions

The FMUL and FMULP instructions multiply two floating point values. These instructions allow the following forms:

        fmul()

        fmulp()

        fmul( sti, st0 );

        fmul( st0, sti );

        fmul( mem_32_64 );

        fmul( real_constant );

        fmulp( st0, sti );

With no operands, FMUL and FMULP both do the same thing - they pop ST0 and ST1, multiply these values, and push their product back onto the stack. The FMUL instructions with two register operands compute destination := destination * source. One of the registers (source or destination) must be ST0.

The FMULP( ST0, STi ) instruction computes STi := STi * ST0 and then pops ST0. This instruction uses the value for i before popping ST0. The FMUL(mem) instruction requires a 32 or 64 bit memory operand. It converts the specified memory variable to an 80 bit extended precision value and the multiplies ST0 by this value.

These instructions can raise the stack, precision, underflow, overflow, denormalized, and illegal operation exceptions, as appropriate. If rounding occurs during the computation, these instructions set the C1 condition code bit. If a stack fault exception occurs, C1 denotes stack overflow or underflow.

Note: the instruction that has a real constant as its operand isn't a true FPU instruction. It is an extension provided by HLA (see the note at the end of the previous section for details).

11.2.6.4 The FDIV, FDIVP, FDIVR, and FDIVRP Instructions

These four instructions allow the following forms:

        fdiv()

        fdivp()

        fdivr()

        fdivrp()

        fdiv( sti, st0 );

        fdiv( st0, sti );

        fdivp( st0, sti );

        fdivr( sti, st0 );

        fdivr( st0, sti );

        fdivrp( st0, sti );

        fdiv( mem_32_64 );

        fdivr( mem_32_64 );

        fdiv( real_constant );

        fdivr( real_constant );

With no operands, the FDIV and FDIVP instructions pop ST0 and ST1, compute ST1/ST0, and push the result back onto the stack. The FDIVR and FDIVRP instructions also pop ST0 and ST1 but compute ST0/ST1 before pushing the quotient onto the stack.

With two register operands, these instructions compute the following quotients:

        fdiv( sti, st0 );						// ST0 := ST0/STi

        fdiv( st0, sti );						// STi := STi/ST0

        fdivp( st0, sti );						// STi := STi/ST0  then pop ST0

        fdivr( st0, sti );						// ST0 := ST0/STi

        fdivrp( st0, sti );						// STi := ST0/STi then pop ST0

The FDIVP and FDIVRP instructions also pop ST0 after performing the division operation. The value for i in these two instructions is computed before popping ST0.

These instructions can raise the stack, precision, underflow, overflow, denormalized, zero divide, and illegal operation exceptions, as appropriate. If rounding occurs during the computation, these instructions set the C1 condition code bit. If a stack fault exception occurs, C1 denotes stack overflow or underflow.

Note: the instructions that have real constants as operands aren't true FPU instructions. These are extensions provided by HLA.

11.2.6.5 The FSQRT Instruction

The FSQRT routine does not allow any operands. It computes the square root of the value on top of stack (TOS) and replaces ST0 with this result. The value on TOS must be zero or positive, otherwise FSQRT will generate an invalid operation exception.

This instruction can raise the stack, precision, denormalized, and invalid operation exceptions, as appropriate. If rounding occurs during the computation, FSQRT sets the C1 condition code bit. If a stack fault exception occurs, C1 denotes stack overflow or underflow.

Example:

// Compute Z := sqrt(x**2 + y**2);

        fld( x );				// Load X.

        fld( st0 );				// Duplicate X on TOS.

        fmul();				// Compute X**2.

        fld( y );				// Load Y

        fld( st0 );				// Duplicate Y.

        fmul();				// Compute Y**2.

        fadd();				// Compute X**2 + Y**2.

        fsqrt();				// Compute sqrt( X**2 + Y**2 ).

        fstp( z );				// Store result away into Z.

11.2.6.6 The FPREM and FPREM1 Instructions

The FPREM and FPREM1 instructions compute a partial remainder. Intel designed the FPREM instruction before the IEEE finalized their floating point standard. In the final draft of the IEEE floating point standard, the definition of FPREM was a little different than Intel's original design. Unfortunately, Intel needed to maintain compatibility with the existing software that used the FPREM instruction, so they designed a new version to handle the IEEE partial remainder operation, FPREM1. You should always use FPREM1 in new software you write, therefore we will only discuss FPREM1 here, although you use FPREM in an identical fashion.

FPREM1 computes the partial remainder of ST0/ST1. If the difference between the exponents of ST0 and ST1 is less than 64, FPREM1 can compute the exact remainder in one operation. Otherwise you will have to execute the FPREM1 two or more times to get the correct remainder value. The C2 condition code bit determines when the computation is complete. Note that FPREM1 does not pop the two operands off the stack; it leaves the partial remainder in ST0 and the original divisor in ST1 in case you need to compute another partial product to complete the result.

The FPREM1 instruction sets the stack exception flag if there aren't two values on the top of stack. It sets the underflow and denormal exception bits if the result is too small. It sets the invalid operation bit if the values on tos are inappropriate for this operation. It sets the C2 condition code bit if the partial remainder operation is not complete. Finally, it loads C3, C1, and C0 with bits zero, one, and two of the quotient, respectively.

Example:

// Compute Z := X mod Y

        fld( y );

        fld( x );

        repeat

            fprem1();

            fstsw( ax );     // Get condition code bits into AX.

            and( 1, ah );    // See if C2 is set.

        until( @z );        // Repeat until C2 is clear.

        fstp( z );          // Store away the remainder.

        fstp( st0 );        // Pop old Y value.

11.2.6.7 The FRNDINT Instruction

The FRNDINT instruction rounds the value on the top of stack (TOS) to the nearest integer using the rounding algorithm specified in the control register.

This instruction sets the stack exception flag if there is no value on the TOS (it will also clear C1 in this case). It sets the precision and denormal exception bits if there was a loss of precision. It sets the invalid operation flag if the value on the tos is not a valid number. Note that the result on tos is still a floating point value, it simply does not have a fractional component.

11.2.6.8 The FABS Instruction

FABS computes the absolute value of ST0 by clearing the mantissa sign bit of ST0. It sets the stack exception bit and invalid operation bits if the stack is empty.

Example:

// Compute X := sqrt(abs(x));

        fld( x );

        fabs();

        fsqrt();

        fstp( x );

11.2.6.9 The FCHS Instruction

FCHS changes the sign of ST0's value by inverting the mantissa sign bit (that is, this is the floating point negation instruction). It sets the stack exception bit and invalid operation bits if the stack is empty. Example:

// Compute X := -X if X is positive, X := X if X is negative.

        fld( x );

        fabs();

        fchs();

        fstp( x );

11.2.7 Comparison Instructions

The FPU provides several instructions for comparing real values. The FCOM, FCOMP, and FCOMPP instructions compare the two values on the top of stack and set the condition codes appropriately. The FTST instruction compares the value on the top of stack with zero.

Generally, most programs test the condition code bits immediately after a comparison. Unfortunately, there are no conditional jump instructions that branch based on the FPU condition codes. Instead, you can use the FSTSW instruction to copy the floating point status register (see "The FPU Status Register" on page 615) into the AX register; then you can use the SAHF instruction to copy the AH register into the 80x86's condition code bits. After doing this, you can use the conditional jump instructions to test some condition. This technique copies C0 into the carry flag, C2 into the parity flag, and C3 into the zero flag. The SAHF instruction does not copy C1 into any of the 80x86's flag bits.

Since the SAHF instruction does not copy any FPU status bits into the sign or overflow flags, you cannot use signed comparison instructions. Instead, use unsigned operations (e.g., SETA, SETB) when testing the results of a floating point comparison. Yes, these instructions normally test unsigned values and floating point numbers are signed values. However, use the unsigned operations anyway; the FSTSW and SAHF instructions set the 80x86 flags register as though you had compared unsigned values with the CMP instruction.

The Pentium II and (upwards) compatible processors provide an extra set of floating point comparison instructions that directly affect the 80x86 condition code flags. These instructions circumvent having to use FSTSW and SAHF to copy the FPU status into the 80x86 condition codes. These instructions include FCOMI and FCOMIP. You use them just like the FCOM and FCOMP instructions except, of course, you do not have to manually copy the status bits to the FLAGS register. Do be aware that these instructions are not available on many processors in common use today (as of 1/1/2000). However, as time passes it may be safe to begin assuming that everyone's CPU supports these instructions. Since this text assumes a minimum Pentium CPU, it will not discuss these two instructions any further.

11.2.7.1 The FCOM, FCOMP, and FCOMPP Instructions

The FCOM, FCOMP, and FCOMPP instructions compare ST0 to the specified operand and set the corresponding FPU condition code bits based on the result of the comparison. The legal forms for these instructions are

        fcom()

        fcomp()

        fcompp()

        fcom( sti )

        fcomp( sti )

        fcom( mem_32_64 )

        fcomp( mem_32_64 )

        fcom( real_constant )

        fcomp( real_constant )

With no operands, FCOM, FCOMP, and FCOMPP compare ST0 against ST1 and set the processor flags accordingly. In addition, FCOMP pops ST0 off the stack and FCOMPP pops both ST0 and ST1 off the stack.

With a single register operand, FCOM and FCOMP compare ST0 against the specified register. FCOMP also pops ST0 after the comparison.

With a 32 or 64 bit memory operand, the FCOM and FCOMP instructions convert the memory variable to an 80 bit extended precision value and then compare ST0 against this value, setting the condition code bits accordingly. FCOMP also pops ST0 after the comparison.

These instructions set C2 (which winds up in the parity flag) if the two operands are not comparable (e.g., NaN). If it is possible for an illegal floating point value to wind up in a comparison, you should check the parity flag for an error before checking the desired condition.

These instructions set the stack fault bit if there aren't two items on the top of the register stack. They set the denormalized exception bit if either or both operands are denormalized. They set the invalid operation flag if either or both operands are quite NaNs. These instructions always clear the C1 condition code.

Note: the instructions that have real constants as operands aren't true FPU instructions. These are extensions provided by HLA. When HLA encounters such an instruction, it creates a real64 read-only variable in the constants segment and initializes this variable with the specified constant. Then HLA translates the instruction to one that specifies a real64 memory operand. Note that because of the precision differences (64 bits vs. 80 bits), if you use a constant operand in a floating point instruction you may not get results that are as precise as you would expect.

Example of a floating point comparison:

        fcompp();

        fstsw( ax );

        sahf();

        setb( al );   // AL = true if ST1 < ST0.

            .

            .

            .

Note that you cannot compare floating point values in an HLA run-time boolean expression (e.g., within an IF statement).

11.2.7.2 The FTST Instruction

The FTST instruction compares the value in ST0 against 0.0. It behaves just like the FCOM instruction would if ST1 contained 0.0. Note that this instruction does not differentiate -0.0 from +0.0. If the value in ST0 is either of these values, ftst will set C3 to denote equality. Note that this instruction does not pop st(0) off the stack. Example:

        ftst();

        fstsw( ax );

        sahf();

        sete( al );					// Set AL to 1 if TOS = 0.0

11.2.8 Constant Instructions

The FPU provides several instructions that let you load commonly used constants onto the FPU's register stack. These instructions set the stack fault, invalid operation, and C1 flags if a stack overflow occurs; they do not otherwise affect the FPU flags. The specific instructions in this category include:

        fldz()			;Pushes +0.0.

        fld1()			;Pushes +1.0.

        fldpi()			;Pushes p.

        fldl2t()			;Pushes log2(10).

        fldl2e()			;Pushes log2(e).

        fldlg2()			;Pushes log10(2).

        fldln2()			;Pushes ln(2).

11.2.9 Transcendental Instructions

The FPU provides eight transcendental (log and trigonometric) instructions to compute sin, cos, partial tangent, partial arctangent, 2x-1, y * log2(x), and y * log2(x+1). Using various algebraic identities, it is easy to compute most of the other common transcendental functions using these instructions.

11.2.9.1 The F2XM1 Instruction

F2XM1 computes 2st0-1. The value in ST0 must be in the range -1.0 ð ST0 ð +1.0. If ST0 is out of range F2XM1 generates an undefined result but raises no exceptions. The computed value replaces the value in ST0. Example:

; Compute 10x using the identity: 10x = 2x*lg(10) (lg = log2).

        fld( x );

        fldl2t();

        fmul();

        f2xm1();

        fld1();

        fadd();

Note that F2XM1 computes 2x-1, which is why the code above adds 1.0 to the result at the end of the computation.

11.2.9.2 The FSIN, FCOS, and FSINCOS Instructions

These instructions pop the value off the top of the register stack and compute the sine, cosine, or both, and push the result(s) back onto the stack. The FSINCOS pushes the sine followed by the cosine of the original operand, hence it leaves cos(ST0) in ST0 and sin(ST0) in ST1.

These instructions assume ST0 specifies an angle in radians and this angle must be in the range -263 < ST0 < +263. If the original operand is out of range, these instructions set the C2 flag and leave ST0 unchanged. You can use the FPREM1 instruction, with a divisor of 2p, to reduce the operand to a reasonable range.

These instructions set the stack fault/C1, precision, underflow, denormalized, and invalid operation flags according to the result of the computation.

11.2.9.3 The FPTAN Instruction

FPTAN computes the tangent of ST0 and pushes this value and then it pushes 1.0 onto the stack. Like the FSIN and FCOS instructions, the value of ST0 is assumed to be in radians and must be in the range -263<ST0<+263. If the value is outside this range, FPTAN sets C2 to indicate that the conversion did not take place. As with the FSIN, FCOS, and FSINCOS instructions, you can use the FPREM1 instruction to reduce this operand to a reasonable range using a divisor of 2p.

If the argument is invalid (i.e., zero or p radians, which causes a division by zero) the result is undefined and this instruction raises no exceptions. FPTAN will set the stack fault, precision, underflow, denormal, invalid operation, C2, and C1 bits as required by the operation.

11.2.9.4 The FPATAN Instruction

This instruction expects two values on the top of stack. It pops them and computes the following:

ST0 = tan-1( ST1 / ST0 )

The resulting value is the arctangent of the ratio on the stack expressed in radians. If you have a value you wish to compute the tangent of, use FLD1 to create the appropriate ratio and then execute the FPATAN instruction.

This instruction affects the stack fault/C1, precision, underflow, denormal, and invalid operation bits if an problem occurs during the computation. It sets the C1 condition code bit if it has to round the result.

11.2.9.5 The FYL2X Instruction

This instruction expects two operands on the FPU stack: y is found in ST1 and x is found in ST0. This function computes:

ST0 = ST1 * log2( ST0 )

This instruction has no operands (to the instruction itself). The instruction uses the following syntax:

        fyl2x();

Note that this instruction computes the base two logarithm. Of course, it is a trivial matter to compute the log of any other base by multiplying by the appropriate constant.

11.2.9.6 The FYL2XP1 Instruction

This instruction expects two operands on the FPU stack: y is found in ST1 and x is found in ST0. This function computes:

ST0 = ST1 * log2( ST0 + 1.0 )

The syntax for this instruction is

        fyl2xp1();

Otherwise, the instruction is identical to FYL2X.

11.2.10 Miscellaneous instructions

The FPU includes several additional instructions which control the FPU, synchronize operations, and let you test or set various status bits. These instructions include FINIT/FNINIT, FLDCW, FSTCW, FCLEX/FNCLEX, and FSTSW.

11.2.10.1 The FINIT and FNINIT Instructions

The FINIT instruction initializes the FPU for proper operation. Your applications should execute this instruction before executing any other FPU instructions. This instruction initializes the control register to 37Fh (see "The FPU Control Register" on page 612), the status register to zero (see "The FPU Status Register" on page 615) and the tag word to 0FFFFh. The other registers are unaffected. Examples:

    FINIT();

    FNINIT();

The difference between FINIT and FNINIT is that FINIT first checks for any pending floating point exceptions before initializing the FPU; FNINIT does not.

11.2.10.2 The FLDCW and FSTCW Instructions

The FLDCW and FSTCW instructions require a single 16 bit memory operand:

        fldcw( mem_16 );

        fstcw( mem_16 );

These two instructions load the control register (see "The FPU Control Register" on page 612) from a memory location (FLDCW) or store the control word to a 16 bit memory location (FSTCW).

When using the FLDCW instruction to turn on one of the exceptions, if the corresponding exception flag is set when you enable that exception, the FPU will generate an immediate interrupt before the CPU executes the next instruction. Therefore, you should use the FCLEX instruction to clear any pending interrupts before changing the FPU exception enable bits.

11.2.10.3 The FCLEX and FNCLEX Instructions

The FCLEX and FNCLEX instructions clear all exception bits the stack fault bit, and the busy flag in the FPU status register (see "The FPU Status Register" on page 615). Examples:

    fclex();

    fnclex();

The difference between these instructions is the same as FINIT and FNINIT.

11.2.10.4 The FSTSW and FNSTSW Instructions

        fstsw( ax )

        fnstsw( ax )

        fstsw( mem_16 )

        fnstsw( mem_16 )

These instructions store the FPU status register (see "The FPU Status Register" on page 615) into a 16 bit memory location or the AX register. These instructions are unusual in the sense that they can copy an FPU value into one of the 80x86 general purpose registers (specifically, AX). Of course, the whole purpose behind allowing the transfer of the status register into AX is to allow the CPU to easily test the condition code register with the SAHF instruction. The difference between FSTSW and FNSTSW is the same as for FCLEX and FNCLEX.

11.2.11 Integer Operations

The 80x87 FPUs provide special instructions that combine integer to extended precision conversion along with various arithmetic and comparison operations. These instructions are the following:

        fiadd( int_16_32 )

        fisub( int_16_32 )

        fisubr( int_16_32 )

        fimul( int_16_32 )

        fidiv( int_16_32 )

        fidivr( int_16_32 )

        ficom( int_16_32 )

        ficomp( int_16_32 )

These instructions convert their 16 or 32 bit integer operands to an 80 bit extended precision floating point value and then use this value as the source operand for the specified operation. These instructions use ST0 as the destination operand.

1Storing a denormalized value into a 32 or 64 bit memory variable will always set the underflow exception bit.


11.3 Converting Floating Point Expressions to Assembly Language

Because the FPU register organization is different than the 80x86 integer register set, translating arithmetic expressions involving floating point operands is a little different than the techniques for translating integer expressions. Therefore, it makes sense to spend some time discussing how to manually translate floating point expressions into assembly language.

In one respect, it's actually easier to translate floating point expressions into assembly language. The stack architecture of the Intel FPU eases the translation of arithmetic expressions into assembly language. If you've ever used a Hewlett-Packard calculator, you'll be right at home on the FPU because, like the HP calculator, the FPU uses reverse polish notation, or RPN, for arithmetic calculations. Once you get used to using RPN, it's actually a bit more convenient for translating expressions because you don't have to worry about allocating temporary variables - they always wind up on the FPU stack.

RPN, as opposed to standard infix notation, places the operands before the operator. The following examples give some simple examples of infix notation and the corresponding RPN notation:

        infix notation						RPN notation

            5 + 6						5  6  +

            7 - 2						7  2  -

            x * y						x  y  *

            a / b						a  b  /

An RPN expression like "5 6 +" says "push five onto the stack, push six onto the stack, then pop the value off the top of stack (six) and add it to the new top of stack." Sound familiar? This is exactly what the FLD and FADD instructions do. In fact, you can calculate this using the following code:

        fld( 5.0 );

        fld( 6.0 );

        fadd();				// 11.0 is now on the top of the FPU stack.

As you can see, RPN is a convenient notation because it's very easy to translate this code into FPU instructions.

One advantage to RPN (or postfix notation) is that it doesn't require any parentheses. The following examples demonstrate some slightly more complex infix to postfix conversions:

        infix notation								postfix notation

        (x + y) * 2								x  y + 2 *

        x * 2 - (a + b)								x 2 * a b + -

        (a + b) * (c + d)								a b + c d + *

The postfix expression "x y + 2 *" says "push x, then push y; next, add those values on the stack (producing X+Y on the stack). Next, push 2 and then multiply the two values (two and X+Y) on the stack to produce two times the quantity X+Y." Once again, we can translate these postfix expressions directly into assembly language. The following code demonstrates the conversion for each of the above expressions:

//		x y + 2 *

        fld( x );

        fld( y );

        fadd();

        fld( 2.0 );

        fmul();

//		x 2 * a b + -

        fld( x );

        fld( 2.0 );

        fmul();

        fld( a );

        fld( b );

        fadd();

        fsub();

//		a b + c d + *

        fld( a );

        fld( b );

        fadd();

        fld( c );

        fld( d );

        fadd();

        fmul();

11.3.1 Converting Arithmetic Expressions to Postfix Notation

Since the process of translating arithmetic expressions into assembly language involves postfix (RPN) notation, converting arithmetic expressions into postfix notation seems like the right place to start. This section will concentrate on that conversion.

For simple expressions, those involving two operands and a single expression, the translation is trivial. Simply move the operator from the infix position to the postfix position (that is, move the operator from inbetween the operands to after the second operand). For example, "5 + 6" becomes "5 6 +". Other than separating your operands so you don't confuse them (i.e., is it "5" and "6" or "56"?) there isn't much to converting simple infix expressions into postfix notation.

For complex expressions, the idea is to convert the simple sub-expressions into postfix notation and then treat each converted subexpression as a single operand in the remaining expression. The following discussion will surround completed conversions in square brackets so it is easy to see which text needs to be treated as a single operand in the conversion.

As for integer expression conversion, the best place to start is in the inner-most parenthetical sub-expression and then work your way outward considering precedence, associativity, and other parenthetical sub-expressions. As a concrete working example, consider the following expression:

x = ((y-z)*a) - ( a + b * c )/3.14159

A possible first translation is to convert the subexpression "(y-z)" into postfix notation. This is accomplished as follows:

x = ([y z -] * a) - ( a + b * c )/3.14159

Square brackets surround the converted postfix code just to separate it from the infix code. These exist only to make the partial translations more readable. Remember, for the purposes of conversion we will treat the text inside the square brackets as a single operand. Therefore, you would treat "[y z -]" as though it were a single variable name or constant.

The next step is to translate the subexpression "([y z -] * a )" into postfix form. This yields the following:

x = [y z - a *] - ( a + b * c )/3.14159

Next, we work on the parenthetical expression "( a + b * c )." Since multiplication has higher precedence than addition, we convert "b*c" first:

x = [y z - a *] - ( a + [b c *])/3.14159

After converting "b*c" we finish the parenthetical expression:

x = [y z - a *] - [a b c * +]/3.14159

This leaves only two infix operators: subtraction and division. Since division has the higher precedence, we'll convert that first:

x = [y z - a *] - [a b c * + 3.14159 /]

Finally, we convert the entire expression into postfix notation by dealing with the last infix operation, subtraction:

x = [y z - a *] [a b c * + 3.14159 /] -

Removing the square brackets to give us true postfix notation yields the following RPN expression:

x = y z - a * a b c * + 3.14159 / -

Here is another example of an infix to postfix conversion:

a = (x * y - z + t)/2.0

Step 1: Work inside the parentheses. Since multiplication has the highest precedence, convert that first:

a = ( [x y *] - z + t)/2.0

Step 2: Still working inside the parentheses, we note that addition and subtraction have the same precedence, so we rely upon associativity to determine what to do next. These operators are left associative, so we must translate the expressions in a left to right order. This means translate the subtraction operator first:

a = ( [x y * z -] + t)/2.0

Step 3: Now translate the addition operator inside the parentheses. Since this finishes the parenthetical operators, we can drop the parentheses:

a = [x y * z - t +]/2.0

Step 4: Translate the final infix operator (division). This yields the following:

a = [x y * z - t + 2.0 / ]

Step 5: Drop the square brackets and we're done:

a = x y * z - t + 2.0 /

11.3.2 Converting Postfix Notation to Assembly Language

Once you've translated an arithmetic expression into postfix notation, finishing the conversion to assembly language is especially easy. All you have to do is issue an FLD instruction whenever you encounter an operand and issue an appropriate arithmetic instruction when you encounter an operator. This section will use the completed examples from the previous section to demonstrate how little there is to this process.

x = y z - a * a b c * + 3.14159 / -
  • Step 1: Convert y to FLD(y);
  • Step 2: Convert z to FLD(z);
  • Step 3: Convert "-" to FSUB();
  • Step 4: Convert a to FLD(a);
  • Step 5: Convert "*" to FMUL();
  • Steps 6-n: Continuing in a left-to-right fashion, generate the following code for the expression:

        fld( y );

        fld( z );

        fsub();

        fld( a );

        fmul();

        fld( a );

        fld( b );

        fld( c );

        fmul();

        fadd();

        fldpi();				// Loads pi (3.14159)

        fdiv();

        fsub();

        fstp( x );				// Store result away into x.

Here's the translation for the second example in the previous section:

a = x y * z - t + 2.0 /

        fld( x );

        fld( y );

        fmul();

        fld( z );

        fsub();

        fld( t );

        fadd();

        fld( 2.0 );

        fdiv();

        fstp( a );				// Store result away into a.

As you can see, the translation is fairly trivial once you've converted the infix notation to postfix notation. Also note that, unlike integer expression conversion, you don't need any explicit temporaries. It turns out that the FPU stack provides the temporaries for you1. For these reasons, conversion of floating point expressions into assembly language is actually easier than converting integer expressions.

11.3.3 Mixed Integer and Floating Point Arithmetic

Throughout the previous sections on floating point arithmetic an unstated assumption was made: all operands in the expressions were floating point variables or constants. In the real world, you'll often need to mix integer and floating point operands in the same expression. Thanks to the FILD instruction, this is a trivial exercise.

Of course, the FPU cannot operate on integer operands. That is, you cannot push an integer operand (in integer format) onto the FPU stack and add this integer to a floating point value that is also on the stack. Instead, you use the FILD instruction to load and translate the integer value; this winds up pushing the floating point equivalent of the integer onto the FPU stack. Once the value is converted to a floating point number, you continue the calculation using the standard real arithmetic operations.

Embedding a floating point value in an integer expression is a little more problematic. In this case you must convert the floating point value to an integer value for use in the integer expression. To do this, you must use the FIST instruction. FIST converts the floating point value on the top of stack into an integer value according to the setting of the rounding bits in the floating point control register (See "The FPU Control Register" on page 612). By default, FIST will round the floating point value to the nearest integer before storing the value into memory; if you want to use the more common fraction truncation mode, you will need to change the value in the FPU control register. You compute the integer expression using the techniques from the previous chapter (see "Complex Expressions" on page 600). The FPU participates only to the point of converting the floating point value to an integer.

static

    intVal1 : uns32 := 1;

    intVal2 : uns32 := 2;

    realVal : real64;

        .

        .

        .

        fild( intVal1 );

        fild( intVal2 );

        fadd();

        fstp( realVal );

        stdout.put( "realVal = ", realVal, nl );

Assuming, of course, that your calculations aren't so complex that you exceed the eight-element limitation of the FPU stack.


11.4 HLA Standard Library Support for Floating Point Arithmetic

The HLA Standard Library provides several routines that support the use of real number on the FPU. In Volume One you saw, with one exception, how the standard input and output routines operate. This section will not repeat that discussion, see "HLA Support for Floating Point Values" on page 93 for more details. One input function that Volume One only mentioned briefly was the stdin.getf function. This section will elaborate on that function. The HLA Standard Library also includes the "math.hhf" module that provides several mathematical functions that the FPU doesn't directly support. This section will discuss those functions, as well.

11.4.1 The stdin.getf and fileio.getf Functions

The stdin.getf function reads a floating point value from the standard input device. It leaves the converted value in ST0 (i.e., on the top of the floating point stack). The only reason Chapter Two did not discuss this function thoroughly was because you hadn't seen the FPU and FPU registers at that point.

The stdin.getf function accepts the same inputs that "stdin.get( fp_variable );" would except. The only difference between the two is where these functions store the floating point value.

As you'd probably surmise, there is a corresponding fileio.getf function as well. This function reads the floating point value from the file whose file handle is the single parameter in this function call. It, too, leaves the converted result on the top of the FPU stack.

11.4.2 Trigonometric Functions in the HLA Math Library

The FPU provides a small handful of trigonometric functions. It does not, however, support the full range of trig functions. The HLA MATH.HHF module fills in most of the missing functions. The trigonometric functions that HLA provides include

  • ACOS( arc cosine)
  • ACOT (arc cotangent)
  • ACSC( arc cosecant )
  • ASEC (arc secant)
  • ASIN (arc sin)
  • COT (cotangent)
  • CSC (cosecant)
  • SEC (secant)

The HLA Standard Library actually provides five different routines you can call for each of these functions. For example, the prototypes for the first four COT (cotangent) routines are:

        procedure cot32( r32: real32 );

        procedure cot64( r64: real64 );

        procedure cot80( r80: real80 );

        procedure _cot();

The first three routines push their parameter onto the FPU stack and compute the cotangent of the result. The fourth routine above (_cot) computes the cotangent of the value in ST0.

The fifth routine is actually an overloaded procedure that calls one of the four routines above depending on the parameter. This call uses the following syntax:

        cot();				// Calls _cot() to compute cot(ST0).

        cot( r32 );				// Calls cot32 to compute the cotangent of r32.

        cot( r64 );				// Calls cot64 to compute the cotangent of r64.

        cot( r80 );				// Calls cot80 to compute the cotangent of r80.

Using this fifth form is probably preferable since it is much more convenient. Note that there is no efficiency loss when you used cot rather than one of the other cotangent routines. HLA actually translates this statement directly into one of the other calls.

The HLA trigonometric functions that require an angle as a parameter expect that angle to be expressed in radians, not degrees. Keep in mind that some of these functions produce undefined results for certain input values. If you've enabled exceptions on the FPU, these functions will raise the appropriate FPU exception if an error occurs.

11.4.3 Exponential and Logarithmic Functions in the HLA Math Library

The HLA MATH.HHF module provides several exponential and logarithmic functions in addition to the trigonometric functions. Like the trig functions, the exponential and logarithmic functions provide five different interfaces to each function depending on the size and location of the parameter. The functions that MATH.HHF supports are

  • TwoToX ( raise 2.0 to the specified power).
  • TenToX (raise 10.0 to the specified power).
  • exp (raises e [2.718281828...] to the specified power).
  • YtoX (raises first parameter to the power specified by the second parameter).
  • log (computes base 10 logarithm).
  • ln (computes base e logarithm).

Except for the YtoX function, all these functions provide the same sort of interface as the cot function mentioned in the previous section. For example, the exp function provides the following prototypes:

        procedure exp32( r32: real32 );

        procedure exp64( r64: real64 );

        procedure exp80( r80: real80 );

        procedure _exp();

The exp function, by itself, automatically calls one of the above functions depending on the parameter type (and presence of a parameter):

        exp();				// Calls _exp() to compute exp(ST0).

        exp( r32 );				// Calls exp32 to compute the e**r32.

        exp( r64 );				// Calls exp64 to compute the e**r64.

        exp( r80 );				// Calls exp80 to compute the e**r80.

The lone exception to the above is the YtoX function. YtoX has its own rules because it has two parameters rather than one (Y and X). YtoX provides the following function prototypes:

        procedure YtoX32( y: real32; x: real32 );

        procedure YtoX64( y: real64; x: real64 );

        procedure YtoX80( y: real80; x: real80 );

        procedure _YtoX();

The _YtoX function computes ST1**ST0 (i.e., ST1 raised to the ST0 power).

The YtoX function provides the following interface:

        YtoX();						// Calls _YtoX() to compute exp(ST0).

        YtoX( y32, x32);						// Calls YtoX32 to compute y32**x32.

        YtoX( y64, x64 );						// Calls YtoX64 to compute y64**x64.

        YtoX( y80, x80 );						// Calls YtoX80 to compute y80**x80.


11.5 Sample Program

This chapter presents a simple "Reverse Polish Notation" calculator that demonstrates the use of the 80x86 FPU. In addition to demonstrating the use of the FPU, this sample program also introduces a few new routines from the HLA Standard Library, specifically the arg.c, arg.v, and conv.strToFlt routines.

The HLA Standard Library conversions module ("conv.hhf") contains dozens of procedures that translate data between various formats. A large percentage of these routines convert data between some internal numeric form and a corresponding string format. The conv.strToFlt routine, as its name suggests, converts string data to a floating point value. The prototype for this function is the following:

procedure conv.strToFlt( s:string; index:dword );

The first parameter is the string containing the textual representation of the floating point value. The second parameter contains an index into the string where the floating point text actually begins (usually the index parameter is zero if the string contains nothing but the floating point text). The conv.strToFlt procedure will attempt to convert the specified string to a floating point number. If there is a problem, this function will raise an appropriate exception (e.g., ex.ConversionError). In fact, the HLA stdin routines that read floating point values from the user actually read string data and call this same procedure to convert that data to a floating point value; hence, you should protect this procedure call with a TRY..ENDTRY statement exactly the same way you protect a call to stdin.get or stdin.getf. If this routine is successful, it leaves the converted floating point value on the top of the FPU stack.

The HLA Standard Library contains numerous other procedures that convert textual data to the corresponding internal format. Examples include conv.strToi8, conv.strToi16, conv.strToi32, conv.strToi64, and more. See the HLA Standard Library documentation for more details.

This sample program also uses the arg.c and arg.v routines from the HLA Standard Library's command line arguments module ("args.hhf"). These functions provide access to the text following the program name when you run a program from the command line prompt. This calculator program, for example, expects the user to supply the desired calculation on the command line immediately after the program name. For example to add the two values 18 and 22 together, you'd specify the following command line:

    rpncalc 18  22  +

The text "18 22 +" is an example of three command line parameters. Programs often use command line parameters to communicate filenames and other data to the application. For example, the HLA compiler uses command line parameters to pass the names of the source files to the compiler. The rpncalc program uses the command line to pass the RPN expression to the calculator.

The arg.c function ("argument count") returns the number of parameters on the command line. This function returns the count in the EAX register. It does not have any parameters. In general, you can probably assume that the maximum possible number of command line arguments is between 64 and 128. Note that the operating system counts the program's name on the command line in this argument count. Therefore, this value will always be one or greater. If arg.c returns one, then there are no extra command line parameters; the single item is the program's name.

A program that expects at least one command line parameter should always call arg.c and verify that it returns the value two or greater. Programs that process command line parameters typically execute a loop of some sort that executes the number of times specified by arg.c's return value. Of course, when you use arg.c's return value for this purpose, don't forget to subtract one from the return result to account for the program's name (unless you are treating the program name as one more parameter).

The arg.v function returns a string containing one of the program's command line arguments. This function has the following prototype:

procedure arg.v( index:uns32 );

The index parameter specifies which command line parameter you wish to retrieve. The value zero returns a string containing the program's name. The value one returns the first command line parameter following the program's name. The value two returns a string containing the second command line parameter following the program's name. Etc. The value you provide as a parameter to this function must fall in the range 0..arg.c()-1 or arg.v will raise an exception.

The arg.v procedure allocates storage for the string it returns on the heap by calling stralloc. It returns a pointer to the allocated string in the EAX register. Don't forget to call strfree to return the storage to the system after you are done processing the command line parameter.

Well, without further ado, here is the RPN calculator program that uses the aforementioned functions.

// This sample program demonstrates how to use

// the FPU to create a simple RPN calculator.

// This program reads a string from the user

// and "parses" that string to figure out the

// calculation the user is requesting.  This

// program assumes that any item beginning

// with a numeric digit is a numeric operand

// to push onto the FPU stack and all other

// items are operators.

//

//  Example of typical user input:

//

//      calc 123.45 67.89 +

//

//  The program responds by printing

//

//      Result = 1.91340000000000000e+2

//

//

//  Current operators supported:

//

//      + - * /

//

//  Current functions supported:

//

//      sin sqrt

program RPNcalculator;

#include( "stdlib.hhf" )

static

    argc:       uns32;

    curOperand: string;

    ItemsOnStk: uns32;

    realRslt:   real80;

    // The following function converts an

    // angle (in ST0) from degrees to radians.

    // It leaves the result in ST0.

    procedure DegreesToRadians; @nodisplay;

    begin DegreesToRadians;

        fld( 2.0 ); // Radians = degrees*2*pi/360.0

        fmul();

        fldpi();

        fmul();

        fld( 360.0 );

        fdiv();

    end DegreesToRadians;

begin RPNcalculator;

    // Initialize the FPU.

    finit();

    // Okay, extract the items from the Windows

    // CMD.EXE command line and process them.

    arg.c();

    if( eax <= 1 ) then

        stdout.put( "Usage: `rpnCalc <rpn expression>'" nl );

        exit RPNcalculator;

    endif;

    // ECX holds the index of the current operand.

    // ItemsOnStk keeps track of the number of numeric operands

    //  pushed onto the FPU stack so we can ensure that each

    //  operation has the appropriate number of operands.

    mov( eax, argc );

    mov( 1, ecx );

    mov( 0, ItemsOnStk );

    // The following loop repeats once for each item on the

    // command line:

    while( ecx < argc ) do

        // Get the string associated with the current item:

        arg.v( ecx );   // Note that this malloc's storage!

        mov( eax, curOperand );

        // If the operand begins with a numeric digit, assume

        // that it's a floating point number.

        if( (type char [eax]) in `0'..'9' ) then

            try

                // Convert this string representation of a numeric

                // value to the equivalent real value.  Leave the

                // result on the top of the FPU stack.  Also, bump

                // ItemsOnStk up by one since we're pushing a new

                // item onto the FPU stack.

                conv.strToFlt( curOperand, 0 );

                inc( ItemsOnStk );

              exception( ex.ConversionError )

                stdout.put("Illegal floating point constant" nl );

                exit RPNcalculator;

              anyexception

                stdout.put

                (

                    "Exception ",

                    (type uns32 eax ),

                    " while converting real constant"

                    nl

                );

                exit RPNcalculator;

            endtry;

        // Handle the addition operation here.

        elseif( str.eq( curOperand, "+" )) then

            // The addition operation requires two

            // operands on the stack.  Ensure we have

            // two operands before proceeding.

            if( ItemsOnStk >= 2 ) then

                fadd();

                dec( ItemsOnStk );  // fadd() removes one operand.

            else

                stdout.put( "`+' operation requires two operands." nl );

                exit RPNcalculator;

            endif;

        // Handle the subtraction operation here.  See the comments

        // for FADD for more details.

        elseif( str.eq( curOperand, "-" )) then

            if( ItemsOnStk >= 2 ) then

                fsub();

                dec( ItemsOnStk );

            else

                stdout.put( "`-' operation requires two operands." nl );

                exit RPNcalculator;

            endif;

        // Handle the multiplication operation here.  See the comments

        // for FADD for more details.

        elseif( str.eq( curOperand, "*" )) then

            if( ItemsOnStk >= 2 ) then

                fmul();

                dec( ItemsOnStk );

            else

                stdout.put( "`*' operation requires two operands." nl );

                exit RPNcalculator;

            endif;

        // Handle the division operation here.  See the comments

        // for FADD for more details.

        elseif( str.eq( curOperand, "/" )) then

            if( ItemsOnStk >= 2 ) then

                fdiv();

                dec( ItemsOnStk );

            else

                stdout.put( "`/' operation requires two operands." nl );

                exit RPNcalculator;

            endif;

        // Provide a square root operation here.

        elseif( str.eq( curOperand, "sqrt" )) then

            // Sqrt is a monadic (unary) function.  Therefore

            // we only require a single item on the stack.

            if( ItemsOnStk >= 1 ) then

                fsqrt();

            else

                stdout.put

                (

                    "SQRT function requires at least one operand."

                    nl

                );

                exit RPNcalculator;

            endif;

        // Provide the SINE function here.  See SQRT comments for details.

        elseif( str.eq( curOperand, "sin" )) then

            if( ItemsOnStk >= 1 ) then

                DegreesToRadians();

                fsin();

            else

                stdout.put( "SIN function requires at least one operand." nl );

                exit RPNcalculator;

            endif;

        else

            stdout.put( "`", curOperand, "` is an unknown operation." nl );

            exit RPNcalculator;

        endif;

        // Free the storage associated with the current item.

        strfree( curOperand );

        // Move on to the next item on the command line:

        inc( ecx );

    endwhile;

    if( ItemsOnStk = 1 ) then

        fstp( realRslt );

        stdout.put( "Result = ", realRslt, nl );

    else

        stdout.put( "Syntax error in expression. ItemsOnStk=", ItemsOnStk, nl );

    endif;

end RPNcalculator;

Program 11.1	 A Floating Point Calculator Program

11.6 Putting It All Together

Between the FPU and the HLA Standard Library, floating point arithmetic is actually quite simple. In this chapter you learned about the floating point instruction set and you learned how to convert arithmetic expressions involving real arithmetic into a sequence of floating point instructions. This chapter also presented several transcendental functions that the HLA Standard Library provides. Armed with the information from this chapter, you should be able to deal with floating point expressions just as easily as integer expressions.


Chapter Four Advanced Arithmetic

4.1 Chapter Overview

This chapter deals with those arithmetic operations for which assembly language is especially well suited and high level languages are, in general, poorly suited. It covers three main topics: extended precision arithmetic, arithmetic on operands who sizes are different, and decimal arithmetic.

By far, the most extensive subject this chapter covers is multi-precision arithmetic. By the conclusion of this chapter you will know how to apply arithmetic and logical operations to integer operands of any size. If you need to work with integer values outside the range ±2 billion (or with unsigned values beyond four billion), no sweat; this chapter will show you how to get the job done.

Operands whose sizes are not the same also present some special problems in arithmetic operations. For example, you may want to add a 128-bit unsigned integer to a 256-bit signed integer value. This chapter discusses how to convert these two operands to a compatible format so the operation may proceed.

Finally, this chapter discusses decimal arithmetic using the BCD (binary coded decimal) features of the 80x86 instruction set and the FPU. This lets you use decimal arithmetic in those few applications that absolutely require base 10 operations (rather than binary).

4.2 Multiprecision Operations

One big advantage of assembly language over high level languages is that assembly language does not limit the size of integer operations. For example, the C programming language defines a maximum of three different integer sizes: short int, int, and long int1. On the PC, these are often 16 and 32 bit integers. Although the 80x86 machine instructions limit you to processing eight, sixteen, or thirty-two bit integers with a single instruction, you can always use more than one instruction to process integers of any size you desire. If you want to add 256 bit integer values together, no problem, it's relatively easy to accomplish this in assembly language. The following sections describe how extended various arithmetic and logical operations from 16 or 32 bits to as many bits as you please.

4.2.1 Multiprecision Addition Operations

The 80x86 ADD instruction adds two eight, sixteen, or thirty-two bit numbers2. After the execution of the add instruction, the 80x86 carry flag is set if there is an overflow out of the H.O. bit of the sum. You can use this information to do multiprecision addition operations. Consider the way you manually perform a multidigit (multiprecision) addition operation:

    Step 1: Add the least significant digits together:

             289		 			 289

            +456	  produces				+456

            ----					----

                                   5 with carry 1.

    Step 2: Add the next significant digits plus the carry:

              1 (previous carry)

             289 					 289

            +456		produces			+456

            ---- 					----

               5					  45 with carry 1.

    Step 3: Add the most significant digits plus the carry:

                                 1 (previous carry)

             289					 289

            +456		produces			+456

            ----					----

              45					 745

The 80x86 handles extended precision arithmetic in an identical fashion, except instead of adding the numbers a digit at a time, it adds them together a byte, word, or dword at a time. Consider the three double word (96 bit) addition operation in Figure 4.1.

Figure 4.1 Adding Two 96-bit Objects Together

As you can see from this figure, the idea is to break up a larger operation into a sequence of smaller operations. Since the x86 processor family is capable of adding together, at most, 32 bits at a time, the operation must proceed in blocks of 32-bits or less. So the first step is to add the two L.O. double words together much as we would add the two L.O. digits of a decimal number together in the manual algorithm. There is nothing special about this operation, you can use the ADD instruction to achieve this.

The second step involves adding together the second pair of double words in the two 96-bit values. Note that in step two, the calculation must also add in the carry out of the previous addition (if any). If there was a carry out of the L.O. addition, the ADD instruction sets the carry flag to one; conversely, if there was no carry out of the L.O. addition, the earlier ADD instruction clears the carry flag. Therefore, in this second addition, we really need to compute the sum of the two double words plus the carry out of the first instruction. Fortunately, the x86 CPUs provide an instruction that does exactly this: the ADC (add with carry) instruction. The ADC instruction uses the same syntax as the ADD instruction and performs almost the same operation:

adc( source, dest );  // dest := dest + source + C

As you can see, the only difference between the ADD and ADC instruction is that the ADC instruction adds in the value of the carry flag along with the source and destination operands. It also sets the flags the same way the ADD instruction does (including setting the carry flag if there is an unsigned overflow). This is exactly what we need to add together the middle two double words of our 96-bit sum.

In step three of Figure 4.1, the algorithm adds together the H.O. double words of the 96-bit value. Once again, this addition operation also requires the addition of the carry out of the sum of the middle two double words; hence the ADC instruction is needed here, as well. To sum it up, the ADD instruction adds the L.O. double words together. The ADC (add with carry) instruction adds all other double word pairs together. At the end of the extended precision addition sequence, the carry flag indicates unsigned overflow (if set), a set overflow flag indicates signed overflow, and the sign flag indicates the sign of the result. The zero flag doesn't have any real meaning at the end of the extended precision addition (it simply means that the sum of the H.O. two double words is zero, this does not indicate that the whole result is zero).

For example, suppose that you have two 64-bit values you wish to add together, defined as follows:

static

    X: qword;

    Y: qword;

Suppose, also, that you want to store the sum in a third variable, Z, that is likewise defined with the qword type. The following x86 code will accomplish this task:

    mov( (type dword X), eax );          // Add together the L.O. 32 bits

    add( (type dword Y), eax );          // of the numbers and store the

    mov( eax, (type dword Z) );          // result into the L.O. dword of Z.

    mov( (type dword X[4]), eax );       // Add together (with carry) the

    adc( (type dword Y[4]), eax );       // H.O. 32 bits and store the result

    mov( eax, (type dword Z[4]) );       // into the H.O. dword of Z.

Remember, these variables are qword objects. Therefore the compiler will not accept an instruction of the form "mov( X, eax );" because this instruction would attempt to load a 64 bit value into a 32 bit register. This code uses the coercion operator to coerce symbols X, Y, and Z to 32 bits. The first three instructions add the L.O. double words of X and Y together and store the result at the L.O. double word of Z. The last three instructions add the H.O. double words of X and Y together, along with the carry out of the L.O. word, and store the result in the H.O. double word of Z. Remember, address expressions of the form "X[4]" access the H.O. double word of a 64 bit entity. This is due to the fact that the x86 address space addresses bytes and it takes four consecutive bytes to form a double word.

You can extend this to any number of bits by using the ADC instruction to add in the higher order words in the values. For example, to add together two 128 bit values, you could use code that looks something like the following:

type

    tBig: dword[4];  // Storage for four dwords is 128 bits.

static

    BigVal1: tBig;

    BigVal2: tBig;

    BigVal3: tBig;

     .

     .

     .

    mov( BigVal1[0], eax );   // Note there is no need for (type dword BigValx)

    add( BigVal2[0], eax );   // because the base type of BitValx is dword.

    mov( eax, BigVal3[0] );

    mov( BigVal1[4], eax );

    adc( BigVal2[4], eax );

    mov( eax, BigVal3[4] );

    mov( BigVal1[8], eax );

    adc( BigVal2[8], eax );

    mov( eax, BigVal3[8] );

    mov( BigVal1[12], eax );

    adc( BigVal2[12], eax );

    mov( eax, BigVal3[12] );

4.2.2 Multiprecision Subtraction Operations

Like addition, the 80x86 performs multi-byte subtraction the same way you would manually, except it subtracts whole bytes, words, or double words at a time rather than decimal digits. The mechanism is similar to that for the ADD operation, You use the SUB instruction on the L.O. byte/word/double word and the SBB (subtract with borrow) instruction on the high order values. The following example demonstrates a 64 bit subtraction using the 32 bit registers on the x86:

static

    Left:		qword;

    Right:		qword;

    Diff:		qword;

         .

         .

         .

    mov( (type dword Left), eax );

    sub( (type dword Right), eax );

    mov( eax, (type dword Diff) );

    mov( (type dword Left[4]), eax );

    sbb( (type dword Right[4]), eax );

    mov( (type dword Diff[4]), eax );

The following example demonstrates a 128-bit subtraction:

type

    tBig: dword[4];  // Storage for four dwords is 128 bits.

static

    BigVal1: tBig;

    BigVal2: tBig;

    BigVal3: tBig;

     .

     .

     .

    // Compute BigVal3 := BigVal1 - BigVal2

    mov( BigVal1[0], eax );   // Note there is no need for (type dword BigValx)

    sub( BigVal2[0], eax );   // because the base type of BitValx is dword.

    mov( eax, BigVal3[0] );

    mov( BigVal1[4], eax );

    sbb( BigVal2[4], eax );

    mov( eax, BigVal3[4] );

    mov( BigVal1[8], eax );

    sbb( BigVal2[8], eax );

    mov( eax, BigVal3[8] );

    mov( BigVal1[12], eax );

    sbb( BigVal2[12], eax );

    mov( eax, BigVal3[12] );

4.2.3 Extended Precision Comparisons

Unfortunately, there isn't a "compare with borrow" instruction that you can use to perform extended precision comparisons. Since the CMP and SUB instructions perform the same operation, at least as far as the flags are concerned, you'd probably guess that you could use the SBB instruction to synthesize an extended precision comparison; however, you'd only be partly right. There is, however, a better way.

Consider the two unsigned values $2157 and $1293. The L.O. bytes of these two values do not affect the outcome of the comparison. Simply comparing $21 with $12 tells us that the first value is greater than the second. In fact, the only time you ever need to look at both bytes of these values is if the H.O. bytes are equal. In all other cases comparing the H.O. bytes tells you everything you need to know about the values. Of course, this is true for any number of bytes, not just two. The following code compares two unsigned 64 bit integers:

// This sequence transfers control to location "IsGreater" if

// QwordValue > QwordValue2. It transfers control to "IsLess" if

// QwordValue < QwordValue2. It falls though to the instruction

// following this sequence if QwordValue = QwordValue2. To test for

// inequality, change the "IsGreater" and "IsLess" operands to "NotEqual"

// in this code.

        mov( (type dword QWordValue[4]), eax );  // Get H.O. dword

        cmp( eax, (type dword QWordValue2[4]));

        jg IsGreater;

        jl IsLess;

        mov( (type dword QWordValue[0]), eax );  // If H.O. dwords were equal,

        cmp( eax, (type dword QWordValue2[0]));  // then we must compare the

        ja IsGreater;                            // L.O. dwords.

        jb IsLess;

// Fall through to this point if the two values were equal.

To compare signed values, simply use the JG and JL instructions in place of JA and JB for the H.O. words (only). You must continue to use unsigned comparisons for all but the H.O. double words you're comparing.

You can easily synthesize any possible comparison from the sequence above, the following examples show how to do this. These examples demonstrate signed comparisons, substitute JA, JAE, JB, and JBE for JG, JGE, JL, and JLE (respectively) for the H.O. comparisons if you want unsigned comparisons.

static

    QW1: qword;

    QW2: qword;

const

    QW1d: text := "(type dword QW1)";

    QW2d: text := "(type dword QW2)";

// 64 bit test to see if QW1 < QW2 (signed).

// Control transfers to "IsLess" label if QW1 < QW2. Control falls

// through to the next statement (at "NotLess") if this is not true.

    mov( QW1d[4], eax );   // Get H.O. dword

    cmp( eax, QW2d[4] );

    jg NotLess;            // Substitute ja here for unsigned comparison.

    jl IsLess;             // Substitute jb here for unsigned comparison.

    mov( QW1d[0], eax );   // Fall through to here if the H.O. dwords are equal.

    cmp( eax, QW2d[0] );

    jb IsLess;

NotLess:

// 64 bit test to see if QW1 <= QW2 (signed).  Jumps to "IsLessEq" if the

// condition is true.

    mov( QW1d[4], eax );   // Get H.O. dword

    cmp( eax, QW2d[4] );

    jg NotLessEQ;          // Substitute ja here for unsigned comparison.

    jl IsLessEQ;           // Substitute jb here for unsigned comparison.

    mov( QW1d[0], eax );   // Fall through to here if the H.O. dwords are equal.

    cmp( eax, QW2d[0] );

    jbe IsLessEQ;

NotLessEQ:

// 64 bit test to see if QW1 > QW2 (signed).  Jumps to "IsGtr" if this condition

// is true.

    mov( QW1d[4], eax );   // Get H.O. dword

    cmp( eax, QW2d[4] );

    jg IsGtr;              // Substitute ja here for unsigned comparison.

    jl NotGtr;             // Substitute jb here for unsigned comparison.

    mov( QW1d[0], eax );   // Fall through to here if the H.O. dwords are equal.

    cmp( eax, QW2d[0] );

    ja IsGtr;

NotGtr:

// 64 bit test to see if QW1 >= QW2 (signed).  Jumps to "IsGtrEQ" if this

// is the case.

    mov( QW1d[4], eax );   // Get H.O. dword

    cmp( eax, QW2d[4] );

    jg IsGtrEQ;            // Substitute ja here for unsigned comparison.

    jl NotGtrEQ;           // Substitute jb here for unsigned comparison.

    mov( QW1d[0], eax );   // Fall through to here if the H.O. dwords are equal.

    cmp( eax, QW2d[0] );

    jae IsGtrEQ;

NotGtrEQ:

// 64 bit test to see if QW1 = QW2 (signed or unsigned). This code branches

// to the label "IsEqual" if QW1 = QW2. It falls through to the next instruction

// if they are not equal.

    mov( QW1d[4], eax );   // Get H.O. dword

    cmp( eax, QW2d[4] );

    jne NotEqual;

    mov( QW1d[0], eax );   // Fall through to here if the H.O. dwords are equal.

    cmp( eax, QW2d[0] );

    je IsEqual;

NotEqual:

// 64 bit test to see if QW1 <> QW2 (signed or unsigned). This code branches

// to the label "NotEqual" if QW1 <> QW2. It falls through to the next

// instruction if they are equal.

    mov( QW1d[4], eax );   // Get H.O. dword

    cmp( eax, QW2d[4] );

    jne NotEqual;

    mov( QW1d[0], eax );   // Fall through to here if the H.O. dwords are equal.

    cmp( eax, QW2d[0] );

    jne NotEqual;

// Fall through to this point if they are equal.

You cannot directly use the HLA high level control structures if you need to perform an extended precision comparison. However, you may use the HLA hybrid control structures and bury the appropriate comparison into this statements. Doing so will probably make your code easier to read. For example, the following if..then..else..endif statement checks to see if QW1 > QW2 using a 64-bit extended precision signed comparison:

if

( #{

    mov( QW1d[4], eax );

    cmp( eax, QW2d[4] );

    jg true;

    mov( QW1d[0], eax );

    cmp( eax, QW2d[0] );

    jna false;

}# ) then

    << code to execute if QW1 > QW2 >>

else

    << code to execute if QW1 <= QW2 >>

endif;

If you need to compare objects that are larger than 64 bits, it is very easy to generalize the code above. Always start the comparison with the H.O. double words of the objects and work you way down towards the L.O. double words of the objects as long as the corresponding double words are equal The following example compares two 128-bit values to see if the first is less than or equal (unsigned) to the second:

type

    t128: dword[4];

static

    Big1: t128;

    Big2: t128;

     .

     .

     .

    if

    ( #{

        mov( Big1[12], eax );

        cmp( eax, Big2[12] );

        jb true;

        mov( Big1[8], eax );

        cmp( eax, Big2[8] );

        jb true;

        mov( Big1[4], eax );

        cmp( eax, Big2[4] );

        jb true;

        mov( Big1[0], eax );

        cmp( eax, Big2[0] );

        jnbe false;

    }# ) then

        << Code to execute if Big1 <= Big2 >>

    else

        << Code to execute if Big1 > Big2 >>

    endif;

1Newer C standards also provide for a "long long int" which is usually a 64-bit integer.

2As usual, 32 bit arithmetic is available only on the 80386 and later processors.


4.2.4 Extended Precision Multiplication

Although an 8x8, 16x16, or 32x32 multiply is usually sufficient, there are times when you may want to multiply larger values together. You will use the x86 single operand MUL and IMUL instructions for extended precision multiplication.

Not surprisingly (in view of how we achieved extended precision addition using ADC and SBB), you use the same techniques to perform extended precision multiplication on the x86 that you employ when manually multiplying two values. Consider a simplified form of the way you perform multi-digit multiplication by hand:

        1) Multiply the first two              2) Multiply 5*2:

           digits together (5*3):

            123                                      123

             45                                       45

            ---                                       ---

             15                                       15

                                                     10

         3) Multiply 5*1:                       4) Multiply 4*3:

            123                                      123

             45                                       45

            ---                                       ---

             15                                       15

            10                                       10

            5                                        5

                                                     12

         5) Multiply 4*2:                       6) Multiply 4*1:

             123                                     123

              45                                      45

             ---                                     ---

              15                                      15

             10                                      10

             5                                       5

             12                                      12

             8                                       8

                                                    4

         7) Add all the partial products together:

             123

              45

             ---

              15

             10

             5

             12

             8

            4

            ------

            5535

The 80x86 does extended precision multiplication in the same manner except that it works with bytes, words, and double words rather than digits. Figure 4.2 shows how this works

Figure 4.2 Extended Precision Multiplication

Probably the most important thing to remember when performing an extended precision multiplication is that you must also perform a multiple precision addition at the same time. Adding up all the partial products requires several additions that will produce the result. The following listing demonstrates the proper way to multiply two 64 bit values on a 32 bit processor:

Note: Multiplier and Multiplicand are 64 bit variables declared in the data segment via the qword type. Product is a 128 bit variable declared in the data segment via the qword[2] type.

program testMUL64;

#include( "stdlib.hhf" )

type

    t128:dword[4];

procedure MUL64( Multiplier:qword; Multiplicand:qword; var Product:t128 );

const

    mp: text := "(type dword Multiplier)";

    mc: text := "(type dword Multiplicand)";

    prd:text := "(type dword [edi])";

begin MUL64;

    mov( Product, edi );

    // Multiply the L.O. dword of Multiplier times Multiplicand.

    mov( mp, eax );

    mul( mc, eax );     // Multiply L.O. dwords.

    mov( eax, prd );    // Save L.O. dword of product.

    mov( edx, ecx );    // Save H.O. dword of partial product result.

    mov( mp, eax );

    mul( mc[4], eax );  // Multiply mp(L.O.) * mc(H.O.)

    add( ecx, eax );    // Add to the partial product.

    adc( 0, edx );      // Don't forget the carry!

    mov( eax, ebx );    // Save partial product for now.

    mov( edx, ecx );

    // Multiply the H.O. word of Multiplier with Multiplicand.

    mov( mp[4], eax );  // Get H.O. dword of Multiplier.

    mul( mc, eax );     // Multiply by L.O. word of Multiplicand.

    add( ebx, eax );    // Add to the partial product.

    mov( eax, prd[4] ); // Save the partial product.

    adc( edx, ecx );    // Add in the carry!

    pushfd();           // Save carry out here.

    mov( mp[4], eax );  // Multiply the two H.O. dwords together.

    mul( mc[4], eax );

    popfd();            // Retrieve carry from above

    adc( ecx, eax );    // Add in partial product from above.

    adc( 0, edx );      // Don't forget the carry!

    mov( eax, prd[8] ); // Save the partial product.

    mov( edx, prd[12] );

end MUL64;

static

    op1: qword;

    op2: qword;

    rslt: t128;

begin testMUL64;

    // Initialize the qword values (note that static objects

    // are initialized with zero bits).

    mov( 1234, (type dword op1 ));

    mov( 5678, (type dword op2 ));

    MUL64( op1, op2, rslt );

    // The following only prints the L.O. qword, but

    // we know the H.O. qword is zero so this is okay.

    stdout.put( "rslt=" );

    stdout.putu64( (type qword rslt));

end testMUL64;

Program 4.1	 Extended Precision Multiplication

One thing you must keep in mind concerning this code, it only works for unsigned operands. To multiply two signed values you must note the signs of the operands before the multiplication, take the absolute value of the two operands, do an unsigned multiplication, and then adjust the sign of the resulting product based on the signs of the original operands. Multiplication of signed operands appears in the exercises.

This example was fairly straight-forward since it was possible to keep the partial products in various registers. If you need to multiply larger values together, you will need to maintain the partial products in temporary (memory) variables. Other than that, the algorithm that Program 4.1 uses generalizes to any number of double words.

4.2.5 Extended Precision Division

You cannot synthesize a general n-bit/m-bit division operation using the DIV and IDIV instructions. Such an operation must be performed using a sequence of shift and subtract instructions and is extremely messy. However, a less general operation, dividing an n-bit quantity by a 32 bit quantity is easily synthesized using the DIV instruction. This section presents both methods for extended precision division.

Before describing how to perform a multi-precision division operation, you should note that some operations require an extended precision division even though they may look calculable with a single DIV or IDIV instruction. Dividing a 64-bit quantity by a 32-bit quantity is easy, as long as the resulting quotient fits into 32 bits. The DIV and IDIV instructions will handle this directly. However, if the quotient does not fit into 32 bits then you have to handle this problem as an extended precision division. The trick here is to divide the (zero or sign extended) H.O dword of the dividend by the divisor, and then repeat the process with the remainder and the L.O. dword of the dividend. The following sequence demonstrates this:

static

    dividend: dword[2] := [$1234, 4];  // = $4_0000_1234.

    divisor:  dword := 2;              // dividend/divisor = $2_0000_091A

    quotient: dword[2];

    remainder:dword;

     .

     .

     .

    mov( divisor, ebx );

    mov( dividend[4], eax );

    xor( edx, edx );            // Zero extend for unsigned division.

    div( ebx, edx:eax );

    mov( eax, quotient[4] );    // Save H.O. dword of the quotient (2).

    mov( dividend[0], eax );    // Note that this code does *NOT* zero extend

    div( ebx, edx:eax );        //  EAX into EDX before this DIV instr.

    mov( eax, quotient[0] );    // Save L.O. dword of the quotient ($91a).

    mov( edx, remainder );      // Save away the remainder.

Since it is perfectly legal to divide a value by one, it is certainly possible that the resulting quotient after a division could require as many bits as the dividend. That is why the quotient variable in this example is the same size (64 bits) as the dividend variable. Regardless of the size of the dividend and divisor operands, the remainder is always no larger than the size of the division operation (32 bits in this case). Hence the remainder variable in this example is just a double word.

Before analyzing this code to see how it works, let's take a brief look at why a single 64/32 division will not work for this particular example even though the DIV instruction does indeed calculate the result for a 64/32 division. The naive approach, assuming that the x86 were capable of this operation, would look something like the following:

    // This code does *NOT* work!

    mov( dividend[0], eax );    // Get dividend into edx:eax

    mov( divident[4], edx );

    div( divisor, edx:eax );    // Divide edx:eax by divisor.

Although this code is syntactically correct and will compile, if you attempt to run this code it will raise an ex.DivideError1 exception. The reason, if you'll remember how the DIV instruction works, is that the quotient must fit into 32 bits; since the quotient turns out to be $2_0000_091A, it will not fit into the EAX register, hence the resulting exception.

Now let's take another look at the former code that correctly computes the 64/32 quotient. This code begins by computing the 32/32 quotient of dividend[4]/divisor. The quotient from this division (2) becomes the H.O. double word of the final quotient. The remainder from this division (0) becomes the extension in EDX for the second half of the division operation. The second half divides edx:dividend[0] by divisor to produce the L.O. double word of the quotient and the remainder from the division. Note that the code does not zero extend EAX into EDX prior to the second DIV instruction. EDX already contains valid bits and this code must not disturb them.

The 64/32 division operation above is actually just a special case of the more general division operation that lets you divide an arbitrary sized value by a 32-bit divisor. To achieve this, you begin by moving the H.O. double word of the dividend into EAX and zero extending this into EDX. Next, you divide this value by the divisor. Then, without modifying EDX along the way, you store away the partial quotients, load EAX with the next lower double word in the dividend, and divide it by the divisor. You repeat this operation until you've processed all the double words in the dividend. At that time the EDX register will contain the remainder. The following program demonstrates how to divide a 128 bit quantity by a 32 bit divisor, producing a 128 bit quotient and a 32 bit remainder:

program testDiv128;

#include( "stdlib.hhf" )

type

    t128:dword[4];

procedure div128

(

        Dividend:   t128;

        Divisor:    dword;

    var QuotAdrs:   t128;

    var Remainder:  dword

);  @nodisplay;

const

    Quotient: text := "(type dword [edi])";

begin div128;

    push( eax );

    push( edx );

    push( edi );

    mov( QuotAdrs, edi );       // Pointer to quotient storage.

    mov( Dividend[12], eax );   // Begin division with the H.O. dword.

    xor( edx, edx );            // Zero extend into EDX.

    div( Divisor, edx:eax );    // Divide H.O. dword.

    mov( eax, Quotient[12] );   // Store away H.O. dword of quotient.

    mov( Dividend[8], eax );    // Get dword #2 from the dividend

    div( Divisor, edx:eax );    // Continue the division.

    mov( eax, Quotient[8] );    // Store away dword #2 of the quotient.

    mov( Dividend[4], eax );    // Get dword #1 from the dividend.

    div( Divisor, edx:eax );    // Continue the division.

    mov( eax, Quotient[4] );    // Store away dword #1 of the quotient.

    mov( Dividend[0], eax );    // Get the L.O. dword of the dividend.

    div( Divisor, edx:eax );    // Finish the division.

    mov( eax, Quotient[0] );    // Store away the L.O. dword of the quotient.

    mov( Remainder, edi );      // Get the pointer to the remainder's value.

    mov( edx, [edi] );          // Store away the remainder value.

    pop( edi );

    pop( edx );

    pop( eax );

end div128;

static

    op1:    t128    := [$2222_2221, $4444_4444, $6666_6666, $8888_8888];

    op2:    dword   := 2;

    quo:    t128;

    rmndr:  dword;

begin testDiv128;

    div128( op1, op2, quo, rmndr );

    stdout.put

    (

        nl

        nl

        "After the division: " nl

        nl

        "Quotient = $",

        quo[12], "_",

        quo[8], "_",

        quo[4], "_",

        quo[0], nl

        "Remainder = ", (type uns32 rmndr )

    );

end testDiv128;

Program 4.2	 Unsigned 128/32 Bit Extended Precision Division

You can extend this code to any number of bits by simply adding additional MOV / DIV / MOV instructions to the sequence. Like the extended multiplication the previous section presents, this extended precision division algorithm works only for unsigned operands. If you need to divide two signed quantities, you must note their signs, take their absolute values, do the unsigned division, and then set the sign of the result based on the signs of the operands.

If you need to use a divisor larger than 32 bits you're going to have to implement the division using a shift and subtract strategy. Unfortunately, such algorithms are very slow. In this section we'll develop two division algorithms that operate on an arbitrary number of bits. The first is slow but easier to understand, the second is quite a bit faster (in general).

As for multiplication, the best way to understand how the computer performs division is to study how you were taught to perform long division by hand. Consider the operation 3456/12 and the steps you would take to manually perform this operation:

Figure 4.3 Manual Digit-by-digit Division Operation

This algorithm is actually easier in binary since at each step you do not have to guess how many times 12 goes into the remainder nor do you have to multiply 12 by your guess to obtain the amount to subtract. At each step in the binary algorithm the divisor goes into the remainder exactly zero or one times. As an example, consider the division of 27 (11011) by three (11):

Figure 4.4 Longhand Division in Binary

There is a novel way to implement this binary division algorithm that computes the quotient and the remainder at the same time. The algorithm is the following:

Quotient := Dividend;

Remainder := 0;

for i:= 1 to NumberBits do

    Remainder:Quotient := Remainder:Quotient SHL 1;

    if Remainder >= Divisor then

        Remainder := Remainder - Divisor;

        Quotient := Quotient + 1;

    endif

endfor

NumberBits is the number of bits in the Remainder, Quotient, Divisor, and Dividend variables. Note that the "Quotient := Quotient + 1;" statement sets the L.O. bit of Quotient to one since this algorithm previously shifts Quotient one bit to the left. The following program implements this algorithm

program testDiv128b;

#include( "stdlib.hhf" )

type

    t128:dword[4];

// div128-

//

// This procedure does a general 128/128 division operation

// using the following algorithm:

// (all variables are assumed to be 128 bit objects)

//

// Quotient := Dividend;

// Remainder := 0;

// for i:= 1 to NumberBits do

//

//  Remainder:Quotient := Remainder:Quotient SHL 1;

//  if Remainder >= Divisor then

//

//      Remainder := Remainder - Divisor;

//      Quotient := Quotient + 1;

//

//  endif

// endfor

//

procedure div128

(

        Dividend:   t128;

        Divisor:    t128;

    var QuotAdrs:   t128;

    var RmndrAdrs:  t128

);  @nodisplay;

const

    Quotient: text := "Dividend";   // Use the Dividend as the Quotient.

var

    Remainder: t128;

begin div128;

    push( eax );

    push( ecx );

    push( edi );

    mov( 0, eax );              // Set the remainder to zero.

    mov( eax, Remainder[0] );

    mov( eax, Remainder[4] );

    mov( eax, Remainder[8] );

    mov( eax, Remainder[12]);

    mov( 128, ecx );            // Count off 128 bits in ECX.

    repeat

        // Compute Remainder:Quotient := Remainder:Quotient SHL 1:

        shl( 1, Dividend[0] );  // See the section on extended

        rcl( 1, Dividend[4] );  // precision shifts to see how

        rcl( 1, Dividend[8] );  // this code shifts 256 bits to

        rcl( 1, Dividend[12]);  // the left by one bit.

        rcl( 1, Remainder[0] );

        rcl( 1, Remainder[4] );

        rcl( 1, Remainder[8] );

        rcl( 1, Remainder[12]);

        // Do a 128-bit comparison to see if the remainder

        // is greater than or equal to the divisor.

        if

        ( #{

            mov( Remainder[12], eax );

            cmp( eax, Divisor[12] );

            ja true;

            jb false;

            mov( Remainder[8], eax );

            cmp( eax, Divisor[8] );

            ja true;

            jb false;

            mov( Remainder[4], eax );

            cmp( eax, Divisor[4] );

            ja true;

            jb false;

            mov( Remainder[0], eax );

            cmp( eax, Divisor[0] );

            jb false;

        }# ) then

            // Remainder := Remainder - Divisor

            mov( Divisor[0], eax );

            sub( eax, Remainder[0] );

            mov( Divisor[4], eax );

            sbb( eax, Remainder[4] );

            mov( Divisor[8], eax );

            sbb( eax, Remainder[8] );

            mov( Divisor[12], eax );

            sbb( eax, Remainder[12] );

            // Quotient := Quotient + 1;

            add( 1, Quotient[0] );

            adc( 0, Quotient[4] );

            adc( 0, Quotient[8] );

            adc( 0, Quotient[12] );

        endif;

        dec( ecx );

    until( @z );

    // Okay, copy the quotient (left in the Dividend variable)

    // and the remainder to their return locations.

    mov( QuotAdrs, edi );

    mov( Quotient[0], eax );

    mov( eax, [edi] );

    mov( Quotient[4], eax );

    mov( eax, [edi+4] );

    mov( Quotient[8], eax );

    mov( eax, [edi+8] );

    mov( Quotient[12], eax );

    mov( eax, [edi+12] );

    mov( RmndrAdrs, edi );

    mov( Remainder[0], eax );

    mov( eax, [edi] );

    mov( Remainder[4], eax );

    mov( eax, [edi+4] );

    mov( Remainder[8], eax );

    mov( eax, [edi+8] );

    mov( Remainder[12], eax );

    mov( eax, [edi+12] );

    pop( edi );

    pop( ecx );

    pop( eax );

end div128;

// Some simple code to test out the division operation:

static

    op1:    t128    := [$2222_2221, $4444_4444, $6666_6666, $8888_8888];

    op2:    t128    := [2, 0, 0, 0];

    quo:    t128;

    rmndr:  t128;

begin testDiv128b;

    div128( op1, op2, quo, rmndr );

    stdout.put

    (

        nl

        nl

        "After the division: " nl

        nl

        "Quotient = $",

        quo[12], "_",

        quo[8], "_",

        quo[4], "_",

        quo[0], nl

        "Remainder = ", (type uns32 rmndr )

    );

end testDiv128b;

Program 4.3	 Extended Precision Division

This code looks simple but there are a few problems with it. First, it does not check for division by zero (it will produce the value $FFFF_FFFF_FFFF_FFFF if you attempt to divide by zero), it only handles unsigned values, and it is very slow. Handling division by zero is very simple, just check the divisor against zero prior to running this code and return an appropriate error code if the divisor is zero (or RAISE the ex.DivisionError exception). Dealing with signed values is the same as the earlier division algorithm, this problem appears as a programming exercise. The performance of this algorithm, however, leaves a lot to be desired. It's around an order of magnitude or two worse than the DIV/IDIV instructions on the x86 and they are among the slowest instructions on the CPU.

There is a technique you can use to boost the performance of this division by a fair amount: check to see if the divisor variable uses only 32 bits. Often, even though the divisor is a 128 bit variable, the value itself fits just fine into 32 bits (i.e., the H.O. double words of Divisor are zero). In this special case, that occurs frequently, you can use the DIV instruction which is much faster.

1Windows may translate this to an ex.IntoInstr exception.


4.2.6 Extended Precision NEG Operations

Although there are several ways to negate an extended precision value, the shortest way for smaller values (96 bits or less) is to use a combination of NEG and SBB instructions. This technique uses the fact that NEG subtracts its operand from zero. In particular, it sets the flags the same way the SUB instruction would if you subtracted the destination value from zero. This code takes the following form (assuming you want to negate the 64-bit value in EDX:EAX):

    neg( edx );

    neg( eax );

    sbb( 0, edx );

The SBB instruction decrements EDX if there is a borrow out of the L.O. word of the negation operation (which always occurs unless EAX is zero).

To extend this operation to additional bytes, words, or double words is easy; all you have to do is start with the H.O. memory location of the object you want to negate and work towards the L.O. byte. The following code computes a 128 bit negation:

static

    Value: dword[4];

     .

     .

     .

    neg( Value[12] );      // Negate the H.O. double word.

    neg( Value[8] );       // Neg previous dword in memory.

    sbb( 0, Value[12] );   // Adjust H.O. dword.

    neg( Value[4] );       // Negate the second dword in the object.

    sbb( 0, Value[8] );    // Adjust third dword in object.

    sbb( 0, Value[12] );   // Adjust the H.O. dword.

    neg( Value );          // Negate the L.O. dword.

    sbb( 0, Value[4] );    // Adjust second dword in object.

    sbb( 0, Value[8] );    // Adjust third dword in object.

    sbb( 0, Value[12] );   // Adjust the H.O. dword.

Unfortunately, this code tends to get really large and slow since you need to propagate the carry through all the H.O. words after each negate operation. A simpler way to negate larger values is to simply subtract that value from zero:

static

    Value: dword[5];   // 160-bit value.

     .

     .

     .

    mov( 0, eax );

    sub( Value, eax );

    mov( eax, Value );

    mov( 0, eax );

    sbb( Value[4], eax );

    mov( eax, Value[4] );

    mov( 0, eax );

    sbb( Value[8], eax );

    mov( eax, Value[8] );

    mov( 0, eax );

    sbb( Value[12], eax );

    mov( eax, Value[12] );

    mov( 0, eax );

    sbb( Value[16], eax );

    mov( eax, Value[16] );

4.2.7 Extended Precision AND Operations

Performing an n-byte AND operation is very easy - simply AND the corresponding bytes between the two operands, saving the result. For example, to perform the AND operation where all operands are 64 bits long, you could use the following code:

    mov( (type dword source1), eax );

    and( (type dword source2), eax );

    mov( eax, (type dword dest) );

    mov( (type dword source1[4]), eax );

    and( (type dword source2[4]), eax );

    mov( eax, (type dword dest[4]) );

This technique easily extends to any number of words, all you need to is logically AND the corresponding bytes, words, or double words together in the operands. Note that this sequence sets the flags according to the value of the last AND operation. If you AND the H.O. double words last, this sets all but the zero flag correctly. If you need to test the zero flag after this sequence, you will need to logically OR the two resulting double words together (or otherwise compare them both against zero).

4.2.8 Extended Precision OR Operations

Multi-byte logical OR operations are performed in the same way as multi-byte AND operations. You simply OR the corresponding bytes in the two operand together. For example, to logically OR two 96 bit values, use the following code:

    mov( (type dword source1), eax );

    or( (type dword source2), eax );

    mov( eax, (type dword dest) );

    mov( (type dword source1[4]), eax );

    or( (type dword source2[4]), eax );

    mov( eax, (type dword dest[4]) );

    mov( (type dword source1[8]), eax );

    or( (type dword source2[8]), eax );

    mov( eax, (type dword dest[8]) );

As for the previous example, this does not set the zero flag properly for the entire operation. If you need to test the zero flag after a multiprecision OR, you must logically OR the resulting double words together.

4.2.9 Extended Precision XOR Operations

Extended precision XOR operations are performed in a manner identical to AND/OR - simply XOR the corresponding bytes in the two operands to obtain the extended precision result. The following code sequence operates on two 64 bit operands, computes their exclusive-or, and stores the result into a 64 bit variable.

    mov( (type dword source1), eax );

    xor( (type dword source2), eax );

    mov( eax, (type dword dest) );

    mov( (type dword source1[4]), eax );

    xor( (type dword source2[4]), eax );

    mov( eax, (type dword dest[4]) );

The comment about the zero flag in the previous two sections applies here.

4.2.10 Extended Precision NOT Operations

The NOT instruction inverts all the bits in the specified operand. An extended precision NOT is performed by simply executing the NOT instruction on all the affected operands. For example, to perform a 64 bit NOT operation on the value in (edx:eax), all you need to do is execute the instructions:

    not( eax );

    not( edx );

Keep in mind that if you execute the NOT instruction twice, you wind up with the original value. Also note that exclusive-ORing a value with all ones ($FF, $FFFF, or $FFFF_FFFF) performs the same operation as the NOT instruction.

4.2.11 Extended Precision Shift Operations

Extended precision shift operations require a shift and a rotate instruction. Consider what must happen to implement a 64 bit SHL using 32 bit operations:

1) A zero must be shifted into bit zero.

2) Bits zero through 30 are shifted into the next higher bit.

3) Bit 31 is shifted into bit 32.

4) Bits 32 through 62 must be shifted into the next higher bit.

5) Bit 63 is shifted into the carry flag.

Figure 4.5 64-bit Shift Left Operation

The two instructions you can use to implement this 32 bit shift are SHL and RCL. For example, to shift the 64 bit quantity in (EDX:EAX) one position to the left, you'd use the instructions:

    shl( 1, eax );

    rcl( 1, eax );

Note that you can only shift an extended precision value one bit at a time. You cannot shift an extended precision operand several bits using the CL register. Nor can you specify a constant value greater than one using this technique.

To understand how this instruction sequence works, consider the operation of these instructions on an individual basis. The SHL instruction shifts a zero into bit zero of the 64 bit operand and shifts bit 31 into the carry flag. The RCL instruction then shifts the carry flag into bit 32 and then shifts bit 63 into the carry flag. The result is exactly what we want.

To perform a shift left on an operand larger than 64 bits you simply add additional RCL instructions. An extended precision shift left operation always starts with the least significant word and each succeeding RCL instruction operates on the next most significant word. For example, to perform a 96 bit shift left operation on a memory location you could use the following instructions:

    shl( 1, (type dword Operand[0]) );

    rcl( 1, (type dword Operand[4])  );

    rcl( 1, (type dword Operand[8])  );

If you need to shift your data by two or more bits, you can either repeat the above sequence the desired number of times (for a constant number of shifts) or you can place the instructions in a loop to repeat them some number of times. For example, the following code shifts the 96 bit value Operand to the left the number of bits specified in ECX:

ShiftLoop:

    shl( 1, (type dword Operand[0]) );

    rcl( 1, (type dword Operand[4]) );

    rcl( 1, (type dword Operand[8]) );

    dec( ecx );

    jnz ShiftLoop;

You implement SHR and SAR in a similar way, except you must start at the H.O. word of the operand and work your way down to the L.O. word:

// Double precision SAR:

    sar( 1, (type dword Operand[8]) );

    rcr( 1, (type dword Operand[4]) );

    rcr( 1, (type dword Operand[0]) );

// Double precision SHR:

    shr( 1, (type dword Operand[8]) );

    rcr( 1, (type dword Operand[4]) );

    rcr( 1, (type dword Operand[0]) );

There is one major difference between the extended precision shifts described here and their 8/16/32 bit counterparts - the extended precision shifts set the flags differently than the single precision operations. This is because the rotate instructions affect the flags differently than the shift instructions. Fortunately, the carry is the flag most often tested after a shift operation and the extended precision shift operations (i.e., rotate instructions) properly set this flag.

The SHLD and SHRD instructions let you efficiently implement multiprecision shifts of several bits. These instructions have the following syntax:

    shld( constant, Operand1, Operand2 );

    shld( cl, Operand1, Operand2 );

    shrd( constant, Operand1, Operand2 );

    shrd( cl, Operand1, Operand2 );

The SHLD instruction does the following:

Figure 4.6 SHLD Operation

Operand1 must be a 16 or 32 bit register. Operand2 can be a register or a memory location. Both operands must be the same size. The immediate operand can be a value in the range zero through n-1, where n is the number of bits in the two operands; it specifies the number of bits to shift.

The SHLD instruction shifts bits in Operand2 to the left. The H.O. bits shift into the carry flag and the H.O. bits of Operand1 shift into the L.O. bits of Operand2. Note that this instruction does not modify the value of Operand1, it uses a temporary copy of Operand1 during the shift. The immediate operand specifies the number of bits to shift. If the count is n, then SHLD shifts bit n-1 into the carry flag. It also shifts the H.O. n bits of Operand1 into the L.O. n bits of Operand2. The SHLD instruction sets the flag bits as follows:

  • If the shift count is zero, the SHLD instruction doesn't affect any flags.
  • The carry flag contains the last bit shifted out of the H.O. bit of the Operand2.
  • If the shift count is one, the overflow flag will contain one if the sign bit of Operand2 changes during the shift. If the count is not one, the overflow flag is undefined.
  • The zero flag will be one if the shift produces a zero result.
  • The sign flag will contain the H.O. bit of the result.

The SHRD instruction is similar to SHLD except, of course, it shifts its bits right rather than left. To get a clear picture of the SHRD instruction, consider Figure 4.7

Figure 4.7 SHRD Operation

The SHRD instruction sets the flag bits as follows:

  • If the shift count is zero, the SHRD instruction doesn't affect any flags.
  • The carry flag contains the last bit shifted out of the L.O. bit of the Operand2.
  • If the shift count is one, the overflow flag will contain one if the H.O. bit of Operand2 changes. If the count is not one, the overflow flag is undefined.
  • The zero flag will be one if the shift produces a zero result.
  • The sign flag will contain the H.O. bit of the result.

Consider the following code sequence:

static

    ShiftMe: dword[3] := [ $1234, $5678, $9012 ];

     .

     .

     .

    mov( ShiftMe[4], eax )

    shld( 6, eax, ShiftMe[8] );

    mov( ShiftMe[0], eax );

    shld( 6, eax, ShiftMe[4] );

    shl( 6, ShiftMe[0] );

The first SHLD instruction above shifts the bits from ShiftMe+4 into ShiftMe+8 without affecting the value in ShiftMe+4. The second SHLD instruction shifts the bits from SHIFTME into SHIFTME+4. Finally, the SHL instruction shifts the L.O. double word the appropriate amount. There are two important things to note about this code. First, unlike the other extended precision shift left operations, this sequence works from the H.O. double word down to the L.O. double word. Second, the carry flag does not contain the carry out of the H.O. shift operation. If you need to preserve the carry flag at that point, you will need to push the flags after the first SHLD instruction and pop the flags after the SHL instruction.

You can do an extended precision shift right operation using the SHRD instruction. It works almost the same way as the code sequence above except you work from the L.O. double word to the H.O. double word. The solution is left as an exercise.

4.2.12 Extended Precision Rotate Operations

The RCL and RCR operations extend in a manner almost identical to that for SHL and SHR . For example, to perform 96 bit RCL and RCR operations, use the following instructions:

    rcl( 1, (type dword Operand[0]) );

    rcl( 1, (type dword Operand[4])  );

    rcl( 1, (type dword Operand[8])  );

    rcr( 1, (type dword Operand[8]) );

    rcr( 1, (type dword Operand[4])  );

    rcr( 1, (type dword Operand[0])  );

The only difference between this code and the code for the extended precision shift operations is that the first instruction is a RCL or RCR rather than a SHL or SHR instruction.

Performing an extended precision ROL or ROR instruction isn't quite as simple an operation. You can use the BT, SHLD, and SHRD instructions to implement an extended precision ROL or ROR instruction. The following code shows how to use the SHLD instruction to do an extended precision ROL:

// Compute ROL( 4, EDX:EAX );

        mov( edx, ebx );

        shld, 4, eax, edx );

        shld( 4, ebx, eax );

        bt( 0, eax );        // Set carry flag, if desired.

An extended precision ROR instruction is similar; just keep in mind that you work on the L.O. end of the object first and the H.O. end last.


4.2.13 Extended Precision I/O

Once you have the ability to compute using extended precision arithmetic, the next problem is how do you get those extended precision values into your program and how do you display those extended precision values to the user? HLA's Standard Library provides routines for unsigned decimal, signed decimal, and hexadecimal I/O for values that are eight, 16, 32, or 64 bits in length. So as long as you're working with values whose size is less than or equal to 64 bits in length, you can use the Standard Library code. If you need to input or output values that are greater than 64 bits in length, you will need to write your own procedures to handle the operation. This section discusses the strategies you will need to write such routines.

The examples in this section work specifically with 128-bit values. The algorithms are perfectly general and extend to any number of bits (indeed, the 128-bit algorithms in this section are really nothing more than an extension of the algorithms the HLA Standard Library uses for 64-bit values). If you need a set of 128-bit unsigned I/O routines, you will probably be able to use the following code as-is. If you need to handle larger values, simple modifications to the following code is all that should be necessary.

The following examples all assume a common data type for 128-bit values. The HLA type declaration for this data type is one of the following depending on the type of value

type

    bits128: dword[4];

    uns128: bits128;

    int128: bits128;

4.2.13.1 Extended Precision Hexadecimal Output

Extended precision hexadecimal output is very easy. All you have to do is output each double word component of the extended precision value from the H.O. double word to the L.O. double word using a call to the stdout.putd routine. The following procedure does exactly this to output a bits128 value:

procedure putb128( b128: bits128 ); nodisplay;

begin putb128;

    stdout.putd( b128[12] );

    stdout.putd( b128[8] );

    stdout.putd( b128[4] );

    stdout.putd( b128[0] );

end putb128;

Since HLA provides the stdout.putq procedure, you can shorten the code above by calling stdout.putq just twice:

procedure putb128( b128: bits128 ); nodisplay;

begin putb128;

    stdout.putq( (type qword b128[8]) );

    stdout.putq( (type qword b128[0]) );

end putb128;

Note that this code outputs the two quad words with the H.O. quad word output first and L.O. quad word output second.

4.2.13.2 Extended Precision Unsigned Decimal Output

Decimal output is a little more complicated than hexadecimal output because the H.O. bits of a binary number affect the L.O. digits of the decimal representation (this was not true for hexadecimal values which is why hexadecimal output is so easy). Therefore, we will have to create the decimal representation for a binary number by extracting one decimal digit at a time from the number.

The most common solution for unsigned decimal output is to successively divide the value by ten until the result becomes zero. The remainder after the first division is a value in the range 0..9 and this value corresponds to the L.O. digit of the decimal number. Successive divisions by ten (and their corresponding remainder) extract successive digits in the number.

Iterative solutions to this problem generally allocate storage for a string of characters large enough to hold the entire number. Then the code extracts the decimal digits in a loop and places them in the string one by one. At the end of the conversion process, the routine prints the characters in the string in reverse order (remember, the divide algorithm extracts the L.O. digits first and the H.O. digits last, the opposite of the way you need to print them).

In this section, we will employ a recursive solution because it is a little more elegant. The recursive solution begins by dividing the value by 10 and saving the remainder in a local variable. If the quotient was not zero, the routine recursively calls itself to print any leading digits first. On return from the recursive call (which prints all the leading digits), the recursive algorithm prints the digit associated with the remainder to complete the operation. Here's how the operation works when printing the decimal value "123":

  • (1) Divide 123 by 10. Quotient is 12, remainder is 3.
  • (2) Save the remainder (3) in a local variable and recursively call the routine with the quotient.
  • (3) [Recursive Entry 1] Divide 12 by 10. Quotient is 1, remainder is 2.
  • (4) Save the remainder (2) in a local variable and recursively call the routine with the quotient.
  • (5) [Recursive Entry 2] Divide 1 by 10. Quotient is 0, remainder is 1.
  • (6) Save the remainder (1) in a local variable. Since the Quotient is zero, don't call the routine recursively.
  • (7) Output the remainder value saved in the local variable (1). Return to the caller (Recursive Entry 1).
  • (8) [Return to Recursive Entry 1] Output the remainder value saved in the local variable in recursive entry 1 (2). Return to the caller (original invocation of the procedure).
  • (9) [Original invocation] Output the remainder value saved in the local variable in the original call (3). Return to the original caller of the output routine.

The only operation that requires extended precision calculation through this entire algorithm is the "divide by 10" requirement. Everything else is simple and straight-forward. We are in luck with this algorithm, since we are dividing an extended precision value by a value that easily fits into a double word, we can use the fast (and easy) extended precision division algorithm that uses the DIV instruction (see "Extended Precision Division" on page 864). The following program implements a 128-bit decimal output routine utilizing this technique.

program out128;

#include( "stdlib.hhf" );

// 128-bit unsigned integer data type:

type

    uns128: dword[4];

// DivideBy10-

//

//  Divides "divisor" by 10 using fast

//  extended precision division algorithm

//  that employs the DIV instruction.

//

//  Returns quotient in "quotient"

//  Returns remainder in eax.

//  Trashes EBX, EDX, and EDI.

procedure DivideBy10( dividend:uns128; var quotient:uns128 ); @nodisplay;

begin DivideBy10;

    mov( quotient, edi );

    xor( edx, edx );

    mov( dividend[12], eax );

    mov( 10, ebx );

    div( ebx, edx:eax );

    mov( eax, [edi+12] );

    mov( dividend[8], eax );

    div( ebx, edx:eax );

    mov( eax, [edi+8] );

    mov( dividend[4], eax );

    div( ebx, edx:eax );

    mov( eax, [edi+4] );

    mov( dividend[0], eax );

    div( ebx, edx:eax );

    mov( eax, [edi+0] );

    mov( edx, eax );

end DivideBy10;

// Recursive version of putu128.

// A separate "shell" procedure calls this so that

// this code does not have to preserve all the registers

// it uses (and DivideBy10 uses) on each recursive call.

procedure recursivePutu128( b128:uns128 ); @nodisplay;

var

    remainder: byte;

begin recursivePutu128;

    // Divide by ten and get the remainder (the char to print).

    DivideBy10( b128, b128 );

    mov( al, remainder );       // Save away the remainder (0..9).

    // If the quotient (left in b128) is not zero, recursively

    // call this routine to print the H.O. digits.

    mov( b128[0], eax );    // If we logically OR all the dwords

    or( b128[4], eax );     // together, the result is zero if and

    or( b128[8], eax );     // only if the entire number is zero.

    or( b128[12], eax );

    if( @nz ) then

        recursivePutu128( b128 );

    endif;

    // Okay, now print the current digit.

    mov( remainder, al );

    or( '0', al );          // Converts 0..9 -> '0..'9'.

    stdout.putc( al );

end recursivePutu128;

// Non-recursive shell to the above routine so we don't bother

// saving all the registers on each recursive call.

procedure putu128( b128:uns128 ); @nodisplay;

begin putu128;

    push( eax );

    push( ebx );

    push( edx );

    push( edi );

    recursivePutu128( b128 );

    pop( edi );

    pop( edx );

    pop( ebx );

    pop( eax );

end putu128;

// Code to test the routines above:

static

    b0: uns128 := [0, 0, 0, 0];             // decimal = 0

    b1: uns128 := [1234567890, 0, 0, 0];    // decimal = 1234567890

    b2: uns128 := [$8000_0000, 0, 0, 0];    // decimal = 2147483648

    b3: uns128 := [0, 1, 0, 0 ];            // decimal = 4294967296

    // Largest uns128 value

    // (decimal=340,282,366,920,938,463,463,374,607,431,768,211,455):

    b4: uns128 := [$FFFF_FFFF, $FFFF_FFFF, $FFFF_FFFF, $FFFF_FFFF ];

begin out128;

    stdout.put( "b0 = " );

    putu128( b0 );

    stdout.newln();

    stdout.put( "b1 = " );

    putu128( b1 );

    stdout.newln();

    stdout.put( "b2 = " );

    putu128( b2 );

    stdout.newln();

    stdout.put( "b3 = " );

    putu128( b3 );

    stdout.newln();

    stdout.put( "b4 = " );

    putu128( b4 );

    stdout.newln();

end out128;

Program 4.4	 128-bit Extended Precision Decimal Output Routine

4.2.13.3 Extended Precision Signed Decimal Output

Once you have an extended precision unsigned decimal output routine, writing an extended precision signed decimal output routine is very easy. The basic algorithm takes the following form:

  • Check the sign of the number. If it is positive, call the unsigned output routine to print it.
  • If the number is negative, print a minus sign. Then negate the number and call the unsigned output routine to print it.

To check the sign of an extended precision integer, of course, you simply test the H.O. bit of the number. To negate a large value, the best solution is to probably subtract that value from zero. Here's a quick version of puti128 that uses the putu128 routine from the previous section.

procedure puti128( i128: int128 ); nodisplay;

begin puti128;

    if( (type int32 i128[12]) < 0 ) then

        stdout.put( '-' );

        // Extended Precision Negation:

        push( eax );

        mov( 0, eax );

        sub( i128[0], eax );

        mov( eax, i128[0] );

        mov( 0, eax );

        sbb( i128[4], eax );

        mov( eax, i128[4] );

        mov( 0, eax );

        sbb( i128[8], eax );

        mov( eax, i128[8] );

        mov( 0, eax );

        sbb( i128[12], eax );

        mov( eax, i128[12] );

        pop( eax );

    endif;

    putu128( (type uns128 i128));

end puti128;

4.2.13.4 Extended Precision Formatted I/O

The code in the previous two sections prints signed and unsigned integers using the minimum number of necessary print positions. To create nicely formatted tables of values you will need the equivalent of a puti128Size or putu128Size routine. Once you have the "unformatted" versions of these routines, implementing the formatted versions is very easy.

The first step is to write an "i128Size" and a "u128Size" routine that computes the minimum number of digits needed to display the value. The algorithm to accomplish this is very similar to the numeric output routines. In fact, the only difference is that you initialize a counter to zero upon entry into the routine (e.g., the non-recursive shell routine) and you increment this counter rather than outputting a digit on each recursive call. (Don't forget to increment the counter inside "i128Size" if the number is negative; you must allow for the output of the minus sign.) After the calculation is complete, these routines should return the size of the operand in the EAX register.

Once you have the "i128Size" and "u128Size" routines, writing the formatted output routines is very easy. Upon initial entry into puti128Size or putu128Size, these routines call the corresponding "size" routine to determine the number of print positions for the number to display. If the value that the "size" routine returns is greater than the absolute value of the minimum size parameter (passed into puti128Size or putu128Size) all you need to do is call the put routine to print the value, no other formatting is necessary. If the absolute value of the parameter size is greater than the value i128Size or u128Size returns, then the program must compute the difference between these two values and print that many spaces (or other filler character) before printing the number (if the parameter size value is positive) or after printing the number (if the parameter size value is negative). The actual implementation of these two routines is left as an exercise at the end of the volume. If you have any further questions about how to do this, you can take a look at the HLA Standard Library code for routines like stdout.putu32Size.

4.2.13.5 Extended Precision Input Routines

There are a couple of fundamental differences between the extended precision output routines and the extended precision input routines. First of all, numeric output generally occurs without possibility of error1; numeric input, on the other hand, must handle the very real possibility of an input error such as illegal characters and numeric overflow. Also, HLA's Standard Library and run-time system encourages a slightly different approach to input conversion. This section discusses those issues that differentiate input conversion from output conversion.

Perhaps the biggest difference between input and output conversion is the fact that output conversion is unbracketed. That is, when converting a numeric value to a string of characters for output, the output routine does not concern itself with characters preceding the output string nor does it concerning itself with the characters following the numeric value in the output stream. Numeric output routines convert their data to a string and print that string without considering the context (i.e., the characters before and after the string representation of the numeric value). Numeric input routines cannot be so cavalier; the contextual information surrounding the numeric string is very important.

A typical numeric input operation consists of reading a string of characters from the user and then translating this string of characters into an internal numeric representation. For example, a statement like "stdin.get(i32);" typically reads a line of text from the user and converts a sequence of digits appearing at the beginning of that line of text into a 32-bit signed integer (assuming i32 is an int32 object). Note, however, that the stdin.get routine skips over certain characters in the string that may appear before the actual numeric characters. For example, stdin.get automatically skips any leading spaces in the string. Likewise, the input string may contain additional data beyond the end of the numeric input (for example, it is possible to read two integer values from the same input line), therefore the input conversion routine must somehow determine where the numeric data ends in the input stream. Fortunately, HLA provides a simple mechanism that lets you easily determine the start and end of the input data: the Delimiters character set.

The Delimiters character set is a variable, internal to HLA, that contains the set of legal characters that may precede or follow a legal numeric value. By default, this character set includes the end of string marker (a zero byte), a tab character, a line feed character, a carriage return character, a space, a comma, a colon, and a semicolon. Therefore, HLA's numeric input routines will automatically ignore any characters in this set that occur on input before a numeric string. Likewise, characters from this set may legally follow a numeric string on input (conversely, if any non-delimiter character follows the numeric string, HLA will raise an ex.ConversionError exception).

The Delimiters character set is a private variable inside the HLA Standard Library. Although you do not have direct access to this object, the HLA Standard Library does provide two accessor functions, conv.setDelimiters and conv.getDelimiters that let you access and modify the value of this character set. These two functions have the following prototypes (found in the "conv.hhf" header file):

procedure conv.setDelimiters( Delims:cset );

procedure conv.getDelimiters( var Delims:cset );

The conv.SetDelimiters procedure will copy the value of the Delims parameter into the internal Delimiters character set. Therefore, you can use this procedure to change the character set if you want to use a different set of delimiters for numeric input. The conv.getDelimiters call returns a copy of the internal Delimiters character set in the variable you pass as a parameter to the conv.getDelimiters procedure. We will use the value returned by conv.getDelimiters to determine the end of numeric input when writing our own extended precision numeric input routines.

When reading a numeric value from the user, the first step will be to get a copy of the Delimiters character set. The second step is to read and discard input characters from the user as long as those characters are members of the Delimiters character set. Once a character is found that is not in the Delimiters set, the input routine must check this character and verify that it is a legal numeric character. If not, the program should raise an ex.IllegalChar exception if the character's value is outside the range $00..$7f or it should raise the ex.ConversionError exception if the character is not a legal numeric character. Once the routine encounters a numeric character, it should continue reading characters as long as they valid numeric characters; while reading the characters the conversion routine should be translating them to the internal representation of the numeric data. If, during conversion, an overflow occurs, the procedure should raise the ex.ValueOutOfRange exception.

Conversion to numeric representation should end when the procedure encounters the first delimiter character at the end of the string of digits. However, it is very important that the procedure does not consume the delimiter character that ends the string. That is, the following is incorrect:

static

    Delimiters: cset;

         .

         .

         .

    conv.getDelimiters( Delimiters );

    // Skip over leading delimiters in the string:

    while( stdin.getc() in Delimiters ) do  /* getc did the work */ endwhile;

    while( al in {'0'..'9'}) do

        // Convert character in AL to numeric representation and

        // accumulate result...

        stdin.getc();

    endwhile;

    if( al not in Delimiters ) then

        raise( ex.ConversionError );

    endif;

The first WHILE loop reads a sequence of delimiter characters. When this first WHILE loop ends, the character in AL is not a delimiter character. So far, so good. The second WHILE loop processes a sequence of decimal digits. First, it checks the character read in the previous WHILE loop to see if it is a decimal digit; if so, it processes that digit and reads the next character. This process continues until the call to stdin.getc (at the bottom of the loop) reads a non-digit character. After the second WHILE loop, the program checks the last character read to ensure that it is a legal delimiter character for a numeric input value.

The problem with this algorithm is that it consumes the delimiter character after the numeric string. For example, the colon symbol is a legal delimiter in the default Delimiters character set. If the user types the input "123:456" and executes the code above, this code will properly convert "123" to the numeric value one hundred twenty-three. However, the very next character read from the input stream will be the character "4" not the colon character (":"). While this may be acceptable in certain circumstances, Most programmers expect numeric input routines to consume only leading delimiter characters and the numeric digit characters. They do not expect the input routine to consume any trailing delimiter characters (e.g., many programs will read the next character and expect a colon as input if presented with the string "123:456"). Since stdin.getc consumes an input character, and there is no way to "put the character back" onto the input stream, some other way of reading input characters from the user, that doesn't consume those characters, is needed2.

The HLA Standard Library comes to the rescue by providing the stdin.peekc function. Like stdin.getc, the stdin.peekc routine reads the next input character from HLA's internal buffer. There are two major differences between stdin.peekc and stdin.getc. First, stdin.peekc will not force the input of a new line of text from the user if the current input line is empty (or you've already read all the text from the input line). Instead, stdin.peekc simply returns zero in the AL register to indicate that there are no more characters on the input line. Since #0 is (by default) a legal delimiter character for numeric values, and the end of line is certainly a legal way to terminate numeric input, this works out rather well. The second difference between stdin.getc and stdin.peekc is that stdin.peekc does not consume the character read from the input buffer. If you call stdin.peekc several times in a row, it will always return the same character; likewise, if you call stdin.getc immediately after stdin.peekc, the call to stdin.getc will generally return the same character as returned by stdin.peekc (the only exception being the end of line condition). So although we cannot put characters back onto the input stream after we've read them with stdin.getc, we can peek ahead at the next character on the input stream and base our logic on that character's value. A corrected version of the previous algorithm might be the following:

static

    Delimiters: cset;

         .

         .

         .

    conv.getDelimiters( Delimiters );

    // Skip over leading delimiters in the string:

    while( stdin.peekc() in Delimiters ) do

        // If at the end of the input buffer, we must explicitly read a

        // new line of text from the user.  stdin.peekc does not do this

        // for us.

        if( al = #0 ) then

            stdin.ReadLn();

        else

            stdin.getc();  // Remove delimiter from the input stream.

        endif;

    endwhile;

    while( stdin.peekc in {'0'..'9'}) do

        stdin.getc();     // Remove the input character from the input stream.

        // Convert character in AL to numeric representation and

        // accumulate result...

    endwhile;

    if( al not in Delimiters ) then

        raise( ex.ConversionError );

    endif;

Note that the call to stdin.peekc in the second WHILE does not consume the delimiter character when the expression evaluates false. Hence, the delimiter character will be the next character read after this algorithm finishes.

The only remaining comment to make about numeric input is to point out that the HLA Standard Library input routines allow arbitrary underscores to appear within a numeric string. The input routines ignore these underscore characters. This allows the user to input strings like "FFFF_F012" and "1_023_596" which are a little more readable than "FFFFF012" or "1023596". To allow underscores (or any other symbol you choose) within a numeric input routine is quite simple; just modify the second WHILE loop above as follows:

    while( stdin.peekc in {'0'..'9', '_'}) do

        stdin.getc();  // Read the character from the input stream.

        // Ignore underscores while processing numeric input.

        if( al <> '_' ) then

            // Convert character in AL to numeric representation and

            // accumulate result...

        endif;

    endwhile;

4.2.13.6 Extended Precision Hexadecimal Input

As was the case for numeric output, hexadecimal input is the easiest numeric input routine to write. The basic algorithm for hexadecimal string to numeric conversion is the following:

  • Initialize the extended precision value to zero.
  • For each input character that is a valid hexadecimal digit, do the following:
  • Convert the hexadecimal character to a value in the range 0..15 ($0..$F).
  • If the H.O. four bits of the extended precision value are non-zero, raise an exception.
  • Multiply the current extended precision value by 16 (i.e., shift left four bits).
  • Add the converted hexadecimal digit value to the accumulator.
  • Check the last input character to ensure it is a valid delimiter. Raise an exception if it is not.

The following program implements this extended precision hexadecimal input routine for 128-bit values.

program Xin128;

#include( "stdlib.hhf" );

// 128-bit unsigned integer data type:

type

    b128: dword[4];

procedure getb128( var inValue:b128 ); @nodisplay;

const

    HexChars  := {'0'..'9', 'a'..'f', 'A'..'F', '_'};

var

    Delimiters: cset;

    LocalValue: b128;

begin getb128;

    push( eax );

    push( ebx );

    // Get a copy of the HLA standard numeric input delimiters:

    conv.getDelimiters( Delimiters );

    // Initialize the numeric input value to zero:

    xor( eax, eax );

    mov( eax, LocalValue[0] );

    mov( eax, LocalValue[4] );

    mov( eax, LocalValue[8] );

    mov( eax, LocalValue[12] );

    // By default, #0 is a member of the HLA Delimiters

    // character set.  However, someone may have called

    // conv.setDelimiters and removed this character

    // from the internal Delimiters character set.  This

    // algorithm depends upon #0 being in the Delimiters

    // character set, so let's add that character in

    // at this point just to be sure.

    cs.unionChar( #0, Delimiters );

    // If we're at the end of the current input

    // line (or the program has yet to read any input),

    // for the input of an actual character.

    if( stdin.peekc() = #0 ) then

        stdin.readLn();

    endif;

    // Skip the delimiters found on input.  This code is

    // somewhat convoluted because stdin.peekc does not

    // force the input of a new line of text if the current

    // input buffer is empty.  We have to force that input

    // ourselves in the event the input buffer is empty.

    while( stdin.peekc() in Delimiters ) do

        // If we're at the end of the line, read a new line

        // of text from the user; otherwise, remove the

        // delimiter character from the input stream.

        if( al = #0 ) then

            stdin.readLn(); // Force a new input line.

        else

            stdin.getc();   // Remove the delimiter from the input buffer.

        endif;

    endwhile;

    // Read the hexadecimal input characters and convert

    // them to the internal representation:

    while( stdin.peekc() in HexChars ) do

        // Actually read the character to remove it from the

        // input buffer.

        stdin.getc();

        // Ignore underscores, process everything else.

        if( al <> '_' ) then

            if( al in '0'..'9' ) then

                and( $f, al );  // '0'..'9' -> 0..9

            else

                and( $f, al );  // 'a'/'A'..'f'/'F' -> 1..6

                add( 9, al );   // 1..6 -> 10..15

            endif;

            // Conversion algorithm is the following:

            //

            // (1) LocalValue := LocalValue * 16.

            // (2) LocalValue := LocalValue + al

            //

            // Note that "* 16" is easily accomplished by

            // shifting LocalValue to the left four bits.

            //

            // Overflow occurs if the H.O. four bits of LocalValue

            // contain a non-zero value prior to this operation.

            // First, check for overflow:

            test( $F0, (type byte LocalValue[15]));

            if( @nz ) then

                raise( ex.ValueOutOfRange );

            endif;

            // Now multiply LocalValue by 16 and add in

            // the current hexadecimal digit (in EAX).

            mov( LocalValue[8], ebx );

            shld( 4, ebx, LocalValue[12] );

            mov( LocalValue[4], ebx );

            shld( 4, ebx, LocalValue[8] );

            mov( LocalValue[0], ebx );

            shld( 4, ebx, LocalValue[4] );

            shl( 4, ebx );

            add( eax, ebx );

            mov( ebx, LocalValue[0] );

        endif;

    endwhile;

    // Okay, we've encountered a non-hexadecimal character.

    // Let's make sure it's a valid delimiter character.

    // Raise the ex.ConversionError exception if it's invalid.

    if( al not in Delimiters ) then

        raise( ex.ConversionError );

    endif;

    // Okay, this conversion has been a success.  Let's store

    // away the converted value into the output parameter.

    mov( inValue, ebx );

    mov( LocalValue[0], eax );

    mov( eax, [ebx] );

    mov( LocalValue[4], eax );

    mov( eax, [ebx+4] );

    mov( LocalValue[8], eax );

    mov( eax, [ebx+8] );

    mov( LocalValue[12], eax );

    mov( eax, [ebx+12] );

    pop( ebx );

    pop( eax );

end getb128;

// Code to test the routines above:

static

    b1:b128;

begin Xin128;

    stdout.put( "Input a 128-bit hexadecimal value: " );

    getb128( b1 );

    stdout.put

    (

        "The value is: $",

        b1[12], '_',

        b1[8],  '_',

        b1[4],  '_',

        b1[0],

        nl

    );

end Xin128;

Program 4.5	 Extended Precision Hexadecimal Input

Extending this code to handle objects that are not 128 bits long is very easy. There are only three changes necessary: you must zero out the whole object at the beginning of the getb128 routine; when checking for overflow (the "test( $F, (type byte LocalValue[15]));" instruction) you must test the H.O. four bits of the new object you're processing; and you must modify the code that multiplies LocalValue by 16 (via SHLD) so that it multiplies your object by 16 (i.e., shifts it to the left four bits).

4.2.13.7 Extended Precision Unsigned Decimal Input

The algorithm for extended precision unsigned decimal input is nearly identical to that for hexadecimal input. In fact, the only difference (beyond only accepting decimal digits) is that you multiply the extended precision value by 10 rather than 16 for each input character (in general, the algorithm is the same for any base; just multiply the accumulating value by the input base). The following code demonstrates how to write a 128-bit unsigned decimal input routine.

program Uin128;

#include( "stdlib.hhf" );

// 128-bit unsigned integer data type:

type

    u128: dword[4];

procedure getu128( var inValue:u128 ); @nodisplay;

var

    Delimiters: cset;

    LocalValue: u128;

    PartialSum: u128;

begin getu128;

    push( eax );

    push( ebx );

    push( ecx );

    push( edx );

    // Get a copy of the HLA standard numeric input delimiters:

    conv.getDelimiters( Delimiters );

    // Initialize the numeric input value to zero:

    xor( eax, eax );

    mov( eax, LocalValue[0] );

    mov( eax, LocalValue[4] );

    mov( eax, LocalValue[8] );

    mov( eax, LocalValue[12] );

    // By default, #0 is a member of the HLA Delimiters

    // character set.  However, someone may have called

    // conv.setDelimiters and removed this character

    // from the internal Delimiters character set.  This

    // algorithm depends upon #0 being in the Delimiters

    // character set, so let's add that character in

    // at this point just to be sure.

    cs.unionChar( #0, Delimiters );

    // If we're at the end of the current input

    // line (or the program has yet to read any input),

    // for the input of an actual character.

    if( stdin.peekc() = #0 ) then

        stdin.readLn();

    endif;

    // Skip the delimiters found on input.  This code is

    // somewhat convoluted because stdin.peekc does not

    // force the input of a new line of text if the current

    // input buffer is empty.  We have to force that input

    // ourselves in the event the input buffer is empty.

    while( stdin.peekc() in Delimiters ) do

        // If we're at the end of the line, read a new line

        // of text from the user; otherwise, remove the

        // delimiter character from the input stream.

        if( al = #0 ) then

            stdin.readLn(); // Force a new input line.

        else

            stdin.getc();   // Remove the delimiter from the input buffer.

        endif;

    endwhile;

    // Read the decimal input characters and convert

    // them to the internal representation:

    while( stdin.peekc() in '0'..'9' ) do

        // Actually read the character to remove it from the

        // input buffer.

        stdin.getc();

        // Ignore underscores, process everything else.

        if( al <> '_' ) then

            and( $f, al );              // '0'..'9' -> 0..9

            mov( eax, PartialSum[0] );  // Save to add in later.

            // Conversion algorithm is the following:

            //

            // (1) LocalValue := LocalValue * 10.

            // (2) LocalValue := LocalValue + al

            //

            // First, multiply LocalValue by 10:

            mov( 10, eax );

            mul( LocalValue[0], eax );

            mov( eax, LocalValue[0] );

            mov( edx, PartialSum[4] );

            mov( 10, eax );

            mul( LocalValue[4], eax );

            mov( eax, LocalValue[4] );

            mov( edx, PartialSum[8] );

            mov( 10, eax );

            mul( LocalValue[8], eax );

            mov( eax, LocalValue[8] );

            mov( edx, PartialSum[12] );

            mov( 10, eax );

            mul( LocalValue[12], eax );

            mov( eax, LocalValue[12] );

            // Check for overflow.  This occurs if EDX

            // contains a none zero value.

            if( edx /* <> 0 */ ) then

                raise( ex.ValueOutOfRange );

            endif;

            // Add in the partial sums (including the

            // most recently converted character).

            mov( PartialSum[0], eax );

            add( eax, LocalValue[0] );

            mov( PartialSum[4], eax );

            adc( eax, LocalValue[4] );

            mov( PartialSum[8], eax );

            adc( eax, LocalValue[8] );

            mov( PartialSum[12], eax );

            adc( eax, LocalValue[12] );

            // Another check for overflow.  If there

            // was a carry out of the extended precision

            // addition above, we've got overflow.

            if( @c ) then

                raise( ex.ValueOutOfRange );

            endif;

        endif;

    endwhile;

    // Okay, we've encountered a non-decimal character.

    // Let's make sure it's a valid delimiter character.

    // Raise the ex.ConversionError exception if it's invalid.

    if( al not in Delimiters ) then

        raise( ex.ConversionError );

    endif;

    // Okay, this conversion has been a success.  Let's store

    // away the converted value into the output parameter.

    mov( inValue, ebx );

    mov( LocalValue[0], eax );

    mov( eax, [ebx] );

    mov( LocalValue[4], eax );

    mov( eax, [ebx+4] );

    mov( LocalValue[8], eax );

    mov( eax, [ebx+8] );

    mov( LocalValue[12], eax );

    mov( eax, [ebx+12] );

    pop( edx );

    pop( ecx );

    pop( ebx );

    pop( eax );

end getu128;

// Code to test the routines above:

static

    b1:u128;

begin Uin128;

    stdout.put( "Input a 128-bit decimal value: " );

    getu128( b1 );

    stdout.put

    (

        "The value is: $",

        b1[12], '_',

        b1[8],  '_',

        b1[4],  '_',

        b1[0],

        nl

    );

end Uin128;

Program 4.6	 Extended Precision Unsigned Decimal Input

As for hexadecimal input, extending this decimal input to some number of bits beyond 128 is fairly easy. All you need do is modify the code that zeros out the LocalValue variable and the code that multiplies LocalValue by ten (overflow checking is done in this same code, so there are only two spots in this code that require modification).

4.2.13.8 Extended Precision Signed Decimal Input

Once you have an unsigned decimal input routine, writing a signed decimal input routine is easy. The following algorithm describes how to accomplish this:

  • Consume any delimiter characters at the beginning of the input stream.
  • If the next input character is a minus sign, consume this character and set a flag noting that the number is negative.
  • Call the unsigned decimal input routine to convert the rest of the string to an integer.
  • Check the return result to make sure it's H.O. bit is clear. Raise the ex.ValueOutOfRange exception if the H.O. bit of the result is set.
  • If the sign flag was set in step two above, negate the result.

The actual code is left as a programming exercise at the end of this volume.

1Technically speaking, this isn't entirely true. It is possible for a device error (e.g., disk full) to occur. The likelihood of this is so low that we can effectively ignore this possibility.

2The HLA Standard Library routines actually buffer up input lines in a string and process characters out of the string. This makes it easy to "peek" ahead one character when looking for a delimiter to end the input value. Your code can also do this, however, the code in this chapter will use a different approach.


4.3 Operating on Different Sized Operands

Occasionally you may need to compute some value on a pair of operands that are not the same size. For example, you may need to add a word and a double word together or subtract a byte value from a word value. The solution is simple: just extend the smaller operand to the size of the larger operand and then do the operation on two similarly sized operands. For signed operands, you would sign extend the smaller operand to the same size as the larger operand; for unsigned values, you zero extend the smaller operand. This works for any operation, although the following examples demonstrate this for the addition operation.

To extend the smaller operand to the size of the larger operand, use a sign extension or zero extension operation (depending upon whether you're adding signed or unsigned values). Once you've extended the smaller value to the size of the larger, the addition can proceed. Consider the following code that adds a byte value to a word value:

static

    var1: byte;

    var2: word;

         .

         .

         .

// Unsigned addition:

    movzx( var1, ax );

    add( var2, ax );

// Signed addition:

movsx( var1, ax );

add( var2, ax );

In both cases, the byte variable was loaded into the AL register, extended to 16 bits, and then added to the word operand. This code works out really well if you can choose the order of the operations (e.g., adding the eight bit value to the sixteen bit value). Sometimes, you cannot specify the order of the operations. Perhaps the sixteen bit value is already in the AX register and you want to add an eight bit value to it. For unsigned addition, you could use the following code:

    mov( var2, ax );      // Load 16 bit value  into AX

         .                // Do some other operations leaving

         .                //  a 16-bit quantity in AX.

        add( var1, al );  // Add in the eight-bit value

        adc( 0, ah );     // Add carry into the H.O. word.

The first ADD instruction in this example adds the byte at var1 to the L.O. byte of the value in the accumulator. The ADC instruction above adds the carry out of the L.O. byte into the H.O. byte of the accumulator. Care must be taken to ensure that this ADC instruction is present. If you leave it out, you may not get the correct result.

Adding an eight bit signed operand to a sixteen bit signed value is a little more difficult. Unfortunately, you cannot add an immediate value (as above) to the H.O. word of AX. This is because the H.O. extension byte can be either $00 or $FF. If a register is available, the best thing to do is the following:

    mov( ax, bx );     // BX is the available register.

    movsx( var1, ax );

    add( bx, ax );

If an extra register is not available, you might try the following code:

        push( ax );          // Save word value.

        movsx( var1, ax );   // Sign extend 8-bit operand to 16 bits.

        add( [esp], ax );   // Add in previous word value

        add( 2, esp );      // Pop junk from stack

Another alternative is to store the 16 bit value in the accumulator into a memory location and then proceed as before:

    mov( ax, temp );

    movsx( var1, ax );

    add( temp, ax );

All the examples above added a byte value to a word value. By zero or sign extending the smaller operand to the size of the larger operand, you can easily add any two different sized variables together.

As a last example, consider adding an eight bit signed value to a quadword (64 bit) value:

static

    QVal:qword;

    BVal:int8;

     .

     .

     .

    movsx( BVal, eax );

    cdq();

    add( (type dword QVal), eax );

    adc( (type dword QVal[4]), edx );


4.4 Decimal Arithmetic

The 80x86 CPUs use the binary numbering system for their native internal representation. The binary numbering system is, by far, the most common numbering system in use in computer systems today. In days long since past, however, there were computer systems that were based on the decimal (base 10) numbering system rather than the binary numbering system. Consequently, their arithmetic system was decimal based rather than binary. Such computer systems were very popular in systems targeted for business/commercial systems1. Although systems designers have discovered that binary arithmetic is almost always better than decimal arithmetic for general calculations, the myth still persists that decimal arithmetic is better for money calculations than binary arithmetic. Therefore, many software systems still specify the use of decimal arithmetic in their calculations (not to mention that there is lots of legacy code out there whose algorithms are only stable if they use decimal arithmetic). Therefore, despite the fact that decimal arithmetic is generally inferior to binary arithmetic, the need for decimal arithmetic still persists.

Of course, the 80x86 is not a decimal computer; therefore we have to play tricks in order to represent decimal numbers using the native binary format. The most common technique, even employed by most so-called decimal computers, is to use the binary coded decimal, or BCD representation. The BCD representation (see "Nibbles" on page 56) uses four bits to represent the 10 possible decimal digits. The binary value of those four bits is equal to the corresponding decimal value in the range 0..9. Of course, with four bits we can actually represent 16 different values. The BCD format ignores the remaining six bit combinations.

Table 1:  Binary Code Decimal (BCD) Representation

BCD Representation

Decimal Equivalent

0000

0

0001

1

0010

2

0011

3

0100

4

0101

5

0110

6

0111

7

1000

8

1001

9

1010

Illegal

1011

Illegal

1100

Illegal

1101

Illegal

1110

Illegal

1111

Illegal

Since each BCD digit requires four bits, we can represent a two-digit BCD value with a single byte. This means that we can represent the decimal values in the range 0..99 using a single byte (versus 0..255 if we treat the value as an unsigned binary number). Clearly it takes a bit more memory to represent the same value in BCD as it does to represent the same value in binary. For example, with a 32-bit value you can represent BCD values in the range 0..99,999,999 (eight significant digits) but you can represent values in the range 0..4,294,967,295 (better than nine significant digits) using the binary representation.

Not only does the BCD format waste memory on a binary computer (since it uses more bits to represent a given integer value), but decimal arithmetic is slower. For these reasons, you should avoid the use of decimal arithmetic unless it is absolutely mandated for a given application.

Binary coded decimal representation does offer one big advantage over binary representation: it is fairly trivial to convert between the string representation of a decimal number and the BCD representation. This feature is particularly beneficial when working with fractional values since fixed and floating point binary representations cannot exactly represent many commonly used values between zero and one (e.g., 1/10). Therefore, BCD operations can be efficient when reading from a BCD device, doing a simple arithmetic operation (e.g., a single addition) and then writing the BCD value to some other device.

4.4.1 Literal BCD Constants

HLA does not provide, nor do you need, a special literal BCD constant. Since BCD is just a special form of hexadecimal notation that does not allow the values $A..$F, you can easily create BCD constants using HLA's hexadecimal notation. Of course, you must take care not to include the symbols 'A'..'F' in a BCD constant since they are illegal BCD values. As an example, consider the following MOV instruction that copies the BCD value '99' into the AL register:

    mov( $99, al );

The important thing to keep in mind is that you must not use HLA literal decimal constants for BCD values. That is, "mov( 95, al );" does not load the BCD representation for ninety-five into the AL register. Instead, it loads $5F into AL and that's an illegal BCD value. Any computations you attempt with illegal BCD values will produce garbage results. Always remember that, even though it seems counter-intuitive, you use hexadecimal literal constants to represent literal BCD values.

4.4.2 The 80x86 DAA and DAS Instructions

The integer unit on the 80x86 does not directly support BCD arithmetic. Instead, the 80x86 requires that you perform the computation using binary arithmetic and use some auxiliary instructions to convert the binary result to BCD. To support packed BCD addition and subtraction with two digits per byte, the 80x86 provides two instructions: decimal adjust after addition (DAA) and decimal adjust after subtraction (DAS). You would execute these two instructions immediately after an ADD/ADC or SUB/SBB instruction to correct the binary result in the AL register.

Two add a pair of two-digit (i.e., single-byte) BCD values together, you would use the following sequence:

    mov( bcd_1, al );    // Assume that bcd1 and bcd2 both contain

    add( bcd_2, al );    // value BCD values.

    daa();

The first two instructions above add the two byte values together using standard binary arithmetic. This may not produce a correct BCD result. For example, if bcd_1 contains $9 and bcd_2 contains $1, then the first two instructions above will produce the binary sum $A instead of the correct BCD result $10. The DAA instruction corrects this invalid result. It checks to see if there was a carry out of the low order BCD digit and adjusts the value (by adding six to it) if there was an overflow. After adjusting for overflow out of the L.O. digit, the DAA instruction repeats this process for the H.O. digit. DAA sets the carry flag if the was a (decimal) carry out of the H.O. digit of the operation.

The DAA instruction only operates on the AL register. It will not adjust (properly) for a decimal addition if you attempt to add a value to AX, EAX, or any other register. Specifically note that DAA limits you to adding two decimal digits (a single byte) at a time. This means that for the purposes of computing decimal sums, you have to treat the 80x86 as though it were an eight-bit processor, capable of adding only eight bits at a time. If you wish to add more than two digits together, you must treat this as a multiprecision operation. For example, to add four decimal digits together (using DAA), you must execute a sequence like the following:

    // Assume "bcd_1:byte[2];", "bcd_2:byte[2];", and "bcd_3:byte[2];"

    mov( bcd_1[0], al );

    add( bcd_2[0], al );

    daa();

    mov( al, bcd_3[0] );

    mov( bcd_1[1], al );

    adc( bcd_2[1], al );

    daa();

    mov( al, bcd_3[1], al );

// Carry is set at this point if there was unsigned overflow.

Since a binary addition of a word requires only three instructions, you can see why decimal arithmetic is so expensive2.

The DAS (decimal adjust after subtraction) adjusts the decimal result after a binary SUB or SBB instruction. You use it the same way you use the DAA instruction. Examples:

    // Two-digit (one byte) decimal subtraction:

    mov( bcd_1, al );    // Assume that bcd1 and bcd2 both contain

    sub( bcd_2, al );    // value BCD values.

    das();

    // Four-digit (two-byte) decimal subtraction.

    // Assume "bcd_1:byte[2];", "bcd_2:byte[2];", and "bcd_3:byte[2];"

    mov( bcd_1[0], al );

    sub( bcd_2[0], al );

    das();

    mov( al, bcd_3[0] );

    mov( bcd_1[1], al );

    sbb( bcd_2[1], al );

    das();

    mov( al, bcd_3[1], al );

// Carry is set at this point if there was unsigned overflow.

Unfortunately, the 80x86 only provides support for addition and subtraction of packed BCD values using the DAA and DAS instructions. It does not support multiplication, division, or any other arithmetic operations. Because decimal arithmetic using these instructions is so limited, you'll rarely see any programs use these instructions.

4.4.3 The 80x86 AAA, AAS, AAM, and AAD Instructions

In addition to the packed decimal instructions (DAA and DAS), the 80x86 CPUs support four unpacked decimal adjustment instructions. Unpacked decimal numbers store only one digit per eight-bit byte. As you can imagine, this data representation scheme wastes a considerable amount of memory. However, the unpacked decimal adjustment instructions support the multiplication and division operations, so they are marginally more useful.

The instruction mnemonics AAA, AAS, AAM, and AAD stand for "ASCII adjust for Addition, Subtraction, Multiplication, and Division" (respectively). Despite their name, these instructions do not process ASCII characters. Instead, they support an unpacked decimal value in AL whose L.O. four bits contain the decimal digit and the H.O. four bits contain zero. Note, though, that you can easily convert an ASCII decimal digit character to an unpacked decimal number by simply ANDing AL with the value $0F.

The AAA instruction adjusts the result of a binary addition of two unpacked decimal numbers. If the addition of those two values exceeds 10, then AAA will subtract 10 from AL and increment AH by one (as well as set the carry flag). AAA assumes that the two values you add together were legal unpacked decimal values. Other than the fact that AAA works with only one decimal digit at a time (rather than two), you use it the same way you use the DAA instruction. Of course, if you need to add together a string of decimal digits, using unpacked decimal arithmetic will require twice as many operations and, therefore, twice the execution time.

You use the AAS instruction the same way you use the DAS instruction except, of course, it operates on unpacked decimal values rather than packed decimal values. As for AAA, AAS will require twice the number of operations to add the same number of decimal digits as the DAS instruction. If you're wondering why anyone would want to use the AAA or AAS instructions, keep in mind that the unpacked format supports multiplication and division, while the packed format does not. Since packing and unpacking the data is usually more expensive than working on the data a digit at a time, the AAA and AAS instruction are more efficient if you have to work with unpacked data (because of the need for multiplication and division).

The AAM instruction modifies the result in the AX register to produce a correct unpacked decimal result after multiplying two unpacked decimal digits using the MUL instruction. Because the largest product you may obtain is 81 (9*9 produces the largest possible product of two single digit values), the result will fit in the AL register. AAM unpacks the binary result by dividing it by 10, leaving the quotient (H.O. digit) in AH and the remainder (L.O. digit) in AL. Note that AAM leaves the quotient and remainder in different registers than a standard eight-bit DIV operation.

Technically, you do not have to use the AAM instruction immediately after a multiply. AAM simply divides AL by ten and leaves the quotient and remainder in AH and AL (respectively). If you have need of this particular operation, you may use the AAM instruction for this purpose (indeed, that's about the only use for AAM in most programs these days).

If you need to multiply more than two unpacked decimal digits together using MUL and AAM, you will need to devise a multiprecision multiplication that uses the manual algorithm from earlier in this chapter. Since that is a lot of work, this section will not present that algorithm. If you need a multiprecision decimal multiplication, see the next section; it presents a better solution.

The AAD instruction, as you might expect, adjusts a value for unpacked decimal division. The unusual thing about this instruction is that you must execute it before a DIV operation. It assumes that AL contains the least significant digit of a two-digit value and AH contains the most significant digit of a two-digit unpacked decimal value. It converts these two numbers to binary so that a standard DIV instruction will produce the correct unpacked decimal result. Like AAM, this instruction is nearly useless for its intended purpose as extended precision operations (e.g., division of more than one or two digits) are extremely inefficient. However, this instruction is actually quite useful in its own right. It computes AX = AH*10+AL (assuming that AH and AL contain single digit decimal values). You can use this instruction to easily convert a two-character string containing the ASCII representation of a value in the range 0..99 to a binary value. E.g.,

    mov( '9', al );

    mov( '9', ah );    // "99" is in AH:AL.

    and( $0F0F, ax );  // Convert from ASCII to unpacked decimal.

    aad();             // After this, AX contains 99.

The decimal and ASCII adjust instructions provide an extremely poor implementation of decimal arithmetic. To better support decimal arithmetic on 80x86 systems, Intel incorporated decimal operations into the FPU. The next section discusses how to use the FPU for this purpose. However, even with FPU support, decimal arithmetic is inefficient and less precise than binary arithmetic. Therefore, you should carefully consider whether you really need to use decimal arithmetic before incorporating it into your programs.

4.4.4 Packed Decimal Arithmetic Using the FPU

To improve the performance of applications that rely on decimal arithmetic, Intel incorporated support for decimal arithmetic directly into the FPU. Unlike the packed and unpacked decimal formats of the previous sections, the FPU easily supports values with up to 18 decimal digits of precision, all at FPU speeds. Furthermore, all the arithmetic capabilities of the FPU (e.g., transcendental operations) are available in addition to addition, subtraction, multiplication, and division. Assuming you can live with only 18 digits of precision and a few other restrictions, decimal arithmetic on the FPU is the right way to go if you must use decimal arithmetic in your programs.

The first fact you must note when using the FPU is that it doesn't really support decimal arithmetic. Instead, the FPU provides two instruction, FBLD and FBSTP, that convert between packed decimal and binary floating point formats when moving data to and from the FPU. The FBLD (float/BCD load) instruction loads an 80-bit packed BCD value unto the top of the FPU stack after converting that BCD value to the IEEE binary floating point format. Likewise, the FBSTP (float/BCD store and pop) instruction pops the floating point value off the top of stack, converts it to a packed BCD value, and stores the BCD value into the destination memory location.

Once you load a packed BCD value into the FPU, it is no longer BCD. It's just a floating point value. This presents the first restriction on the use of the FPU as a decimal integer processor: calculations are done using binary arithmetic. If you have an algorithm that absolutely positively depends upon the use of decimal arithmetic, it may fail if you use the FPU to implement it3.

The second limitation is that the FPU supports only one BCD data type: a ten-byte 18-digit packed decimal value. It will not support smaller values nor will it support larger values. Since 18 digits is usually sufficient and memory is cheap, this isn't a big restriction.

A third consideration is that the conversion between packed BCD and the floating point format is not a cheap operation. The FBLD and FBSTP instructions can be quite slow (more than two orders of magnitude slower than FLD and FSTP, for example). Therefore, these instructions can be costly if you're doing simple additions or subtractions; the cost of conversion far outweighs the time spent adding the values a byte at a time using the DAA and DAS instructions (multiplication and division, however, are going to be faster on the FPU).

You may be wondering why the FPU's packed decimal format only supports 18 digits. After all, with ten bytes it should be possible to represent 20 BCD digits. As it turns out, the FPU's packed decimal format uses the first nine bytes to hold the packed BCD value in a standard packed decimal format (the first byte contains the two L.O. digits and the ninth byte holds the H.O. two digits). The H.O. bit of the tenth byte holds the sign bit and the FPU ignores the remaining bits in the tenth byte. If you're wondering why Intel didn't squeeze in one more digit (i.e., use the L.O. four bits of the tenth byte to allow for 19 digits of precision), just keep in mind that doing so would create some possible BCD values that the FPU could not exactly represent in the native floating point format. Hence the limitation to 18 digits.

The FPU uses a one's complement notation for negative BCD values. That is, the sign bit contains a one if the number is negative or zero and it contains a zero if the number is positive or zero (like the binary one's complement format, there are two distinct representations for zero).

HLA's tbyte type is the standard data type you would use to define packed BCD variables. The FBLD and FBSTP instructions require a tbyte operand. Unfortunately, the current version of HLA does not let you (directly) provide an initializer for a tbyte variable. One solution is to use the @NOSTORAGE option and initialize the data following the variable declaration. For example, consider the following code fragment:

static

    tbyteObject: tbyte; @nostorage

                    byte  $21, $43, $65, 0, 0, 0, 0, 0, 0, 0;

This tbyteObject declaration tells HLA that this is a tbyte object but does not explicitly set aside any space for the variable (see "The Static Sections" on page 167). The following BYTE directive sets aside ten bytes of storage and initializes these ten bytes with the value $654321 (remember that the 80x86 organizes data from the L.O. byte to the H.O. byte in memory). While this scheme is inelegant, it will get the job done. The chapters on Macros and the Compile-Time Language will discuss a better way to initialize tbyte and qword data.

Because the FPU converts packed decimal values to the internal floating point format, you can mix packed decimal, floating point, and (binary) integer formats in the same calculation. The following program demonstrate how you might achieve this:

program MixedArithmetic;

#include( "stdlib.hhf" )

static

    tb: tbyte; @nostorage;

        byte $21,$43,$65,0,0,0,0,0,0,0;

begin MixedArithmetic;

    fbld( tb );

    fmul( 2.0 );

    fiadd( 1 );

    fbstp( tb );

    stdout.put( "bcd value is " );

    stdout.puttb( tb );

    stdout.newln();

end MixedArithmetic;

Program 4.7	 Mixed Mode FPU Arithmetic

The FPU treats packed decimal values as integer values. Therefore, if your calculations produce fractional results, the FBSTP instruction will round the result according to the current FPU rounding mode. If you need to work with fractional values, you need to stick with floating point results.

1In fact, until the release of the IBM 360 in the middle 1960's, most scientific computer systems were binary based while most commercial/business systems were decimal based. IBM pushed their system\360 as a single purpose solution for both business and scientific applications. Indeed, the model designation (360) was derived from the 360 degrees on a compass so as to suggest that the system\360 was suitable for computations "at all points of the compass" (i.e., business and scientific).

2You'll also soon see that it's rare to find decimal arithmetic done this way. So it hardly matters.

3An example of such an algorithm might by a multiplication by ten by shifting the number one digit to the left. However, such operations are not possible within the FPU itself, so algorithms that misbehave inside the FPU are actually quite rare.


4.5 Sample Program

The following sample program demonstrates BCD I/O. The following program provides two procedures, BCDin and BCDout. These two procedures read an 18-digit BCD value from the user (with possible leading minus sign) and write a BCD value to the standard output device.

program bcdIO;

#include( "stdlib.hhf" )

// The following is equivalent to TBYTE except it

// lets us easily gain access to the individual

// components of a BCD value.

type

    bcd:record

            LO8:    dword;

            MID8:   dword;

            HO2:    byte;

            Sign:   byte;

        endrecord;

// BCDin-

//

// This function reads a BCD value from the standard input

// device.  The number can be up to 18 decimal digits long

// and may contain a leading minus sign.

//

// This procedure stores the BCD value in the variable passed

// by reference as a parameter to this routine.

procedure BCDin( var input:tbyte ); @nodisplay;

var

    bcdVal:     bcd;

    delimiters: cset;

begin BCDin;

    push( eax );

    push( ebx );

    // Get a copy of the input delimiter characters and

    // make sure that #0 is a member of this set.

    conv.getDelimiters( delimiters );

    cs.unionChar( #0, delimiters );

    // Skip over any leading delimiter characters in the text:

    while( stdin.peekc() in delimiters ) do

        // If we're at the end of an input line, read a new

        // line of text from the user, otherwise remove the

        // delimiter character from the input stream.

        if( stdin.peekc() = #0 ) then

            stdin.readLn(); // Get a new line of input text.

        else

            stdin.getc();   // Remove the delimeter.

        endif;

    endwhile;

    // Initialize our input accumulator to zero:

    xor( eax, eax );

    mov( eax, bcdVal.LO8 );

    mov( eax, bcdVal.MID8 );

    mov( al, bcdVal.HO2 );

    mov( al, bcdVal.Sign );

    // If the first character is a minus sign, then eat it and

    // set the sign bit to one.

    if( stdin.peekc() = '-' ) then

        stdin.getc();             // Eat the sign character.

        mov( $80, bcdVal.Sign );  // Make this number negative.

    endif;

    // We must have at least one decimal digit in this number:

    if( stdin.peekc() not in '0'..'9' ) then

        raise( ex.ConversionError );

    endif;

    // Okay, read in up to 18 decimal digits:

    while( stdin.peekc() in '0'..'9' ) do

        stdin.getc();   // Read this decimal digit.

        shl( 4, al );   // Move digit to H.O. bits of AL

        mov( 4, ebx );

        repeat

            // Cheesy way to SHL bcdVal by four bits and

            // merge in the new character.

            shl( 1, al );

            rcl( 1, bcdVal.LO8 );

            rcl( 1, bcdVal.MID8 );

            rcl( 1, bcdVal.HO2 );

            // If the user has entered more than 18

            // decimal digits, the carry will be set

            // after the RCL above.  Test that here.

            if( @c ) then

                raise( ex.ValueOutOfRange );

            endif;

            dec( ebx );

        until( @z );

    endwhile;

    // Be sure that the number ends with a proper delimiter:

    if( stdin.peekc() not in delimiters ) then

        raise( ex.ConversionError );

    endif;

    // Okay, store the ten-byte input result into

    // the location specified by the parameter.

    mov( input, ebx );

    mov( bcdVal.LO8, eax );

    mov( eax, [ebx] );

    mov( bcdVal.MID8, eax );

    mov( eax, [ebx+4] );

    mov( (type word bcdVal.HO2), ax ); // Grabs "Sign" too.

    mov( ax, [ebx+8] );

    pop( ebx );

    pop( eax );

end BCDin;

// BCDout-

//

// The converse of the above.  Prints the string representation

// of the packed BCD value to the standard output device.

procedure BCDout( output:tbyte ); @nodisplay;

var

    q:qword;

begin BCDout;

    // This code cheats *big time*.

    // It converts the BCD value to a 64-bit integer

    // and then calls the stdout.puti64 routine to

    // actually print the number.  In theory, this is

    // a whole lot slower than converting the BCD value

    // to ASCII and printing the ASCII chars, however,

    // I/O is so much slower than the conversion that

    // no one will notice the extra time.

    fbld( output );

    fistp( q );

    stdout.puti64( q );

end BCDout;

static

    tb1:    tbyte;

    tb2:    tbyte;

    tbRslt: tbyte;

begin bcdIO;

    stdout.put( "Enter a BCD value: " );

    BCDin( tb1 );

    stdout.put( "Enter a second BCD value: " );

    BCDin( tb2 );

    fbld( tb1 );

    fbld( tb2 );

    fadd();

    fbstp( tbRslt );

    stdout.put( "The sum of " );

    BCDout( tb1 );

    stdout.put( " + " );

    BCDout( tb2 );

    stdout.put( " is " );

    BCDout( tbRslt );

    stdout.newln();

end bcdIO;

Program 4.8	 BCD I/O Sample Program

4.6 Putting It All Together

Extended precision arithmetic is one of those activities where assembly language truly shines. It's much easier to perform extended precision arithmetic in assembly language than in most high level languages; it's far more efficient to do it in assembly language, as well. Extended precision arithmetic was, perhaps, the most important subject that this chapter teaches.

Although extended precision arithmetic and logical calculations are important, what good are extended precision calculations if you can't get the extend precision values in and out of the machine? Therefore, this chapter devotes a fair amount of space to describing how to write your own extended precision I/O routines. Between the calculations and the I/O this chapter describes, you're set to perform those really hairy calculations you've always dreamed of!

Although decimal arithmetic is nowhere near as prominent as it once was, the need for decimal arithmetic does arise on occasion. Therefore, this chapter spends some time discussing BCD arithmetic on the 80x86.


µDico MASM Flottant 4.4

                        µDico > MASM > Flottant 4.4

Préambule:

Pour réaliser le présent document, j'ai utilisé les documentations suivantes:

- Manuel de référence de MASM de IBM version 2.0

- Manuel de référence de Microsoft version ML 6.15

- Manuel de programmation de Microsoft ML 6.15.

- Manuel iA-32 Intel Architecture - Volume 1- Basic Architecture.

- Manuel iA-32 Intel Architecture - Volume 2A- Instructions Set Reference A-M.

- Manuel iA-32 Intel Architecture - Volume 2B- Instructions Set Reference N-Z.

- Manuel iA-32 Intel Architecture - Volume 3- System Programming Guide.

Ce document contient:

- la description du FPU, de son fonctionnement et de sa programmation.

- la liste des instructions classées regroupées par thèmes.

- la liste des instructions classées par ordre alphabétiques.

Je n'ai pas non plus traité des instructions  MMX, SSE, SSE2 et SSE3  qui travaillent sur des paquets de données (SIMD).
J'ai laissé des phrases en anglais non traduites en français qui seront peut être traduites dans une prochaine version.
Surtout si je reçois de l'aide dans ce sens soit par des courriels ou des messages sur le forum assembleur de MOTEURPROG.COM.

Bonne utilisation,                                                                                  Le 1er avril 2004

Yonnel BALEZ (alias moby)  Courriel moby de FAI cario.fr Adresse postale: RHB-INPG /90 Av.Jules Vallès/38100 GRENOBLE


µDico MASM Flottant 4.4

1. Présentation matérielle:

Description du FPU, de son fonctionnement et de sa programmation.

Depuis le 8087 jusqu'au 387 inclus, cette unité de calcul est appelé coproce
sseur ou FPU (Floating Point Unit) et à partir du 486 elle est intégrée dans
la même puce que l'UC, . Elle a un fonctionnement distinct de l'UC.
Les liaisons se faisant presque exclusivement par l'intermédiaire de la mémoire.
On peut considérer qu'elle constitue un second ordinateur distinct du précédent.

Le fonctionnement de la FPU est indépendant de celui de la CPU.

Cependant, les instructions sur les nombres réels et les instructions SIMD o
nt des registres et des instructions en commun. Lorsqu'on utilise ensemble les
instruction du flottant et celles de SIMD, il faut gérer explicitement les états de chacun.

FPU permet l'exécution très rapide des opérations sur les nombres réels.

FPU travaille en parallèle avec le processeur. Aussi, avec une structure de
programmation appropriée, il y a un accroissement de la vitesse d'exécution
des programmes.

Les mnémoniques des instructions flottantes sont précédées de la lettre F
(Exemple FADD ou FSUB).

Les 5 premiers bits de l'opcode sont 11011

La longueur des instructions flottantes est de deux octets pour celles qui n
'accèdent pas à la mémoire.

Pour celles qui accèdent à la mémoire, la taille est de 4 octets sur le 8087  et 287.

Sur le 387 et le 486, leur taille est de 4 octets en programmation 16 bits et
de 6 octets en programmation 32 bits..


µDico MASM Flottant 4.4

2. La représentation des nombres en mémoire

2.1. Les nombres entiers en mémoire sont représentés sous les formes suivantes:

SBYTE	1 octet	           -128 à +127

SWORD	2 octets   	 -32768 à +32767

SDWORD	4 octets 	 -2**31 à +2**31-1

FWORD	6 octets	 -2**47 à +2**47-1

TENBYTE	10 octets        -2**63 à +2**63-1

QWORD	16 octets 	-2**127 à +2**127-1

2.2.Les nombres réels, sont représentés en mémoire en quatre champs de bits:

1) le bit de signe du nombre, 0 si positif, 1 si négatif.

2) l'exposant qui est un nombre entier toujours positif car décalé par ajout d'une constante.

3) uniquement pour REAL10 en format interne: un bit indiquant si nombre entier ou réel.

4) la partie fractionnaire ou mantisse.

Avec les versions antérieure à 6.0, DD, DQ, DT permettaient aussi de déclarer
des nombres réels. A partir de 6.1, il faut utiliser les directives REAL4|8|10.

Les nombres réels peuvent être déclarés sous trois formes:

{+|-}  {nombre entier} [  . {nombre entier} ]  E { +|- } { nombre entier}

{+|-}  {nombre entier} . {nombre entier}

{nombre hexadécimal } r

L'assembleur évalue les chiffres des nombres réels comme étant:

- soit des chiffres décimaux.
Le nombre maximum de chiffres dépend de l'attribut:  	REAL4	8	REAL8	16	REAL10	20

Si la mantisse commence par un zéro, ce nombre maximum est majoré de 1.

- soit un nombre hexadécimal suivi de la lettre R, dans ce cas, le nombre doit être codé dans la forme
mémoire des nombre réels, sans signe, ni séparati on de la mantisse et de l'exposant calculé en décalé. (biased)

Exemples:

simple	REAL4	25.233

double	REAL8	2.523E1

etendu	REAL10	2864325.2894E-6

hexa4	REAL4	3F800000r       	; 1.0

hexa8	REAL8	3FF0000000000000r	; 1.0

hexa10	REAL10	3FFF8000000000000000r	; 1.0

Les nombres Réels à virgule flottante sont traités en mémoire suivant la représentation ci dessous.
Ce mode permet de gérer des nombres très grands dont seuls les premiers chiffres sont significatifs.
Converti en décimal, le nombre de chiffres significatif est donné ci dessous.

    taille	 signe	 exposant      mantisse	décimal significatif

REAL4	32 bits	 1 bit	  8 bits       23 bits	 6 à 7

REAL8 	64 bits	 1 bit	 11 bits       52 bits	16 à 18

REAL10	80 bits	 1 bit	 16 bits       63 bits	19 à 20

Les limites de ces nombres sont:

           en binaire	                en  décimal

REAL4	    2**-126 à 2**+127         	 1.18*10**-38 à 3.40*10**+38

REAL8	   2**-1022 à 2**+1023      	2.23*10**-308 à 1,79*10**+308

REAL10    2**-16382 à 2**+16383        3.37*10**-4932 à 1,18*10**+4932

Nombres réels normalisés ou non normalisés:

La mantisse est de la forme 1,bb.....bb

Un nombre réel non normalisé sera de la forme  0,0...01bb.....bb

Pour rendre le nombre normalisé, il suffira de décaler la virgule jusqu'après
le 1er chiffre 1 rencontré, et décrémenter l'exposant du même nombre de décalages.

L'intérêt de la forme normalisée, est de maximiser le nombre de chiffres
significatifs pour un même nombre de bits de mantisse disponibles.

On en déduit que:

- la mantisse d'un nombre normalisé est comprise entre 1d et 2d, puisqu'elle commence toujours par 1 suivi d'une suite quelconque de 0 ou de 1.

- la mantisse d'un nombre non normalisé est comprise entre 0d et 1d, puisque avant la virgule il y a le chiffre 0 et après une suite quelconque de chiffres 0 ou 1 .

Les exposants décalés (biased):

Les exposants, des nombres normalisés ou non, sont décalés d'une valeur fixe de telle manière qu'ils soient représentés par un nombre toujours positif.

Les décalages sont pour: REAL4: 127   REAL8: 1023    REAL10: 16383

Pour les calculs, la FPU effectue la correction nécessaire.

Les représentations des nombres réels sont décrits dans le tableau ci-dessous.

Il y a deux zéros dans cette représentation: un zéro positif et un zéro négatif..
Ces deux valeurs sont identiques.
Le signe dépend du mode de calcul qui a été conduit pour parvenir à cette valeur.
Il indique aussi la manière dont le débordement (overflow ) a été traité

                              signe	exposant	mantisse

infini négatif	                1	11.....11	00.....00

nbre. normalisés négatifs	1	00.....01   à 	00.....00   à    1	11.....10	11.....11

nbre. non normalisés négatifs	1	00.....00   à	00.....01   à    1	00.....00	11.....11

zéro négatif	      	        1	00.....00	00.....00

zéro positif	       		0	00.....00	00.....00

nbre. non normalisés positifs	0	00.....00   à	11.....11   à 	 0	00.....00	00.....01

nbre. normalisés positifs	0	11.....10   à 	11.....11   à    0	00.....01	00.....00

infini positif	                0	11.....11	00.....00

Les autres représentations dites NaN (Not a Number)

SNaN (Signaling NaN)	b	11.....11  	1	0b.....bb (!= 0)

QNaN (Quiet NaN)	b	11.....11  	1	1b.....bb

NaN invalides	        1	11.....11	1	10.....00

Les NaN ne sont pas reconnus comme des nombres par la FPU.
Ils ont des valeurs extérieures au segment [-infini à +infini].

Il y a trois sortes de NaN:

A) Les QNaN (Quiet NaN). C'est un NaN dont le bit de poids fort de la mantisse est égal à 1 et dont l'exposant a tous les bits à 1.
   Ces nombres sont utilisés pour dérouler un calcul sans signification, mais qui n'entraînera pas une rupture.

B) les SNaN (Signaling NaN). C'est un Nan dont le bit de poids fort de la mantisse est égal à zéro,
   mais dont les reste de la mantisse est différent de zéro.
   Au contraire de QNaN, les SNan entrainent un rupture, sauf si la rupture est masquée (voir le registre CW).

C) les NaN invalides qui ne sont ni des SNaN, ni des QNaN, ni les infinis,ni les zéros, ni les nombres réels.


MASM_PASCAL

3. Les instructions du FPU

The Pascal language type does not cause names to be mapped to uppercase.

F2XM1 Instruction
----------------------------------------------------------------------------

  Title:    Calculate 2(^X)-1

  Syntax:   F2XM1

  See also: FYL2X, FYL2XP1

  Description:

     Calculates Y = 2(^X) - 1 where X is taken from ST. The result, Y,
     is returned in ST. X must be in the range 0 <= X <= 0.5 on the
     8087/287, or in the range -1.0 <= X <= +1.0 on the 80387/486.

-----------------+------------------------+---------------------------------
 F2XM1           | f2xm1                  |  87 310-630
                 |                        | 287 310-630
                 |                        | 387 211-476
                 |                        | 486 140-279
-----------------+------------------------+---------------------------------

FABS Instruction
----------------------------------------------------------------------------

  Title:    Absolute Value

  Syntax:   FABS

  See also: FCHS, FRNDINT

  Description:

     Converts the element in ST to its absolute value.

-----------------+------------------------+---------------------------------
 FABS            | fabs                   |  87 10-17
                 |                        | 287 10-17
                 |                        | 387 22
                 |                        | 486 3
-----------------+------------------------+---------------------------------

FADD Instruction
----------------------------------------------------------------------------

  Title:    Add

  Syntax:   FADD [dest,src]
            FADDP dest,ST
            FADD src
            FIADD src

  See also: FSUB, FSUBR

  Description:

     Adds the source to the destination and returns the sum in the
     destination. If two register operands are specified, one must be
     ST. If a memory operand is specified, the sum replaces the value
     in ST. Memory operands can be 32- or 64-bit real numbers or 16-
     or 32-bit integers.

     If no operand is specified, ST is added to ST(1) and the stack is
     popped; the sum is returned in ST. For FADDP, the source must be
     ST; the sum is returned in the destination and ST is popped.

----------------+-------------------------+---------------------------------
 FADD [reg,reg] | fadd   st,st(2)         |  87 70-100
                | fadd   st(5),st         | 287 70-100
                | fadd                    | 387 to=23-31,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FADDP reg,ST   | faddp  st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 23-31
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FADD memreal   | fadd   QWORD PTR [bx]   |  87 (s=90-120,s=95-125)+EA
                | fadd   shortreal        | 287 s=90-120,l=95-125
                |                         | 387 s=24-32,l=29-37
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FIADD memint   | fiadd  int16            |  87 (w=102-137,d=108-143)+EA
                | fiadd  warray[di]       | 287 w=102-137,d=108-143
                | fiadd  double           | 387 w=71-85,d=57-72
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------

FCHS Instruction

----------------------------------------------------------------------------

  Title:    Change Sign

  Syntax:   FCHS

  See also: FABS

  Description:

     Reverses the sign of the value in ST.

-----------------+------------------------+---------------------------------
 FCHS            | fchs                   |  87 10-17
                 |                        | 287 10-17
                 |                        | 387 24-25
                 |                        | 486 6
-----------------+------------------------+---------------------------------

FCLEX Instruction

----------------------------------------------------------------------------

  Title:    Clear Exceptions

  Syntax:   FCLEX
            FNCLEX

  See also: Status Word

  Description:

     Clears all exception flags, the busy flag, and bit 7 in the status
     word. Bit 7 is the interrupt-request flag on the 8087 and the
     error-status flag on the 80287, 80387, and 80486.

     This instruction has wait and no-wait versions. The wait version
     (FCLEX) checks for unmasked numeric errors. The no-wait version
     (FNCLEX) does not. When the .8087 directive is used, the assembler
     puts the WAIT instruction before the wait versions and the NOP
     instruction before the no-wait versions.

-----------------+------------------------+---------------------------------
 FCLEX           | fclex                  |  87 2-8
 FNCLEX          |                        | 287 2-8
                 |                        | 387 11
                 |                        | 486 7
-----------------+------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.

FCOM Instruction
----------------------------------------------------------------------------

  Title:    Compare

  Syntax:   FCOM [src]
            FCOMP [src]
            FCOMPP
            FICOM src
            FICOMP src

  See also: FTST, FUCOM, FSTSW, SAHF, FXAM, Status Word

  Description:

     Compares the specified source operand to ST and sets the condition
     codes of the status word according to the result. The instruction
     subtracts the source operand from ST without changing either
     operand. Memory operands can be 32- or 64-bit real numbers or 16-
     or 32-bit integers.

     If no operand is specified or if two pops are specified, ST is
     compared to ST(1) and the stack is popped. If one pop is specified
     with an operand, the operand is compared to ST. If one of the
     operands is a NAN, an invalid-operation exception occurs.
     See: FUCOM

     The condition codes for FCOM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST > source
     0      0      ?      1      ST < source
     1      0      ?      0      ST = source
     1      1      ?      1      ST not comparable to source

----------------+-------------------------+---------------------------------
 FCOM [reg]     | fcom   st(2)            |  87 40-50
                | fcom                    | 287 40-50
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMP [reg]    | fcomp  st(7)            |  87 42-52
                | fcomp                   | 287 42-52
                |                         | 387 26
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMPP         | fcompp                  |  87 45-55
                |                         | 287 45-55
                |                         | 387 26
                |                         | 486 5
----------------+-------------------------+---------------------------------
 FCOM memreal   | fcom   shortreals[di]   |  87 (s=60-70,l=65-75)+EA
                | fcom   longreal         | 287 s=60-70,l=65-75
                |                         | 387 s=26,l=31
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FCOMP memreal  | fcomp  longreal         |  87 (s=63-73,l=67-77)+EA
                | fcomp  shorts[di]       | 287 s=63-73,l=67-77
                |                         | 387 s=26,l=31
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FICOM memint   | ficom  double           |  87 (w=72-86,d=78-91)+EA
                | ficom  warray[di]       | 287 w=72-86,d=78-91
                |                         | 387 w=71-75,d=56-63
                |                         | 486 w=16-20,d=15-17
----------------+-------------------------+---------------------------------
 FICOMP memint  | ficomp WORD PTR [bp+6]  |  87 (w=74-88,d=80-93)+EA
                | ficomp darray[di]       | 287 w=74-88,d=80-93
                |                         | 387 w=71-75,d=56-63
                |                         | 486 w=16-20,d=15-17
----------------+-------------------------+---------------------------------

FSIN/FCOS/FSINCOS Instructions

----------------------------------------------------------------------------

  Title:    Sine/Cosine
            80387/486 Only

  Syntax:   FSIN
            FCOS
            FSINCOS

  See also: .387, .486, FPREM, FPREM1, FPTAN

  Description:

     FSIN replaces a value in radians in ST with its sine. FCOS
     replaces a value in radians in ST with its cosine. FSINCOS
     calculates the sine and cosine of a radian value in ST. The sine
     replaces the value in ST, and then the cosine is pushed onto the
     stack.

     If |ST| < 2^63, the C2 bit of the status word is cleared and the
     sine and/or cosine is calculated. Otherwise, C2 is set and no
     calculation is performed. ST can be reduced to the required range
     with FPREM or FPREM1.

----------------+-------------------------+---------------------------------
 FSIN           | fsin                    |  87 --
                |                         | 287 --
                |                         | 387 122-771*
                |                         | 486 257-354œ
----------------+-------------------------+---------------------------------
 FCOS           | fcos                    |  87 --
                |                         | 287 --
                |                         | 387 123-772*
                |                         | 486 257-354œ
----------------+-------------------------+---------------------------------
 FSINCOS        | fsincos                 |  87 --
                |                         | 287 --
                |                         | 387 194-809*
                |                         | 486 292-365œ
----------------+-------------------------+---------------------------------
  * For operands with an absolute value greater than pi/4, up to 76
    additional clocks may be required.

  œ For operands with an absolute value greater than pi/4, add n clocks
    where n = operand / (pi/4).

FINCSTP/FDECSTP Instructions

----------------------------------------------------------------------------

  Title:    Increment or Decrement Stack Pointer

  Syntax:   FINCSTP
            FDECSTP

  Description:

     Increments or decrements the stack-top pointer in the status word.
     No tags or registers are changed, and no data is transferred. If
     the stack pointer is 7, FINCSTP changes it to 0. If the stack
     pointer is 0, FDECSTP changes it to 7.

----------------+-------------------------+---------------------------------
 FINCSTP        | fincstp                 |  87 6-12
                |                         | 287 6-12
                |                         | 387 21
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FDECSTP        | fdecstp                 |  87 6-12
                |                         | 287 6-12
                |                         | 387 22
                |                         | 486 3
----------------+-------------------------+---------------------------------

FDISI/FENI Instructions

----------------------------------------------------------------------------

  Title:    Enable or Disable Interrupts
            8087 Only

  Syntax:   FDISI
            FNDISI
            FENI
            FNENI

  See also: .8087

  Description:

     FDISI disables interrupts by setting the interrupt-enable mask in
     the control word; FENI enables them by clearing the interrupt-
     enable mask. Since the 80287, 80387, and 80486 do not have an
     interrupt-enable mask, FDISI and FENI are recognized but ignored
     on these coprocessors.

     These instructions have wait and no-wait versions. The wait
     versions (FDISI and FENI) check for unmasked numeric errors; the
     no-wait versions (FNDISI and FNENI) do not. When the .8087
     directive is used, the assembler puts the WAIT instruction before
     the wait versions and the NOP instruction before the no-wait
     versions.

----------------+-------------------------+---------------------------------
 FDISI          | fdisi                   |  87 2-8
 FNDISI         |                         | 287 2
                |                         | 387 2
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FENI           | feni                    |  87 2-8
 FNENI          |                         | 287 2
                |                         | 387 2
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instructions.
        The wait versions may take additional clock cycles.

FDIV Instructions

----------------------------------------------------------------------------

  Title:    Divide

  Syntax:   FDIV [dest,src]
            FDIVP dest,ST
            FDIV src
            FIDIV src

  See also: FDIVR, FMUL

  Description:

     Divides the destination by the source and returns the quotient in
     the destination. If two register operands are specified, one must
     be ST. If a memory operand is specified, the quotient replaces the
     value in ST. Memory operands can be 32- or 64-bit real numbers or
     16- or 32-bit integers.

     If no operand is specified, ST(1) is divided by ST and the stack
     is popped; the result is returned in ST. For FDIVP, the source
     must be ST; the quotient is returned in the destination register
     and ST is popped.

----------------+-------------------------+---------------------------------
 FDIV [reg,reg] | fdiv   st,st(2)         |  87 193-203
                | fdiv   st(5),st         | 287 193-203
                | fdiv                    | 387 to=88,fr=91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVP reg,ST   | fdivp  st(6),st         |  87 197-207
                |                         | 287 197-207
                |                         | 387 91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIV memreal   | fdiv   DWORD PTR [bx]   |  87 (s=215-225,l=220-230)+EA
                | fdiv   shortreal[di]    | 287 s=215-225,l=220-230
                | fdiv   longreal         | 387 s=89,l=94
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FIDIV memint   | fidiv  int16            |  87 (w=224-238,d=230-243)+EA
                | fidiv  warray[di]       | 287 w=224-238,d=230-243
                | fidiv  double           | 387 w=136-140,d=120-127
                |                         | 486 w=85-89,d=84-86
----------------+-------------------------+---------------------------------

FDIVR Instructions

----------------------------------------------------------------------------

  Title:    Divide Reversed

  Syntax:   FDIVR [dest,src]
            FDIVRP dest,ST
            FDIVR src
            FIDIVR src

  See also: FDIV, FMUL

  Description:

     Divides the source by the destination and returns the quotient in
     the destination. If two register operands are specified, one must
     be ST. If a memory operand is specified, the quotient replaces the
     value in ST. Memory operands can be 32- or 64-bit real numbers or
     16- or 32-bit integers.

     If no operand is specified, ST is divided by ST(1) and the stack
     is popped; the result is returned in ST. For FDIVRP, the source
     must be ST; the quotient is returned in the destination register
     and ST is popped.

----------------+-------------------------+---------------------------------
 FDIVR [reg,reg]| fdivr  st,st(2)         |  87 194-204
                | fdivr  st(5),st         | 287 194-204
                | fdivr                   | 387 to=88,fr=91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVRP reg,ST  | fdivrp st(6),st         |  87 198-208
                |                         | 287 198-208
                |                         | 387 91
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FDIVR memreal  | fdivr  longreal         |  87 (s=216-226,l=221-231)+EA
                | fdivr  shortreal[di]    | 287 s=216-226,l=221-231
                |                         | 387 s=89,l=94
                |                         | 486 73
----------------+-------------------------+---------------------------------
 FIDIVR memint  | fidivr double           |  87 (w=225-239,d=231-245)+EA
                | fidivr warray[di]       | 287 w=225-239,d=231-245
                |                         | 387 w=135-141,d=121-128
                |                         | 486 w=85-89,d=84-86
----------------+-------------------------+---------------------------------

FFREE Instruction
----------------------------------------------------------------------------

  Title:    Free Register

  Syntax:   FFREE dest

  See also: Coprocessor Environment

  Description:

     Changes the specified register's tag to empty without changing the
     contents of the register.

----------------+-------------------------+---------------------------------
 FFREE ST(i)    | ffree  st(3)            |  87 9-16
                |                         | 287 9-16
                |                         | 387 18
                |                         | 486 3
----------------+-------------------------+---------------------------------

FINIT Instruction

----------------------------------------------------------------------------

  Title:    Initialize Coprocessor

  Syntax:    FINIT
             FNINIT

  Description:

     Initializes the coprocessor and resets all the registers and flags
     to their default values. On the 80387/486, the condition codes of
     the status word are cleared. On the 8087/287, they are unchanged.

     This instruction has wait and no-wait versions. The wait version
     (FINIT) checks for unmasked numeric errors; the no-wait version
     (FNINIT) does not. When the .8087 directive is used, the assembler
     puts the WAIT instruction before the wait versions and the NOP
     instruction before the no-wait versions.

----------------+-------------------------+---------------------------------
 FINIT          | finit                   |  87 2-8
 FNINIT         |                         | 287 2-8
                |                         | 387 33
                |                         | 486 17
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.

FLD Instruction
----------------------------------------------------------------------------

  Title:    Load

  Syntax:   FLD src
            FILD src
            FBLD src

  See also: FLDconst, FST, FXCH

  Description:

     Pushes the specified operand onto the stack. All memory operands
     are automatically converted to temporary-real numbers before being
     loaded.

     Memory operands can be 32-, 64-, or 80-bit real numbers or 16-,
     32-, or 64-bit integers.

--------------+------------------------+-----------------------------------
 FLD reg      | fld   st(3)            |  87 17-22
              |                        | 287 17-22
              |                        | 387 14
              |                        | 486  4
--------------+------------------------+-----------------------------------
 FLD memreal  | fld   longreal         |  87 (s=38-56,l=40-60,t=53-65)+EA
              | fld   shortarray[bx+di]| 287 s=38-56,l=40-60,t=53-65
              | fld   tempreal         | 387 s=20,l=25,t=44
              |                        | 486 s=3,l=3,t=6
--------------+------------------------+-----------------------------------
 FILD memint  | fild  mem16            |  87 (w=46-54,d=52-60,q=60-68)+EA
              | fild  DWORD PTR [bx]   | 287 w=46-54,d=52-60,q=60-68
              | fild  quads[si]        | 387 w=61-65,d=45-52,q=56-67
              |                        | 486 w=13-16,d=9-12,q=10-18
--------------+------------------------+-----------------------------------
 FBLD membcd  | fbld  packbcd          |  87 (290-310)+EA
              |                        | 287 290-310
              |                        | 387 266-275
              |                        | 486 70-103
--------------+------------------------+-----------------------------------

FLDconst Instruction
----------------------------------------------------------------------------

  Title:    Load Constant

  Syntax:   FLDconst

  See also: FLD

  Description:

     Pushes a constant onto the stack. The following constants can be
     loaded:

     Instruction     Constant

     FLD1            +1.0

     FLDZ            +0.0

     FLDPI           pi

     FLDL2E          Log (e)
                        2

     FLDL2T          Log (10)
                        2

     FLDLG2          Log  (2)
                        10

     FLDLN2          Log (2)
                        e
----------------+-------------------------+---------------------------------
 FLD1           | fld1                    |  87 15-21
                |                         | 287 15-21
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FLDZ           | fldz                    |  87 11-17
                |                         | 287 11-17
                |                         | 387 20
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FLDPI          | fldpi                   |  87 16-22
                |                         | 287 16-22
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDL2E         | fldl2e                  |  87 15-21
                |                         | 287 15-21
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDL2T         | fldl2t                  |  87 16-22
                |                         | 287 16-22
                |                         | 387 40
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDLG2         | fldlg2                  |  87 18-24
                |                         | 287 18-24
                |                         | 387 41
                |                         | 486 8
----------------+-------------------------+---------------------------------
 FLDLN2         | fldln2                  |  87 17-23
                |                         | 287 17-23
                |                         | 387 41
                |                         | 486 8
----------------+-------------------------+---------------------------------

FLDCW/FSTCW Instructions

----------------------------------------------------------------------------

  Title:    Load or Store Control Word

  Syntax:   FLDCW src
            FSTCW dest
            FNSTCW dest

  See also: FSTSW, FSTENV, FSAVE, Control Word

  Description:

     FLDCW loads the specified word into the coprocessor control word.
     FSTCW stores the control word to a specified 16-bit memory
     operand.

     FSTCW has wait and no-wait versions. The wait version (FSTCW)
     checks for unmasked numeric errors; the no-wait version (FNSTCW)
     does not. When the .8087 directive is used, the assembler puts the
     WAIT instruction before the wait version and the NOP instruction
     before the no-wait version.

----------------+-------------------------+---------------------------------
 FLDCW mem16    | fldcw  ctrlword         |  87 (7-14)+EA
                |                         | 287 7-14
                |                         | 387 19
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FSTCW mem16    | fstcw  ctrlword         |  87 12-18
 FNSTCW mem16   |                         | 287 12-18
                |                         | 387 15
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the FSTCW. The wait
        version may take additional clock cycles.

FSTENV/FLDENV Instructions
----------------------------------------------------------------------------

  Title:    Load or Store Environment State

  Syntax:   FSTENV dest
            FNSTENV dest
            FLDENV mem

  See also: FSAVE, FSTCW, FSTSW, Coprocessor Environment

  Description:

     FSTENV stores the 14-byte coprocessor environment state to a
     specified memory location. FLDENV loads it from a specified memory
     location.

     The environment state includes the control word, status word, tag
     word, instruction pointer, and operand pointer. On the 80387/486
     in 32-bit mode, the environment state is 28 bytes.

     The FSTENV instruction has wait and no-wait versions. The wait
     version (FSTENV) checks for unmasked numeric errors; the no-wait
     version (FNSTENV) does not. When the .8087 directive is used, the
     assembler puts the WAIT instruction before the wait version and
     the NOP instruction before the no-wait version.

----------------+-------------------------+---------------------------------
 FLDENV mem     | fldenv [bp+10]          |  87 (35-45)+EA
 FLDENVW mem*   |                         | 287 35-45
 FLDENVD mem*   |                         | 387 71
                |                         | 486 44,pm=34
----------------+-------------------------+---------------------------------
 FSTENV mem     | fstenv [bp-14]          |  87 (40-50)+EA
 FSTENVW mem*   |                         | 287 40-50
 FSTENVD mem*   |                         | 387 103-104
 FNSTENV mem    |                         | 486 67,pm=56
 FNSTENVW mem*  |                         |
 FNSTENVD mem*  |                         |
----------------+-------------------------+---------------------------------
  * 80387/486 only.

  NOTE: The timings reflect the no-wait versions of FSTENV. The wait
        versions may take additional clock cycles.

FMUL Instruction
----------------------------------------------------------------------------

  Title:    Multiply

  Syntax:   FMUL [dest,src]
            FMULP dest,ST
            FMUL src
            FIMUL src

  See also: FDIV, FDIVR

  Description:

     Multiplies the source by the destination and returns the product
     in the destination. If two register operands are specified, one
     must be ST. If a memory operand is specified, the product replaces
     the value in ST. Memory operands can be 32- or 64-bit real numbers
     or 16- or 32-bit integers.

     If no operand is specified, ST(1) is multiplied by ST and the
     stack is popped; the product is returned in ST. For FMULP, the
     source must be ST; the product is returned in the destination
     register and ST is popped.

----------------+------------------------+----------------------------------
 FMUL [reg,reg] | fmul   st,st(2)        |  87 130-145 (90-105)*
                | fmul   st(5),st        | 287 130-145 (90-105)*
                | fmul                   | 387 to=46-54 (49),fr=29-57 (52)œ
                |                        | 486 16
----------------+------------------------+----------------------------------
 FMULP reg,ST   | fmulp  st(6),st        |  87 134-148 (94-108)*
                |                        | 287 134-148 (94-108)*
                |                        | 387 29-57 (52)œ
                |                        | 486 16
----------------+------------------------+----------------------------------
 FMUL memreal   | fmul   DWORD PTR [bx]  |  87 (s=110-125,l=154-168)+EA
                | fmul   shortreal[di+3] | 287 s=110-125,l=154-168
                | fmul   longreal        | 387 s=27-35,l=32-57
                |                        | 486 s=11,l=14
----------------+------------------------+----------------------------------
 FIMUL memint   | fimul  int16           |  87 (w=124-138,d=130-144)+EA
                | fimul  warray[di]      | 287 w=124-138,d=130-144
                | fimul  double          | 387 w=76-87,d=61-82
                |                        | 486 w=23-27,d=22-24
----------------+------------------------+----------------------------------
  * The clocks in parentheses show times for short values--those with 40
    trailing zeros in their fraction because they were loaded from a
    short-real memory operand.

  œ The clocks in parentheses show typical speeds.

   If the register operand is a short value--having 40 trailing zeros
    in its fraction because it was loaded from a short-real memory
    operand--then the timing is (112-126)+EA on the 8087 or 112-126 on
    the 80287.

FNOP Instruction
----------------------------------------------------------------------------

  Title:    No Operation

  Syntax:   FNOP

  See also: NOP

  Description:

     Performs no operation. FNOP can be used for timing delays or
     alignment.

----------------+-------------------------+---------------------------------
 FNOP           | fnop                    |  87 10-16
                |                         | 287 10-16
                |                         | 387 12
                |                         | 486 3
----------------+-------------------------+---------------------------------

FPATAN/FPTAN Instructions
----------------------------------------------------------------------------

  Title:    Partial Arctangent and Tangent

  Syntax:   FPATAN
            FPTAN

  See also: FSIN

  Description:

     FPATAN finds the partial tangent by calculating Z = ARCTAN(Y / X)
     where X is taken from ST and Y from ST(1). On the 8087/287, Y and
     X must be in the range 0 <= Y < X < infinity. On the 80387/486,
     there is no restriction on X and Y. X is popped from the stack and
     Z replaces Y in ST.

     FPTAN finds the partial tangent by calculating Y / X = TAN(Z). Z
     is taken from ST. Z must be in the range 0 <= Z <= pi/4 on the
     8087/287. On the 80387/486, |Z| must be less than 2^63. The result
     is the ratio Y / X. Y replaces Z, and X is pushed into ST. Thus, Y
     is returned in ST(1) and X in ST.

----------------+-------------------------+---------------------------------
 FPATAN         | fpatan                  |  87 250-800
                |                         | 287 250-800
                |                         | 387 314-487
                |                         | 486 218-303
----------------+-------------------------+---------------------------------
 FPTAN          | fptan                   |  87 30-540
                |                         | 287 30-540
                |                         | 387 191-497*
                |                         | 486 200-273œ
----------------+-------------------------+---------------------------------
  * For operands with an absolute value greater than pi/4, up to 76
    additional clocks may be required.

  œ For operands with an absolute value greater than pi/4, add n clocks
    where n = operand / (pi/4).

FPREM/FPREM1 Instructions
----------------------------------------------------------------------------

  Title:    Partial Remainder

  Syntax:   FPREM
            FPREM1 (80387/487 only)

  Description:

     Calculates the remainder of ST divided by ST(1); the result is
     returned in ST. The remainder retains the same sign as the original
     dividend. The calculation uses the following formula:

          remainder = ST - ST(1) * quotient

     For FPREM, the quotient is the exact value obtained by chopping
     ST / ST(1) toward 0. For FPREM1, the quotient is the integer
     nearest to the exact value of ST / ST(1). When two integers are
     equally close to the given value, the even integer is used. FPREM1
     conforms to the IEEE standard.

     These instructions are normally used in a loop that repeats until
     the reduction is complete, as indicated by the condition codes of
     the status word.

     The condition codes for FPREM and FPREM1 are as follows:

     C3     C2     C1     C0     Meaning

     ?      1      ?      ?      Incomplete reduction
     0      0      0      0      Quotient MOD 8 = 0
     0      0      0      1      Quotient MOD 8 = 4
     0      0      1      0      Quotient MOD 8 = 1
     0      0      1      1      Quotient MOD 8 = 5
     1      0      0      0      Quotient MOD 8 = 2
     1      0      0      1      Quotient MOD 8 = 6
     1      0      1      0      Quotient MOD 8 = 3
     1      0      1      1      Quotient MOD 8 = 7

----------------+-------------------------+---------------------------------
 FPREM          | fprem                   |  87 15-190
                |                         | 287 15-190
                |                         | 387 74-155
                |                         | 486 70-138
----------------+-------------------------+---------------------------------
 FPREM1         | fprem1                  |  87 --
                |                         | 287 --
                |                         | 387 95-185
                |                         | 486 72-167
----------------+-------------------------+---------------------------------

FRNDINT Instruction
----------------------------------------------------------------------------

  Title:    Round to Integer

  Syntax:   FRNDINT

  See also: FABS

  Description:

     Rounds ST from a real number to an integer. The rounding control
     (RC) field of the control word specifies the rounding method.

----------------+-------------------------+---------------------------------
 FRNDINT        | frndint                 |  87 16-50
                |                         | 287 16-50
                |                         | 387 66-80
                |                         | 486 21-30
----------------+-------------------------+---------------------------------

FSAVE/FRSTOR Instructions
----------------------------------------------------------------------------

  Title:    Save or Restore Coprocessor State

  Syntax:   FSAVE dest
            FNSAVE dest
            FRSTOR src

  See also: FSTENV, FSTSW, FSTCW, Coprocessor Environment

  Description:

     FSAVE stores the 94-byte coprocessor state to a specified memory
     location; FRSTOR restores it from a specified memory location.

     In 32-bit mode on the 80387/486, the environment state takes 108
     bytes.

     FSAVE has wait and no-wait versions. The wait version (FSAVE)
     checks for unmasked numeric errors; the no-wait version (FNSAVE)
     does not. After saving the state, the coprocessor is initialized
     as if FINIT had been executed.

     When the .8087 directive is used, the assembler puts the WAIT
     instruction before the wait version and the NOP instruction before
     the no-wait version.

----------------+-------------------------+---------------------------------
 FSAVE mem      | fsave  [bp-94]          |  87 (197-207)+EA
 FSAVEW mem*    | fsave  cobuffer         | 287 œ
 FSAVED mem*    |                         | 387 375-376
 FNSAVE mem     |                         | 486 154,pm=143
 FNSAVEW mem*   |                         |
 FNSAVED mem*   |                         |
----------------+-------------------------+---------------------------------
 FRSTOR mem     | frstor [bp-94]          |  87 (197-207)+EA
 FRSTORW mem*   |                         | 287 œ
 FRSTORD mem*   |                         | 387 308
                |                         | 486 131,pm=120
----------------+-------------------------+---------------------------------
  * 80387/486 only.

  œ Clock counts are not meaningful in determining overall execution
    time of this instruction. Timing is determined by operand
    transfers.

  NOTE: The timings reflect the no-wait versions of FSAVE. The wait
        versions may take additional clock cycles.

FSCALE Instruction
----------------------------------------------------------------------------

  Title:    Scale

  Syntax:   FSCALE

  See also: FSQRT

  Description:

     Scales by powers of 2 by calculating the function Y = Y * 2^X. X
     is the scaling factor taken from ST(1), and Y is the value to be
     scaled from ST. The scaled result replaces the value in ST; the
     scaling factor remains in ST(1). If the scaling factor is not an
     integer, it will be truncated toward zero before the scaling.

     On the 8087/287 processors, if X is not in the range -2^15 <= X < 2^15
     or if X is in the range 0 < X < 1, the result will be undefined. The
     80387/486 processors have no restrictions on the range of operands.

----------------+-------------------------+---------------------------------
 FSCALE         | fscale                  |  87 32-38
                |                         | 287 32-38
                |                         | 387 67-86
                |                         | 486 30-32
----------------+-------------------------+---------------------------------

FSETPM Instruction
----------------------------------------------------------------------------

  Title:    Set Protected Mode
            80287 Only

  Syntax:   FSETPM

  See also: .287

  Description:

     Sets the 80287 to protected mode. The instruction and operand
     pointers are in the protected-mode format after this instruction.
     On the 80387/486, FSETPM is recognized but interpreted as the FNOP
     instruction, since the 80386/486 processors handle addressing
     identically in real and protected modes.

----------------+-------------------------+---------------------------------
 FSETPM         | fsetpm                  |  87 --
                |                         | 287 2-8
                |                         | 387 12
                |                         | 486 3
----------------+-------------------------+---------------------------------

FSQRT Instruction
----------------------------------------------------------------------------

  Title:    Square Root

  Syntax:   FSQRT

  See also: FSCALE

  Description:

     Replaces the value of ST with its square root. The square root
     of -0 is -0.

----------------+-------------------------+---------------------------------
 FSQRT          | fsqrt                   |  87 180-186
                |                         | 287 180-186
                |                         | 387 122-129
                |                         | 486 83-87
----------------+-------------------------+---------------------------------

FST Instruction
----------------------------------------------------------------------------

  Title:    Store

  Syntax:   FST dest
            FSTP dest
            FIST dest
            FISTP dest
            FBSTP dest

  Description:

     Stores the value in ST to the specified memory location or
     register. Temporary-real values in registers are converted to the
     appropriate integer, BCD, or floating-point format as they are
     stored. With FSTP, FISTP, and FBSTP, the ST register value is
     popped off the stack.

     Memory operands can be 32-, 64-, or 80-bit real numbers for FSTP,
     or 16-, 32-, or 64-bit integers for FISTP.

---------------+-----------------------+------------------------------------
 FST reg       | fst    st(6)          |  87 15-22
               | fst    st             | 287 15-22
               |                       | 387 11
               |                       | 486 3
---------------+-----------------------+------------------------------------
 FSTP reg      | fstp   st             |  87 17-24
               | fstp   st(3)          | 287 17-24
               |                       | 387 12
               |                       | 486 3
---------------+-----------------------+------------------------------------
 FST memreal   | fst    shortreal      |  87 (s=84-90,l=96-104)+EA
               | fst    longs[bx]      | 287 s=84-90,l=96-104
               |                       | 387 s=44,l=45
               |                       | 486 s=7,l=8
---------------+-----------------------+------------------------------------
 FSTP memreal  | fstp   longreal       |  87 (s=86-92,l=98-106,t=52-58)+EA
               | fstp   tempreals[bx]  | 287 s=86-92,l=98-106,t=52-58
               |                       | 387 s=44,l=45,t=53
               |                       | 486 s=7,l=8,t=6
---------------+-----------------------+------------------------------------
 FIST memint   | fist   int16          |  87 (w=80-90,d=82-92)+EA
               | fist   doubles[8]     | 287 w=80-90,d=82-92
               |                       | 387 w=82-95,d=79-93
               |                       | 486 w=29-34,d=28-34
---------------+-----------------------+------------------------------------
 FISTP memint  | fistp  longint        |  87 (w=82-92,d=84-94,q=94-105)+EA
               | fistp  doubles[bx]    | 287 w=82-92,d=84-94,q=94-105
               |                       | 387 w=82-95,d=79-93,q=80-97
               |                       | 486 29-34
---------------+-----------------------+------------------------------------
 FBSTP membcd  | fbstp  bcds[bx]       |  87 (520-540)+EA
               |                       | 287 520-540
               |                       | 387 512-534
               |                       | 486 172-176
---------------+-----------------------+------------------------------------

FSTSW Instruction
----------------------------------------------------------------------------

  Title:    Store Status Word

  Syntax:   FSTSW dest
            FNSTSW dest

  Description:

     Stores the status word to a specified 16-bit memory operand. On
     the 80287, 80387, and 80486, the status word can also be stored to
     the processor's AX register.

     FSTSW has wait and no-wait versions. The wait version (FSTSW)
     checks for unmasked numeric errors; the no-wait version (FNSTSW)
     does not. When the .8087 directive is used, the assembler puts the
     WAIT instruction before the wait version and the NOP instruction
     before the no-wait version.

----------------+-------------------------+---------------------------------
 FSTSW mem16    | fstsw  statword         |  87 12-18
 FNSTSW mem16   |                         | 287 12-18
                |                         | 387 15
                |                         | 486 3
----------------+-------------------------+---------------------------------
 FSTSW AX       | fstsw  ax               |  87 --
 FNSTSW AX      |                         | 287 10-16
                |                         | 387 13
                |                         | 486 3
----------------+-------------------------+---------------------------------

  NOTE: The timings reflect the no-wait version of the instruction.
        The wait version may take additional clock cycles.

FSUB Instruction

----------------------------------------------------------------------------

  Title:    Subtract

  Syntax:   FSUB [dest,src]
            FSUBP dest,ST
            FSUB src
            FISUB src

  See also: FSUBR, FADD

  Description:

     Subtracts the source from the destination and returns the
     difference in the destination. If two register operands are
     specified, one must be ST. If a memory operand is specified, the
     result replaces the value in ST. Memory operands can be 32- or
     64-bit real numbers or 16- or 32-bit integers.

     If no operand is specified, ST is subtracted from ST(1) and the
     stack is popped; the difference is returned in ST. For FSUBP, the
     source must be ST; the difference (destination minus source) is
     returned in the destination register and ST is popped.

----------------+-------------------------+---------------------------------
 FSUB [reg,reg] | fsub   st,st(2)         |  87 70-100
                | fsub   st(5),st         | 287 70-100
                | fsub                    | 387 to=29-37,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBP reg,ST   | fsubp  st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUB memreal   | fsub   longreal         |  87 (s=90-120,s=95-125)+EA
                | fsub   shortreals[di]   | 287 s=90-120,l=95-125
                |                         | 387 s=24-32,l=28-36
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FISUB memint   | fisub  double           |  87 (w=102-137,d=108-143)+EA
                | fisub  warray[di]       | 287 w=102-137,d=108-143
                |                         | 387 w=71-83,d=57-82
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------

FSUBR Instruction
----------------------------------------------------------------------------

  Title:    Subtract Reversed

  Syntax:   FSUBR [dest,src]
            FSUBRP dest,ST
            FSUBR src
            FISUBR src

  See also: FSUB, FADD

  Description:

     Subtracts the destination operand from the source operand and
     returns the result in the destination operand. If two register
     operands are specified, one must be ST. If a memory operand is
     specified, the result replaces the value in ST. Memory operands
     can be 32- or 64-bit real numbers or 16- or 32-bit integers.

     If no operand is specified, ST(1) is subtracted from ST and the
     stack is popped; the difference is returned in ST. For FSUBRP, the
     source must be ST; the difference (source minus destination) is
     returned in the destination register and ST is popped.

----------------+-------------------------+---------------------------------
 FSUBR [reg,reg]| fsubr  st,st(2)         |  87 70-100
                | fsubr  st(5),st         | 287 70-100
                | fsubr                   | 387 to=29-37,fr=26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBRP reg,ST  | fsubrp st(6),st         |  87 75-105
                |                         | 287 75-105
                |                         | 387 26-34
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FSUBR memreal  | fsubr  QWORD PTR [bx]   |  87 (s=90-120,s=95-125)+EA
                | fsubr  shortreal[di]    | 287 s=90-120,l=95-125
                | fsubr  longreal         | 387 s=25-33,l=29-37
                |                         | 486 8-20
----------------+-------------------------+---------------------------------
 FISUBR memint  | fisubr int16            |  87 (w=103-139,d=109-144)+EA
                | fisubr warray[di]       | 287 w=103-139,d=109-144
                | fisubr double           | 387 w=72-84,d=58-83
                |                         | 486 w=20-35,d=19-32
----------------+-------------------------+---------------------------------

FTST Instruction
----------------------------------------------------------------------------

  Title:    Test for Zero

  Syntax:   FTST

  See also: FCOM, Status Word

  Description:

     Compares ST with +0.0 and sets the condition of the status word
     according to the result.

     The condition codes for FTST are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST is positive
     0      0      ?      1      ST is negative
     1      0      ?      0      ST is 0
     1      1      ?      1      ST is not comparable (NAN or
                                 projective infinity)
----------------+-------------------------+---------------------------------
 FTST           | ftst                    |  87 38-48
                |                         | 287 38-48
                |                         | 387 28
                |                         | 486 4
----------------+-------------------------+---------------------------------

FUCOM Instruction
----------------------------------------------------------------------------

  Title:    Unordered Compare
            80387/486 Only

  Syntax:   FUCOM [src]
            FUCOMP [src]
            FUCOMPP

  See also: FCOM, FTST, Status Word

  Description:

     Compares the specified source to ST and sets the condition codes
     of the status word according to the result. The instruction
     subtracts the source operand from ST without changing either
     operand. Memory operands are not allowed.

     If no operand is specified or if two pops are specified, ST is
     compared to ST(1). If one pop is specified with an operand, the
     given register is compared to ST.

     Unlike FCOM, FUCOM does not cause an invalid-operation exception
     if one of the operands is a NAN. Instead, the condition codes are
     set to unordered.

     The condition codes for FUCOM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      ?      0      ST > source
     0      0      ?      1      ST < source
     1      0      ?      0      ST = source
     1      1      ?      1      Unordered

----------------+-------------------------+---------------------------------
 FUCOM [reg]    | fucom  st(2)            |  87 --
                | fucom                   | 287 --
                |                         | 387 24
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FUCOMP [reg]   | fucomp st(7)            |  87 --
                | fucomp                  | 287 --
                |                         | 387 26
                |                         | 486 4
----------------+-------------------------+---------------------------------
 FUCOMPP        | fucompp                 |  87 --
                |                         | 287 --
                |                         | 387 26
                |                         | 486 5
----------------+-------------------------+---------------------------------

FWAIT Instruction
----------------------------------------------------------------------------

  Title:    Wait

  Syntax:   FWAIT

  See also: WAIT

  Description:

     Suspends execution of the processor until the coprocessor is
     finished executing. This is an alternate mnemonic for the
     processor WAIT instruction.

----------------+-------------------------+---------------------------------
 FWAIT          | fwait                   |  87 4
                |                         | 287 3
                |                         | 387 6
                |                         | 486 1-3
----------------+-------------------------+---------------------------------

FXAM Instruction
----------------------------------------------------------------------------

  Title:    Examine

  Syntax:   FXAM

  See also: Status Word

  Description:

     Reports the contents of ST in the condition flags of the status
     word.

     The condition codes for FXAM are as follows:

     C3     C2     C1     C0     Meaning

     0      0      0      0      + Unnormal*
     0      0      0      1      + NAN
     0      0      1      0      - Unnormal*
     0      0      1      1      - NAN
     0      1      0      0      + Normal
     0      1      0      1      + Infinity
     0      1      1      0      - Normal
     0      1      1      1      - Infinity
     1      0      0      0      + 0
     1      0      0      1      Empty
     1      0      1      0      - 0
     1      0      1      1      Empty
     1      1      0      0      + Denormal
     1      1      0      1      Empty*
     1      1      1      0      - Denormal
     1      1      1      1      Empty*

     * Not used on the 80387/486 processors. Unnormals are not supported by
       the 80387/486. Also, the 80387/486 processors use two codes instead
       of four to identify empty registers.

----------------+-------------------------+---------------------------------
 FXAM           | fxam                    |  87 12-23
                |                         | 287 12-23
                |                         | 387 30-38
                |                         | 486 8
----------------+-------------------------+---------------------------------

FXCH Instruction
----------------------------------------------------------------------------

  Title:    Exchange Registers

  Syntax:   FXCH [dest]

  See also: FLD

  Description:

     Exchanges the specified (destination) register and ST. If no
     operand is specified, ST and ST(1) are exchanged.

----------------+-------------------------+---------------------------------
 FXCH [reg]     | fxch   st(3)            |  87 10-15
                | fxch                    | 287 10-15
                |                         | 387 18
                |                         | 486 4
----------------+-------------------------+---------------------------------

FXTRACT Instruction
----------------------------------------------------------------------------

  Title:    Extract Exponent and Significand

  Syntax:   FXTRACT

  Description:

     Extracts the exponent and significand (mantissa) fields of ST. The
     exponent replaces the value in ST, and then the significand is
     pushed onto the stack.

----------------+-------------------------+---------------------------------
 FXTRACT        | fxtract                 |  87 27-55
                |                         | 287 27-55
                |                         | 387 70-76
                |                         | 486 16-20
----------------+-------------------------+---------------------------------

FYL2X Instruction
----------------------------------------------------------------------------

  Title:    Y log(sub2)(X)

  Syntax:   FYL2X

  See also: FYL2XP1, F2XM1

  Description:

     Calculates Z = Y log(sub2)(X) where X is taken from ST and Y from
     ST(1). The stack is popped and the result, Z, replaces Y in ST. X
     must be in the range 0 < X < infinity and Y in the range
     minus infinity < Y < infinity.

----------------+-------------------------+---------------------------------
 FYL2X          | fyl2x                   |  87 900-1100
                |                         | 287 900-1100
                |                         | 387 120-538
                |                         | 486 196-329
----------------+-------------------------+---------------------------------

FYL2XP1 Instruction
----------------------------------------------------------------------------

  Title:    Y log(sub2)(X+1)

  Syntax:   FYL2XP1

  See also: FYL2X, F2XM1

  Description:

     Calculates Z = Y log(sub2)(X + 1) where X is taken from ST and Y
     from ST(1). The stack is popped once and the result, Z, replaces Y
     in ST. X must be in the range 0 <= |X| < (1 - (SQRT2 / 2)) and Y in
     the range minus infinity < Y < infinity.

----------------+-------------------------+---------------------------------
 FYL2XP1        | fyl2xp                  |  87 700-1000
                |                         | 287 700-1000
                |                         | 387 257-547
                |                         | 486 171-326
----------------+-------------------------+---------------------------------


AvailableFunctions.htm

Available Functions

FpuLib Copyright Raymond Filiatreault 2002 - 2007

Conversion Functions

FpuAtoFL | Converts a decimal string to a REAL10 number
FpuFLtoA | Converts a REAL10 number to a decimal string

Arithmetic Functions

FpuAdd | Adds two numbers
FpuSub | Subtracts one number from another
FpuMul | Multiplies one number by another
FpuDiv | Divides one number by another
FpuSqrt | Extracts the square root of a positive number
FpuXexpY | Raises a positive number by an exponent
FpuAbs | Returns the absolute value of a number
FpuChs | Changes the sign of a number
FpuRound | Rounds a number to the nearest integer
FpuTrunc | Discards the decimal portion of a number

Logarithmic Functions

FpuLogx | Returns the base 10 logarithm of a positive number
FpuLnx | Returns the Napierian logarithm of a positive number
FpuTexpX | Returns the number 10 raised to a power (common antilog)
FpuEexpX | Returns the Napierian constant e raised to a power (Napierian antilog)

Trigonometric Functions

FpuSin | Returns the sine of an angle
FpuCos | Returns the cosine of an angle
FpuTan | Returns the tangent of an angle
FpuArcsin | Returns the angle corresponding to a sine value
FpuArccos | Returns the angle corresponding to a cosine value
FpuArctan | Returns the angle corresponding to a tangent value

Hyperbolic Functions

FpuSinh | Returns the hyperbolic sine of a number
FpuCosh | Returns the hyperbolic cosine of a number
FpuTanh | Returns the hyperbolic tangent of a number
FpuArcsinh | Returns the number corresponding to a hyperbolic sine
FpuArccosh | Returns the number corresponding to a hyperbolic cosine
FpuArctanh | Returns the number corresponding to a hyperbolic tangent

Miscellaneous Functions

FpuSize | Returns the exponent of scientific notation
FpuComp | Compares two numbers
FpuExam | Checks REAL10 for its validity, its sign, a value of zero, an absolute value less than 1, and/or a value of infinity
FpuState | Returns a null-terminated string listing the content of the FPU registers


index.htm

FpuLib Help
Copyright Raymond Filiatreault 2002 - 2007

This library contains functions to perform high precision floating point computations with the FPU coprocessor, without the need to learn all the intricacies of the coding syntax and limitations of the FPU. The obvious drawback is some code overhead to decipher the meaning of parameters. In most cases however, the speed should not be affected significantly when compared to directly programming the FPU.

Original release. December 2002

Modified January 2004 to free all registers required by the function to avoid any potential FPU stack overflow.

Modified March 2004 to preserve all FPU registers while avoiding any stack overflow. Also corrected a minor flaw in the FpuSize function and added the FpuState function.

Modified January 2005 to trash the FPU’s ST(7) data register whenever necessary to prevent reporting an error if a register is not available for returning a result on the FPU.

Updated this Help file May 2006 to cover the addition of a parameter for the FpuState function.

Design Philosophy
Sources of Errors
Available Functions
FPU Constants
Example
Legal
Acknowledgement

Prepared by Raymond Filiatreault.


Legal.htm

Legal

This library of functions may be used free of charge for personal non-commercial use. The library functions may not be used for any commercial program (including shareware) without the express permission from the author.

The author retains all copyright to the library and the source code of the modules.

The author may be reached at rayfil@hotmail.com

Prepared by Raymond Filiatreault.


Acknowledgement.htm

Acknowledgement

This library would never have been possible without the timely help and encouragement from Steve Hutchesson (hutch) during my early attempts with Win32 programming. My initial impression of it was like trying to learn a dozen alien dialects with instructions written in hieroglyphs. I was therefore enthusiastic when Steve invited me to prepare this library of functions for the FPU.

Thank you Steve.

Raymond Filiatreault


Designphilosophy.htm

Design Philosophy

The philosophy adopted for the design of the library was to make it as flexible as possible without making it overly complex and/or cumbersome.

Only REAL10 (80-bit) numbers are allowed as floating point numbers. The reasoning was that the FPU performs all its operations strictly on 80-bit numbers. Any other type of number used by the FPU is first converted to the 80-bit format. Therefore there is no significant advantage in FPU speed performance when using SHORT REAL (32-bit) or LONG REAL (64-bit) floating point numbers. The only disadvantage is a minimal increase in memory storage requirements.

Only LONG (32-bit) integers (DWORD) are allowed for operations with the library functions. Although CHAR (BYTES) and SHORT integers (WORD) could be allowed with some FPU instructions, they can be easily sign extended for the purpose of these functions. However, although only values from memory can be retrieved by the FPU, the functions were designed to directly accept integer values (either immediate or CPU register content) as input where integers may be used.

There are two widely used constants (π and e) which can be loaded directly on the FPU. They can be coded as input parameters in all functions where they may be used.

Unless a source parameter was specified as being in the TOP data register, the original Fpulib was designed to initialize the FPU to prevent any potential stack overflow. This destroyed any data which may have been present in the other FPU registers. This was revised later to destroy only the data (if any) in the registers which were necessary to perform the function. This latest version will not destroy any of the existing data, except possibly the data in the ST(7) register.

Whenever a result is specified to be returned on the FPU, it would replace a source parameter which would have been identified as having been in the TOP data register. If no source is identified as being on the FPU, its ST7 data register must then be free if the result must be returned on the FPU. Otherwise, the function will trash that register in order to free it for the returned value.

Whenever a result is specified to be returned to a memory location, any source parameter identified as having been on the FPU may or may not be preserved. See the description of each function. (As a general guide, such source would not be preserved when used for a computation.)

The only function which performs a thorough checking of the input is the one for converting an alphanumeric decimal string to a REAL10 number. None of the other functions verify the adequacy of the input. If a source parameter is not identified or if the FPU reports an invalid operation, EAX is simply returned with zero indicating an error. No error description is provided.

See also Sources of Errors .

Prepared by Raymond Filiatreault.


Example.htm

Example

This example makes use of some of the library functions to compute the monthly payments on a loan (such as a mortgage) according to the following equation:

where:

P = principal (mortgage)
R = interest rate for each period (expressed as a decimal)
N = number of periods (generally number of months for a mortgage)
PMT = payment for each period (generally monthly payments for a mortgage)

This example assumes that some input from a user has already been obtained as alphanumeric strings for the loan principal, the annual interest rate (%) and the term of the loan (years). That input is then converted to REAL10 values and corrected to the units required for the computation. The result is then computed and converted to its decimal representation with two decimal places ready for display.

The code also presumes that the interest on the loan can be compounded on a monthly basis. This simplifies the process by being able to simply divide the annual interest rate by 12. (Several more operations would have been required if the interest could only have been compounded on a semi-annual or annual basis.)

The entire source code of a fully functional demo is provided for assembly with MASM32 (the Fpu.lib and Fpu.inc files must be in the required sub-folders). Simply copy and paste it into your editor, modify the MASM32 path if necessary, save it and compile it. Or you may like to design your own “dialog box” to get user input and use the code as a procedure to prepare the results.

No error checking is included in this demo. It would be wise to add some if you use it as a procedure.

; ##########################################

.486
.model flat, stdcall
option casemap :none ; case sensitive

; ##########################################

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\Fpu.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\Fpu.lib

; ##########################################

.data

mortgage dt 0
monthly_rate dt 0
months dt 0
temp_real dt 0
loan_inp db "92500",0
rate_inp db "6.75",0
year_inp db "20",0
result db 25 dup(0)

.code

start:

;convert loan principal input to REAL10 and store it

invoke FpuAtoFL, ADDR loan_inp, ADDR mortgage, DEST_MEM

;convert yearly interest (%) rate input to REAL10 and store it as a monthly fractional rate

invoke FpuAtoFL,ADDR rate_inp, 0, DEST_FPU
invoke FpuDiv, 0, 1200, ADDR monthly_rate, SRC1_FPU or SRC2_DIMM

;convert term of loan (years) input to REAL10 and store it as months

invoke FpuAtoFL, ADDR year_inp, 0, DEST_FPU
invoke FpuMul, 0, 12, ADDR months, SRC1_FPU or SRC2_DIMM

;compute the (1+R) portion, keeping it on the FPU, and then the (1+R)N term
;and storing it as a temporary REAL10 value

invoke FpuAdd, ADDR monthly_rate, 1, 0, SRC1_REAL or SRC2_DIMM or DEST_FPU
invoke FpuXexpY, 0, ADDR months, ADDR temp_real, SRC1_FPU or SRC2_REAL

;compute the denominator of the equation and leave it on the FPU

invoke FpuSub, ADDR temp_real, 1, 0, SRC1_REAL or SRC2_DIMM or DEST_FPU

;divide the (1+R)N term by that denominator and leave on the FPU

invoke FpuDiv, ADDR temp_real, 0, 0, SRC1_REAL or SRC2_FPU or DEST_FPU

;multiply by the other terms and leave answer on the FPU

invoke FpuMul, 0, ADDR monthly_rate, 0, SRC1_FPU or SRC2_REAL or DEST_FPU
invoke FpuMul, 0, ADDR mortgage, 0, SRC1_FPU or SRC2_REAL or DEST_FPU

;convert result to alphanumeric with 2 decimals and display

invoke FpuFLtoA, 0, 2, ADDR result, SRC1_FPU or SRC2_DIMM
invoke MessageBox, 0, ADDR result, 0, MB_OK

invoke ExitProcess, 0

end start

; ##########################################

Prepared by Raymond Filiatreault.


FpuAbs.htm

FpuAbs

The FpuAbs function returns the absolute value of an 80-bit REAL number at the specified destination.

|Src| → Dest

FpuAbs (

lpSrc //pointer to source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This points to an 80-bit REAL number either in memory or on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the source is considered a valid REAL number, its absolute value will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified source and destination will not be modified.

When the source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If the source is not from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be an 80-bit REAL number. (The absolute value of integers can be easily obtained with CPU instructions.)

Example:

invoke FpuAbs, 0, 0, SRC1_FPU or DEST_FPU

In this example, the 80-bit REAL number already on the FPU will be replaced by its absolute value if no error is reported. (Both 1st and 2nd parameters are ignored.)

Prepared by Raymond Filiatreault.


FpuAdd.htm

FpuAdd

The FpuAdd function adds two numbers and returns the result as an 80-bit REAL number at the specified destination.

Src1 + Src2 → Dest

FpuAdd (

lpSrc1 // pointer to, or value of, 1st parameter
lpSrc2 // pointer to, or value of, 2nd parameter
lpDest // pointer to destination of result
uID // ID flags for sources and destination
)

Parameters

lpSrc1

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER,
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result of the addition will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags and OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
SRC2_FPU Src2 is already on the FPU
SRC2_REAL Src2 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
SRC2_CONST Src2 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Because these library functions allow only one value to be retained on the FPU, identifying both sources as being on the FPU will result in adding the FPU value to itself.

Example:

invoke FpuAdd, ADDR Src1, eax, ADDR Dest, SRC1_REAL or SRC2_DIMM

In this example, the 80-bit REAL number from the TBYTE memory variable labeled Src1 is added with the DWORD integer in EAX, and the result is transferred as an 80-bit REAL to the TBYTE memory variable labeled Dest.

Prepared by Raymond Filiatreault.


FpuArccos.htm

FpuArccos

The FpuArccos function returns the angle expressed in degrees or radians corresponding to the cosine value provided as the source.

acos(Src) → Dest

FpuArccos (

lpSrc //pointer to the cosine value (Src)
lpDest //pointer to destination of result
uID //ID flags for source, angle type, and destination
)

Parameters

lpSrc

This parameter points to an 80-bit REAL number according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL angle value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the ANG_? Flags and OR’ed with one of the DEST_? flags. (The ANG flag does not need to be OR’ed if the angle is required in degrees; that is the default type. The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
ANG_RAD The value of the angle will be returned in radians
ANG_DEG The value of the angle will be returned in degrees
(this is the default and does not need to be indicated)
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter must have a value within the range of -1 to +1 inclusive. Otherwise, the function will fail and return with the error code.

Positive values of the cosine will result in angles within the range of 0 - ½π radians (0-90 degrees). Negative values of the cosine will result in angles within the range of ½π - π radians (90-180 degrees).

If the angle with the corresponding cosine is known to be in the other two quadrants (i.e. in the 180-360 degree range), its value must be subtracted from 360 to get the required result.

Similarly, if the angle in radians needs to be adjusted for the other two quadrants, its value must be subtracted from 2π to get the required result

Example:

invoke FpuArccos, ADDR Src, ADDR Dest, SRC1_REAL or ANG_RAD

In this example, the cosine value is identified as being the 80-bit REAL at the TBYTE memory variable labeled Src. The resulting angle in radians will be stored at the TBYTE memory variable labeled Dest if the function succeeds. All FPU registers would be preserved.

Prepared by Raymond Filiatreault.


FpuArccosh.htm

FpuArccosh

The FpuArccosh function returns the number corresponding to a hyperbolic cosine value.

acosh(Src) → Dest

FpuArccosh (

lpSrc //pointer to, or value of, source number (Src)
lpDest //pointer to destination of result
uID //ID flags for source, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL hyperbolic arccosine value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src is a pointer to a 32-bit signed integer
SRC1_DIMM Src is a 32-bit signed integer
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Hyperbolic cosine values can only be positive and greater than or equal to 1. Only such values (except infinity) are thus acceptable as the source parameter for this function. The result will also be always returned positive although negative numbers can generate the same hyperbolic cosine values. If the negative result may be required, simply change its sign.

Example:

invoke FpuArccosh, 0, 0, SRC1_FPU or DEST_FPU

In this example, the value already on the FPU is replaced by its hyperbolic arccosine if the function succeeds. Both the 1st and 2nd parameters are ignored.

Prepared by Raymond Filiatreault.


FpuArcsin.htm

FpuArcsin

The FpuArcsin function returns the angle expressed in degrees or radians corresponding to the sine value provided as the source.

asin(Src) → Dest

FpuArcsin (

lpSrc //pointer to the sine value (Src)
lpDest //pointer to destination of result
uID //ID flags for source, angle type, and destination
)

Parameters

lpSrc

This parameter points to an 80-bit REAL number according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL angle value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the ANG_? Flags and OR’ed with one of the DEST_? flags. (The ANG flag does not need to be OR’ed if the angle is required in degrees; that is the default type. The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
ANG_RAD The value of the angle will be returned in radians
ANG_DEG The value of the angle will be returned in degrees
(this is the default and does not need to be indicated)
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter must have a value within the range of -1 to +1 inclusive. Otherwise, the function will fail and return with the error code.

Negative values of a sine yield negative values of the corresponding angle. If the angle is specified to be in radians, it remains as is with a value between -½π and +½π inclusive. If the angle is specified to be in degrees, the negative values are modified to the 270-360 degree range; positive values remain in the 0-90 degree range.
If the angle with the corresponding sine is known to be in the other two quadrants (i.e. in the 90-270 degree range), its value must be subtracted from 180 to get the required result; this may lead to a negative angle which would require the addition of 360 to get it in the 0-360 positive range.
Similarly, if the angle in radians needs to be adjusted for the other two quadrants, its value must be subtracted from π to get the required result.

Example:

invoke FpuArcsin, 0, ADDR Dest, SRC1_FPU

In this example, the 1st parameter is ignored since the sine value is identified as being already on the FPU. The resulting angle in degrees will be stored at the TBYTE memory variable labeled Dest and the source parameter would be removed from the FPU if the function succeeds.

Prepared by Raymond Filiatreault.


FpuArcsinh.htm

FpuArcsinh

The FpuArcsinh function returns the number corresponding to a hyperbolic sine value.
asinh(Src) → Dest

FpuSinh (

lpSrc //pointer to, or value of, source number (Src)
lpDest //pointer to destination of result
uID //ID flags for source, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL hyperbolic arcsine value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src is a pointer to a 32-bit signed integer
SRC1_DIMM Src is a 32-bit signed integer
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All valid positive and negative values (except infinity) are acceptable as the source parameter for this function.

Example:

invoke FpuArcsinh, ADDR Src, ADDR Dest, SRC1_REAL

In this example, the hyperbolic arcsine of the 80-bit REAL number from the TBYTE memory variable labeled Src will be stored as an 80-bit REAL number at the TBYTE memory variable labeled Dest if the function succeeds.

Prepared by Raymond Filiatreault.


FpuArctan.htm

FpuArctan

The FpuArctan function returns the angle expressed in degrees or radians corresponding to the tangent value provided as the source.

atan(Src) → Dest

FpuArctan (

lpSrc //pointer to the tangent value (Src)
lpDest //pointer to destination of result
uID //ID flags for source, angle type, and destination
)

Parameters

lpSrc

This parameter points to an 80-bit REAL number according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL angle value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the ANG_? Flags and OR’ed with one of the DEST_? flags. (The ANG flag does not need to be OR’ed if the angle is required in degrees; that is the default type. The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
ANG_RAD The value of the angle will be returned in radians
ANG_DEG The value of the angle will be returned in degrees
(this is the default and does not need to be indicated)
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All positive and negative numbers (including the +/- infinity values) are acceptable as a source parameter for this function.
Negative values of a tangent yield negative values of the corresponding angle. If the angle is specified to be in radians, it remains as is with a value between -½π and +½π inclusive. If the angle is specified to be in degrees, the negative values are modified to the 270-360 degree range; positive values remain in the 0-90 degree range.

If the angle with the corresponding tangent is known to be in the other two quadrants (i.e. in the 90-270 degree range), a value of 180 will need to be added to the result.

Similarly, if the angle in radians needs to be adjusted for the other two quadrants, the value of π must be added to it.

Example:

In the following example, the value of the angle formed by two vectors at right angle is computed. The value of the vector opposite the angle is available as an 80-bit REAL at the TBYTE variable labeled Opp and the value of the vector adjacent to the angle is available as an 80-bit REAL at the TBYTE variable labeled Adj. The resulting angle in degrees will be stored as an 80-bit REAL at the TBYTE variable labeled Dest if no error is reported.

Invoke FpuDiv, ADDR Opp, ADDR Adj, 0, SRC1_REAL or SRC2_REAL or DEST_FPU
invoke FpuArctan, 0, ADDR Dest, SRC1_FPU

The first function computes the tangent, leaving the result on the FPU (the FPU’s ST7 data register must be free or will be trashed). The second function computes the corresponding angle and stores the result expressed in degrees.

Prepared by Raymond Filiatreault.


FpuArctanh.htm

FpuArctanh

The FpuArctanh function returns the number corresponding to a hyperbolic tangent value.

atanh(Src) → Dest

FpuArctanh (

lpSrc //pointer to the source value (Src)
lpDest //pointer to destination of result
uID //ID flags for source, and destination
)

Parameters

lpSrc

This parameter points to an 80-bit REAL number according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL hyperbolic arctangent value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter for this function must be within the range of -1 to +1 exclusive.

Example:

invoke FpuArctanh, ADDR Src, 0, SRC1_REAL or DEST_FPU

In this example, the hyperbolic arctangent of the 80-bit REAL number from the TBYTE memory variable labeled Src will be computed and remain on the FPU if the function succeeds. The 2nd parameter is ignored.

Prepared by Raymond Filiatreault.


FpuAtoFL.htm

FpuAtoFL

The FpuAtoFL converts an alphanumeric decimal string to an 80-bit REAL number.

FpuAtoFL (

lpszSrc //pointer to a null-terminated string
lpDest //pointer to destination of result
uID //ID flag for destination
)

Parameters

lpSrc

This points to the null-terminated alphanumeric decimal representation of the number. Leading spaces are disregarded. The string can be in regular notation or in scientific notation. Characters other than those allowed between the leading sign or leading digit and the terminating zero will return an immediate error. A maximum of 18 digits (excluding all leading 0's and trailing decimal 0's, and excluding the digits of the exponent in scientific notation) is permitted. The only permitted decimal delimiter is the period (“.”) but is not a necessity for integer values. Decimal digits are not a necessity either. The exponent in scientific notation can be preceded with “e” or “E”. That exponent must be within the +/-4931 range. The “+” sign is not necessary for positive exponents.

lpDest

This points to a TBYTE where the 80-bit REAL result of the conversion will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the DEST_? flags needs to be indicated.

uID Flag Meaning
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified destination will not be modified.

If the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Example:

invoke FpuAtoFL, ADDR Src, 0, DEST_FPU

In this example, the alphanumeric decimal number pointed to by the memory variable labeled Src is converted to an 80-bit REAL number and retained on the FPU.

Prepared by Raymond Filiatreault.


FpuChs.htm

FpuChs

The FpuChs function changes the sign of an 80-bit REAL number and returns it at the specified destination.

-Src → Dest

FpuChs (

lpSrc //pointer to source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This points to an 80-bit REAL number either in memory or on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the source is considered a valid REAL number, its absolute value will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified source and destination will not be modified.

When the source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If the source is not from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be an 80-bit REAL number. (The sign of integers can be changed easily with CPU instructions.)

Example:

invoke FpuChs, 0, ADDR Dest, SRC1_FPU

In this example, the 1st parameter is ignored and the sign of the 80-bit REAL value already on the FPU is changed; the result is stored at the TBYTE memory variable labeled Dest if no error is reported.

Prepared by Raymond Filiatreault.


FpuComp.htm

FpuComp

The FpuComp function compares the 1st parameter to the 2nd parameter and returns the result as coded bits in the EAX register.

FpuComp (

lpSrc1 // pointer to, or value of, 1st parameter
lpSrc2 // pointer to, or value of, 2nd parameter
uID // ID flags for sources and destination
)

Parameters

lpSrc1

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER,
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags.

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
SRC2_FPU Src2 is already on the FPU
SRC2_REAL Src2 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
SRC2_CONST Src2 is one of the special FPU constants

Return Values:

If either of the source numbers is considered invalid by the FPU, or cannot be compared such as a value of infinity, EAX returns with zero.

Otherwise, the result of the comparison is returned as a coded bit in EAX which can be tested with the following constants defined in the Fpu.inc file:

CMP_EQU Src1 = Src2 if bit is set
CMP_GREATER Src1 > Src2 if bit is set
CMP_LOWER Src1 < Src2 if bit is set

Note: The FPU performs a signed comparison; negative numbers are smaller than positive numbers. All the FPU registers and all the CPU registers (except EAX) are preserved. If one of the sources was on the FPU, its value would be preserved.

Example:

invoke FpuComp,0,10,SRC1_FPU or SRC2_DIMM
or eax,eax
jz error_handler
test eax,CMP_EQU or CMP_GREATER
jz lower_than_10

In this example, the value already on the FPU is compared to the immediate value of 10. Then EAX is first checked for a non-comparable parameter (only the value on the FPU could be at fault in this example) and the code branching off to the error handler if necessary. Finally, EAX is tested for equal-or-greater and the jump made if the value on the FPU is lower than 10.

Prepared by Raymond Filiatreault.


FPUConstants.htm

FPU Constants

Two widely used constants are made available with the FPU, the value of pi (π = 3.14159...) and the value of the Napierian constant (e = 2.71828...). They can be used where permitted as the source for a number by specifying one of the following constants instead of a pointer to a REAL10 or an integer, and identifying it as such.

Constant / Meaning

FPU_PI

The value of π. This constant needs to be used in numerous computations such as those related to the circle or sphere.

For information, that constant is loaded on the FPU with the following instruction:

fldpi

FPU_NAPIER

The value of e. Although this constant is not directly available from the FPU as the one above, it is generated internally with a few FPU instructions when specified as a parameter.

For information, that constant is loaded on the FPU with the following instructions:

fld1
fldl2e
fsub st,st(1)
f2xm1
fadd st,st(1)
fscale
fxch
fstp st(1)

Prepared by Raymond Filiatreault.


FpuCos.htm

FpuCos

The FpuCos function returns the cosine of an angle expressed in degrees or radians.

cos(Src) → Dest

FpuCos (

lpSrc //pointer to, or value of, angle (Src)
lpDest //pointer to destination of result
uID //ID flags for source, angle type, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL cosine value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the ANG_? Flags and OR’ed with one of the DEST_? flags. (The ANG flag does not need to be OR’ed if the angle is in degrees; that is the default type. The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
ANG_RAD The value of the angle is expressed in radians
ANG_DEG The value of the angle is expressed in degrees
(this is the default and does not need to be indicated)
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All positive and negative numbers except infinity are acceptable for this function.

Example:

invoke FpuCos, 0, 0, SRC1_FPU or ANG_RAD or DEST_FPU

In this example, the cosine of the 80-bit REAL already on the FPU and expressed in radians is computed and is retained on the FPU if no error is reported, both the 1st and 2nd parameters being ignored.

Prepared by Raymond Filiatreault.


FpuCosh.htm

FpuCosh

The FpuCosh function returns the hyperbolic cosine of a number.
cosh(Src) → Dest

FpuCosh (

lpSrc //pointer to, or value of, source number (Src)
lpDest //pointer to destination of result
uID //ID flags for source, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL hyperbolic cosine value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src is a pointer to a 32-bit signed integer
SRC1_DIMM Src is a 32-bit signed integer
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All positive and negative numbers are acceptable for this function. However, very large absolute values of that number may exceed the limits of the FPU and return a value of infinity. This would not be considered as an invalid operation and would not return the error code, but the returned result may not be usable with other library functions.

Example:

invoke FpuCosh, 0, 0, SRC1_FPU or DEST_FPU

In this example, the 1st and 2nd parameters are ignored and the hyperbolic cosine of the 80-bit REAL number already on the FPU will replace it if the function succeeds.

Prepared by Raymond Filiatreault.


FpuDiv.htm

FpuDiv

The FpuDiv function divides the first parameter by the second one and returns the result as an 80-bit REAL number at the specified destination.

Src1 ÷ Src2 → Dest

FpuDiv (

lpSrc1 // pointer to, or value of, 1st parameter
lpSrc2 // pointer to, or value of, 2nd parameter
lpDest // pointer to destination of result
uID // ID flags for sources and destination
)

Parameters

lpSrc1

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER,
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result of the division will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags and OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
SRC2_FPU Src2 is already on the FPU
SRC2_REAL Src2 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
SRC2_CONST Src2 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Because these library functions allow only one value to be retained on the FPU, identifying both sources as being on the FPU will result in dividing the FPU value by itself, leaving 1 as the answer.

Dividing by zero is not considered an invalid operation and would NOT return the error code 0 in EAX. However, the result is a special FPU value interpreted as equal to infinity.

Example:

invoke FpuDiv, 1, eax, ADDR Dest, SRC1_DIMM or SRC2_DIMM

In this example, the value of 1 is divided by the DWORD signed integer in EAX (yielding its reciprocal), and the result is transferred as an 80-bit REAL to the TBYTE memory variable labeled Dest.

Prepared by Raymond Filiatreault.


FpuEexpX.htm

FpuEexpX

The FpuEexpX function raises the Napierian constant e (2.71828...) by an exponent. (It could also be considered as returning the Napierian antilogarithm of a number.)

e^(Src) → Dest

FpuEexpX (

lpSrc //pointer to, or value of, exponent parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR’ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Very large positive values of the source exponent may exceed the limits of the FPU and return a value of infinity. This would not be considered as an invalid operation and would not return the error code. However that return value may not be usable with other library functions.

Example:

invoke FpuEexpX, 0, 0, SRC1_FPU or DEST_FPU

In this example, the Napierian constant e is raised by the exponent already on the FPU and the result is retained on the FPU if no error is reported. Both 1st and 2nd parameters are ignored.

Prepared by Raymond Filiatreault.


FpuExam.htm

FpuExam

The FpuExam function examines an 80-bit REAL number for its validity, its sign, a value of zero, an absolute value less than 1, and a value of infinity.

FpuExam (

lpSrc // pointer to source parameter
uID // ID flags for source
)

Parameters

lpSrc

This parameter points to an 80-bit REAL number according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

uID

Only one of the SRC1_? flags must be used.

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number

Return Values:

The result of the examination is returned as coded bits in EAX which can be tested with the following constants defined in the Fpu.inc file:

XAM_VALID Src = valid 80-bit REAL if bit is set
XAM_ZERO Src = zero if bit is set
XAM_NEG Src = negative if bit is set
XAM_SMALL Src = absolute value less than 1 if bit is set
XAM_INFINIT Src = infinity if bit is set

All the FPU registers and all the CPU registers (except EAX) are preserved. If the source was on the FPU, its value would be preserved.

Example:

invoke FpuExam, 0, SRC1_FPU
test eax,XAM_ZERO or XAM_NEG
jz above_zero

In this example, the 1st parameter is ignored and the 80-bit REAL number already on the FPU is examined, the result being returned in EAX. It is then tested to verify if equal to zero or negative and the jump taken if greater than zero.

Prepared by Raymond Filiatreault.


FpuFLtoA.htm

FpuFLtoA

The FpuFLtoA function converts an 80-bit REAL number to its alphanumeric decimal representation (either in decimal format or scientific notation) and returns the result as a null-terminated string at the specified destination.

FpuFLtoA (

lpSrc1 // pointer to an 80-bit REAL number
lpSrc2 // pointer to, or value of, 2nd parameter
lpszDest // pointer to destination of result
uID // ID flags for sources and format
)

Parameters

lpSrc1

This points to an 80-bit REAL number. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to a DWORD integer in memory, or is an immediate DWORD integer according to the content of uID.
The low byte of the low word of the integer indicates the number of decimal digits to be returned in the result. The total number of integer digits and decimal digits cannot exceed 16. Otherwise, the number of decimal digits will be reduced accordingly. (Note that at least one integer digit is always returned.)
The high byte of the low word of the integer indicates the number of characters before the decimal point, spaces being used for padding. This number cannot exceed 17 (16 digits + sign); it would otherwise be reduced to 17. (This byte is ignored if uID indicates that the result must be returned in the scientific format.)
The high word of this parameter is ignored.

lpszDest

This points to a memory location where the result will be returned. It must be large enough to hold the number of characters returned. At most, it may have 17 characters before the decimal point, one decimal point, 15 decimal digits, and the terminating zero (for a maximum of 34 characters).

uID

One of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags and OR’ed with one of the STR_? flags. (The STR_REG flag does not need to be OR’ed if the string must be returned in decimal format; that is the default.)

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
STR_REG The string must be returned in decimal format
STR_SCI The string must be returned in scientific notation

Return Values:

If the source is considered a valid REAL number, the converted string will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the null-terminated string ERROR will be placed at the specified destination.
The source is never modified, whether it is in memory or on the FPU.

If the number is greater than or equal to 1016, the result will be returned in scientific notation (with the specified number of decimals) regardless of the STR flag. If the number is equal to zero, it is simply returned as an ASCII “0" and a terminating zero regardless of the STR flag.

All the FPU registers and all the CPU registers (except EAX) are preserved.

Example:

invoke FpuFLtoA, 0, 4, ADDR Dest, SRC1_FPU or SRC2_DIMM

In this example, the 80-bit REAL number is taken from the FPU (ignoring the 1st parameter) and the null-terminated alphanumeric decimal string is written in decimal format with 4 decimal digits at the destination identified as the memory variable labeled Dest.

Prepared by Raymond Filiatreault.


FpuLnx.htm

FpuLnx

The FpuLnx function returns the natural (Napierian) logarithm of a positive non-zero number.
ln(Src) → Dest

FpuLnx (

lpSrc //pointer to, or value of, source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL logarithm will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR’ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be a positive non-zero number. Negative or zero values will result in an invalid operation and the function will fail.

Example:

invoke FpuLnx, eax, 0, SRC1_DIMM or DEST_FPU

In this example, the natural logarithm of the 32-bit signed integer in EAX is retained on the FPU as an 80-bit REAL value if no error is reported. The 2nd parameter is ignored.

Prepared by Raymond Filiatreault.


FpuLogx.htm

FpuLogx

The FpuLogx function returns the common (base 10) logarithm of a positive non-zero number.
log(Src) → Dest

FpuLogx (

lpSrc //pointer to, or value of, source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL logarithm will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR’ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be a positive non-zero number. Negative or zero values will result in an invalid operation and the function will fail.

Example:

invoke FpuLogx, ADDR Src, 0, SRC1_REAL or DEST_FPU

In this example, the common (base 10) logarithm of the 80-bit REAL identified as the memory variable labeled Scr is computed and is retained on the FPU if no error is reported. The second parameter is ignored.

Prepared by Raymond Filiatreault.


FpuMul.htm

FpuMul

The FpuMul function multiplies two numbers and returns the result as an 80-bit REAL number at the specified destination.

Src1 × Src2 → Dest

FpuMul (

lpSrc1 // pointer to, or value of, 1st parameter
lpSrc2 // pointer to, or value of, 2nd parameter
lpDest // pointer to destination of result
uID // ID flags for sources and destination
)

Parameters

lpSrc1

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER,
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result of the multiplication will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags and OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
SRC2_FPU Src2 is already on the FPU
SRC2_REAL Src2 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
SRC2_CONST Src2 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Because these library functions allow only one value to be retained on the FPU, identifying both sources as being on the FPU will result in multiplying the FPU value by itself. This may be occasionally useful.

Example:

Assuming that the radius of a circle would be an 80-bit REAL number in the TBYTE memory variable labeled Radius, the following sequence would compute the area of the circle and leave the result on the FPU:

invoke FpuMul, ADDR Radius, ADDR Radius, 0, SRC1_REAL or SRC2_REAL or DEST_FPU
invoke FpuMul, FPU_PI, 0, 0, SRC1_CONST or SRC2_FPU or DEST_FPU

Prepared by Raymond Filiatreault.


FpuRound.htm

FpuRound

The FpuRound function rounds an 80-bit REAL number to its nearest integer value.

round(Src) → Dest

FpuRound (

lpSrc //pointer to source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This points to an 80-bit REAL number either in memory or on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the result can be stored as an 80-bit REAL number or points to a DWORD where the result can be stored as a 32-bit LONG integer. The result can instead be retained on the FPU, all according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
DEST_FPU The result remains on the FPU
DEST_IMEM lpDest is a pointer to a DWORD
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the source is considered a valid REAL number, its absolute value will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified source and destination will not be modified.

When the source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If the source is not from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be an 80-bit REAL number. (Integers are already rounded.)

Example:

invoke FpuRound, 0, ADDR Dest, SRC1_FPU or DEST_IMEM

In this example, the 1st parameter is ignored and the 80-bit REAL number already on the FPU will be rounded to its nearest integer; the result will be stored as a 32-bit LONG integer at the DWORD memory variable labeled Dest.

Prepared by Raymond Filiatreault.


FpuSin.htm

FpuSin

The FpuSin function returns the sine of an angle expressed in degrees or radians.

sin(Src) → Dest

FpuSin (

lpSrc //pointer to, or value of, angle (Src)
lpDest //pointer to destination of result
uID //ID flags for source, angle type, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL sine value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the ANG_? Flags and OR’ed with one of the DEST_? flags. (The ANG flag does not need to be OR’ed if the angle is in degrees; that is the default type. The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
ANG_RAD The value of the angle is expressed in radians
ANG_DEG The value of the angle is expressed in degrees
(this is the default and does not need to be indicated)
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All positive and negative numbers except infinity are acceptable for this function.

Example:

invoke FpuSin, ADDR Src, 0, SRC1_REAL or ANG_RAD or DEST_FPU

In this example, the sine of the 80-bit REAL number at the TBYTE memory variable labeled Scr and expressed in radians is computed and is retained on the FPU if no error is reported, the 2nd parameter being ignored.

Prepared by Raymond Filiatreault.


FpuSinh.htm

FpuSinh

The FpuSinh function returns the hyperbolic sine of a number.
sinh(Src) → Dest

FpuSinh (

lpSrc //pointer to, or value of, source number (Src)
lpDest //pointer to destination of result
uID //ID flags for source, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL hyperbolic sine value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src is a pointer to a 32-bit signed integer
SRC1_DIMM Src is a 32-bit signed integer
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All positive and negative numbers are acceptable for this function. However, very large absolute values of that number may exceed the limits of the FPU and return a value of infinity. This would not be considered as an invalid operation and would not return the error code, but the returned result may not be usable with other library functions.

Example:

invoke FpuSinh, 0, ADDR Dest, SRC1_FPU

In this example, the 1st parameter is ignored and the hyperbolic sine of the 80-bit REAL number already on the FPU will be returned as an 80-bit REAL number at the TBYTE memory variable labeled Dest if no error is reported. The source operand would be removed from the FPU if the function succeeds.

Prepared by Raymond Filiatreault.


FpuSize.htm

FpuSize

The FpuSize function returns the exponent of a number as would be expressed in scientific notation.

size(Src) → Dest

FpuSize (

lpSrc //pointer to, or value of, source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This must point to a 32-bit LONG integer memory variable where the exponent will be returned.

uID

Only one of the SRC1_? flags must be used

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer

Return Values:

If the source is considered a valid non-zero number, its size will be transferred to the specified destination and EAX will be non-zero.

If the source is equal to zero, a value of -0 (80000000h) will be returned to the specified destination and EAX will be non-zero.

Otherwise, EAX will be zero and the specified destination will not be modified.

All the FPU registers and all the CPU registers (except EAX) are preserved. (If the source was on the FPU, its value on the FPU is preserved.)

Remarks:

The sign of the source number is disregarded with this function. Negative results (including the negative zero) indicate absolute values less than 1, while positive results indicate absolute values higher than 1.

Example:

invoke FpuSize, 0, ADDR Dest, SRC1_FPU

In this example, the 1st parameter is ignored and the size of the 80-bit REAL already on the FPU is returned to the LONG integer memory variable labeled Dest, the source 80-bit REAL value being preserved on the FPU if no error is reported.

Prepared by Raymond Filiatreault.


FpuSqrt.htm

FpuSqrt

The FpuSqrt function extracts the square root of a positive number.

sqrt(Src) → Dest

FpuSqrt (

lpSrc //pointer to, or value of, source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result of the square root will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR’ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be a positive number. Trying to extract the square root of a negative number on the FPU will result in an invalid operation and the function will fail.

Example:

invoke FpuSqrt, edx, 0, SRC1_DIMM or DEST_FPU

In this example, the square root of the 32-bit signed integer in EDX is extracted and the 80-bit REAL result is retained on the FPU (ignoring the 2nd parameter) if no error is reported.

Prepared by Raymond Filiatreault.


FpuState.htm

FpuState

The FpuState function returns a null-terminated string listing the content of the FPU registers to a memory buffer.

FpuState (

lpszDest // pointer to memory buffer
uID // ID flags for source
)

Parameters

lpszDest

This points to a memory location where the string will be returned. It must be large enough to hold the number of characters returned. A buffer size of 432 bytes would be sufficient.

uID

Reference number allocated by the programmer to identify the area of the code from where the function has been called.

Return Values:

All the FPU registers and all the CPU registers are preserved.

The returned string is formatted as shown below when displayed in a Message Box:

Fields in the Control Word (RC and PC) and the Status Word (TOP) which would contain more than 1 bit have those bits grouped together.

The exception flags in the Status Word have an upper case letter if set or a lower case letter if clear.

The individual tags of the Tag Word are positioned to reflect the order of the data registers based on ST0.

The IP is the address of the last FPU instruction before using this function. The OA is the address of the memory operand if one was used by that last FPU instruction. The IP may not be of much use if a separate section of a program is used to handle exceptions and prepare error reports. The uID has been included as a parameter to circumvent this problem.

Example:

invoke FpuState, ADDR Dest, 12

In this example, the formatted string would be returned in the buffer labeled Dest. The 12 could be the 12th instance of calling the function in the program (The ID is always returned in the string as a decimal number.)

Prepared by Raymond Filiatreault.


FpuSub.htm

FpuSub

The FpuSub function subtracts the second parameter from the first one and returns the result as an 80-bit REAL number at the specified destination.

Src1 - Src2 → Dest

FpuSub (

lpSrc1 // pointer to, or value of, 1st parameter
lpSrc2 // pointer to, or value of, 2nd parameter
lpDest // pointer to destination of result
uID // ID flags for sources and destination
)

Parameters

lpSrc1

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER,
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result of the subtraction will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags and OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
SRC2_FPU Src2 is already on the FPU
SRC2_REAL Src2 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
SRC2_CONST Src2 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Because these library functions allow only one value to be retained on the FPU, identifying both sources as being on the FPU will result in subtracting the FPU value from itself, leaving 0 as the answer.

Example:

invoke FpuSub, 0, 100, 0, SRC1_FPU or SRC2_DIMM or DEST_FPU

In this example, the 1st parameter is ignored because its value is identified as being on the FPU. Thus, 100 is subtracted from the 80-bit REAL number already on the FPU, and the result is retained on the FPU, ignoring the 3rd parameter.

Prepared by Raymond Filiatreault.


FpuTan.htm

FpuTan

The FpuTan function returns the tangent of an angle expressed in degrees or radians.

tan(Src) → Dest

FpuTan (

lpSrc //pointer to, or value of, angle (Src)
lpDest //pointer to destination of result
uID //ID flags for source, angle type, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL tangent value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the ANG_? Flags and OR’ed with one of the DEST_? flags. (The ANG flag does not need to be OR’ed if the angle is in degrees; that is the default type. The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
ANG_RAD The value of the angle is expressed in radians
ANG_DEG The value of the angle is expressed in degrees
(this is the default and does not need to be indicated)
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All positive and negative numbers except infinity are acceptable for this function.

Example:

invoke FpuTan, edx, ADDR Dest, SRC1_DIMM

In this example, the tangent of the 32-bit signed integer in EDX expressed in degrees is computed and transferred to the TBYTE memory variable labeled Dest if no error is reported.

Prepared by Raymond Filiatreault.


FpuTanh.htm

FpuTanh

The FpuTanh function returns the hyperbolic tangent of a number.
tanh(Src) → Dest

FpuTanh (

lpSrc //pointer to, or value of, source number (Src)
lpDest //pointer to destination of result
uID //ID flags for source, and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer, all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL hyperbolic tangent value will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src is a pointer to a 32-bit signed integer
SRC1_DIMM Src is a 32-bit signed integer
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

All valid positive and negative numbers except infinity are acceptable for this function. However, the hyperbolic tangent rapidly approaches the values of 1 (or -1) with increasing numbers. A source value of ±23 already saturates the first 19 decimal places of the result with 9's.

The hyperbolic tangent is computed from the division of two exponential numbers. If a source value larger than approx. ±11300 is used, such exponential numbers would reach a value of “infinity” and their division would result in an invalid operation.

Example:

invoke FpuTanh, ADDR Src, 0, SRC1_REAL or DEST_FPU

In this example, the hyperbolic tangent of the 80-bit REAL number from the TBYTE memory variable labeled Src will be retained on the FPU if the function succeeds. The 2nd parameter is thus ignored.

Prepared by Raymond Filiatreault.


FpuTexpX.htm

FpuTexpX

The FpuTexpX function raises the number 10 by an exponent. (It could also be considered as returning the common antilogarithm of a number.)

10^(Src) → Dest

FpuTexpX (

lpSrc //pointer to, or value of, exponent parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR’ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

Very large positive values of the source exponent may exceed the limits of the FPU and return a value of infinity. This would not be considered as an invalid operation and would not return the error code. However that return value may not be usable with other library functions.

Example:

invoke FpuTexpX, ADDR Src, 0, SRC1_REAL or DEST_FPU

In this example, the number 10 is raised by the 80-bit REAL exponent located at the TBYTE memory variable labeled Scr and the result is retained on the FPU if no error is reported. The 2nd parameter is ignored.

Prepared by Raymond Filiatreault.


FpuTrunc.htm

FpuTrunc

The FpuTrunc function returns the integer part of an 80-bit REAL number after discarding the fractional part.

trunc(Src) → Dest

FpuTrunc (

lpSrc //pointer to source parameter (Src)
lpDest //pointer to destination of result
uID //ID flags for source and destination
)

Parameters

lpSrc

This points to an 80-bit REAL number either in memory or on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the result can be stored as an 80-bit REAL number or points to a DWORD where the result can be stored as a 32-bit LONG integer. The result can instead be retained on the FPU, all according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

One of the SRC1_? flags must be OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src is already on the FPU
SRC1_REAL Src is a pointer to an 80-bit REAL number
DEST_FPU The result remains on the FPU
DEST_IMEM lpDest is a pointer to a DWORD
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the source is considered a valid REAL number, its absolute value will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified source and destination will not be modified.

When the source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If the source is not from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source parameter MUST be an 80-bit REAL number. (Integers are already truncated.)

Example:

invoke FpuTrunc, ADDR Src, 0, SRC1_REAL or DEST_FPU

In this example, the source is an 80-bit REAL at memory variable labeled Scr and its truncated value will be retained on the FPU (as an 80-bit REAL number), ignoring the 2nd parameter.

Prepared by Raymond Filiatreault.


FpuXexpY.htm

FpuXexpY

The FpuXexpY function raises a positive number (first parameter) by an exponent (second parameter).

Src1^Src2 → Dest

FpuXexpY (

lpSrc1 // pointer to, or value of, base parameter (Src1)
lpSrc2 // pointer to, or value of, exponent parameter (Src2)
lpDest // pointer to destination of result
uID // ID flags for sources and destination
)

Parameters

lpSrc1

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER (see FPU Constants for description),
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpSrc2

This either points to an 80-bit REAL number or to a DWORD integer, or is an immediate DWORD integer or one of the FPU constants:
FPU_PI or FPU_NAPIER,
all according to the content of uID. (This parameter is ignored if uID indicates that the value is already on the FPU.)

lpDest

This points to a TBYTE where the 80-bit REAL result of the function will be returned. The result can instead be retained on the FPU according to the content of uID. (This parameter is ignored if uID indicates that the result should remain on the FPU.)

uID

Only one of the SRC1_? flags must be OR'ed with only one of the SRC2_? flags and OR'ed with one of the DEST_? flags. (The DEST flag does not need to be OR’ed if the destination is in memory; that is the default destination.)

uID Flag Meaning
SRC1_FPU Src1 is already on the FPU
SRC1_REAL Src1 is a pointer to an 80-bit REAL number
SRC1_DMEM Src1 is a pointer to a 32-bit signed integer
SRC1_DIMM Src1 is a 32-bit signed integer
SRC1_CONST Src1 is one of the special FPU constants
SRC2_FPU Src2 is already on the FPU
SRC2_REAL Src2 is a pointer to an 80-bit REAL number
SRC2_DMEM Src2 is a pointer to a 32-bit signed integer
SRC2_DIMM Src2 is a 32-bit signed integer
SRC2_CONST Src2 is one of the special FPU constants
DEST_FPU The result remains on the FPU
DEST_MEM lpDest is a pointer to a TBYTE
(this is the default and does not need to be indicated)

Return Values:

If the result is considered a valid REAL number, it will be transferred to the specified destination and EAX will be non-zero.
If the function fails, EAX will be zero and the specified sources and destination will not be modified.

When a source is from the FPU, it will be discarded if the function succeeds; it would be replaced by the result when it is specified to be returned on the FPU. If no source is from the FPU but the result must be returned on the FPU, the FPU’s ST7 data register must be free; otherwise, the content of that register will be trashed in order to return the required result.

All other FPU registers and all the CPU registers (except EAX) are preserved.

Remarks:

The source base parameter MUST be a positive number. Trying to raise a negative number by an exponent on the FPU will result in an invalid operation and the function will fail.

Very large positive values of the source exponent with a source base parameter greater than 1 may exceed the limits of the FPU and return a value of infinity. A similar situation could arise with very large negative values of the source exponent coupled with a source base parameter smaller than 1. This would not be considered as an invalid operation and would not return the error code. However that return value may not be usable with other library functions.

Although the Napier constant e can be specified as the source for the base parameter, raising that constant to a power can also be performed with the FpuEexpX function.

Example:

invoke FpuXexpY, ADDR Src1, 0, ADDR Dest, SRC1_REAL or SRC2_FPU

In this example, the base number is an 80-bit REAL identified by the TBYTE memory variable labeled Src1 and the exponent is already on the FPU (ignoring the 2nd parameter). The 80-bit REAL result is returned to the TBYTE memory variable labeled Dest.

Prepared by Raymond Filiatreault.


SourcesOfErrors.htm

Sources of Errors

Errors in the use of these library functions are all reported by returning 0 in EAX. It is the user’s responsibility to verify this return code to avoid a proliferation of errors whenever in doubt. The types of errors are too numerous to provide more specific error codes or messages.

Some of the potential errors are listed below. That list must not be considered as exhaustive considering the complexity of the library and other external factors. Passed parameters must be accepted as is by the functions because of the impossibility of type checking.

The following errors will generate an immediate error code within the function.

* A source parameter is identified as being on the FPU but the FPU is empty.

* A source parameter is identified as a type not allowed by the function.

* An 80-bit REAL source parameter is not considered by the FPU as being a valid one.

* A negative number is used as a parameter for a function which only allows a positive number for that parameter.

* A source parameter has a value outside the range allowable by the function.

Some other errors may not immediately return the error code in EAX but the returned result would be erroneous, possibly generating the error code with other functions when used as a parameter.

Dividing by 0 does NOT return the error code but the result is a special valid number treated by the FPU as equal to infinity. Using that result as a source parameter for other functions may then result in the error code being generated.

A source parameter is identified as being an 80-bit REAL number or a LONG integer at the specified memory address but a different type resides at that address (while still being considered as acceptable data by the FPU). The erroneous result could thus be propagated through other functions without ever generating the error code.

Prepared by Raymond Filiatreault.

S


FPULIB liste

   FPULIB  Library

   The original FPULIB (library of floating point math functions) was primarily designed for the beginners in assembly programming who had some need for such math
   functions but did not yet have the opportunity lo learn how to use the FPU and its instructions.

   Some feedback proved that a little knowledge can be dangerous. Some users were loading too many FPU registers themselves;
   called functions then resulted in failure because an insufficient number of free registers were remaining to accomplish the task.

   A modification was issued later to blindly free any register(s) required by the function, regardless of whether it contained valid data or not.
   This would have no effect on the beginner using strictly the lib functions.
   However, it would prevent "FPU stack overflow" errors due to inadvertant use of the FPU registers by "intermediate" programmers.
   The drawback for that latter group was that valid data on the FPU could be trashed.

   Later, it also became evident that some assembler HLL instructions would blindly trash FPU registers to perform their task.
   Although such action seems to be generally accepted as "fair game", an alternate version of the FPULIB has thus been prepared to avoid such blind trashing.
   Again, this modified version would have no effect on the beginner using strictly the lib functions.
   The only drawback is the added overhead to save the FPU registers and restore them.

   This version may be an advantage for intermediate users who may want to use some of the library functions without any fear of loosing valuable data in FPU registers,
   and without any need of preserving such data themselves.

   The only restriction of that modified version was that the ST7 register must be free whenever the FPU was not the source for any input parameter AND the result was
   specified to be returned on the FPU. If that ST7 register was NOT free under those conditions, it would NOT have been trashed but the function would have failed.

   Because the primary intention of the library is to help beginners, AND beginners are not expected to know about FPU register management,
   AND the library functions don't free the registers, it became evident that preserving ALL the FPU registers was not entirely practical.
   This latest version (and hopefully the last major one) would now trash the FPU's ST7 register if,
   and only if, no input parameter is specified as being present in the FPU top register but the result is specified to remain on the FPU.

   The latest modification was a redesign of the FpuAtoFL function after it was observed that the conversion of integer strings (no decimal digits)
   may have resulted in the incorporation of a minuscule decimal difference (19th significant digit). The modified algorithm corrects that.
   However, any string with only decimal digits may still suffer that minuscule inaccuracy.

   This FPULIB21.zip file contains all necessary files, including all source code.
   The bulk of the .zip file is the HELP file which has also been updated to reflect the modifications.

          All source code ------------------------------------------------------------------------------------------------------------

          FpuAbs.asm          FpuAdd.asm          FpuArccos.asm          FpuArccosh.asm          FpuArcsin.asm          FpuArcsinh.asm
          FpuArctan.asm       FpuArctanh.asm      FpuAtoFL.asm           FpuChs.asm              FpuComp.asm            FpuCos.asm
          FpuCosh.asm         FpuDiv.asm          FpuEexpX.asm           FpuExam.asm             FpuFLtoA.asm           FpuLnx.asm
          FpuLogx.asm         FpuMul.asm          FpuRound.asm           FpuSin.asm              FpuSinh.asm            FpuSize.asm
          FpuSqrt.asm         FpuState.asm        FpuSub.asm             FpuTan.asm              FpuTanh.asm            FpuTexpX.asm
          FpuTrunc.asm        FpuXexpY.asm

          ----------------------------------------------------------------------------------------------------------------------------


FPU inc

Include file proto FPU

FpuAtoFL    PROTO :DWORD,:DWORD,:DWORD
FpuFLtoA    PROTO :DWORD,:DWORD,:DWORD,:DWORD

FpuAdd      PROTO :DWORD,:DWORD,:DWORD,:DWORD
FpuSub      PROTO :DWORD,:DWORD,:DWORD,:DWORD
FpuMul      PROTO :DWORD,:DWORD,:DWORD,:DWORD
FpuDiv      PROTO :DWORD,:DWORD,:DWORD,:DWORD
FpuSqrt     PROTO :DWORD,:DWORD,:DWORD
FpuXexpY    PROTO :DWORD,:DWORD,:DWORD,:DWORD
FpuAbs      PROTO :DWORD,:DWORD,:DWORD
FpuTrunc    PROTO :DWORD,:DWORD,:DWORD
FpuRound    PROTO :DWORD,:DWORD,:DWORD
FpuChs      PROTO :DWORD,:DWORD,:DWORD

FpuLnx      PROTO :DWORD,:DWORD,:DWORD
FpuLogx     PROTO :DWORD,:DWORD,:DWORD
FpuEexpX    PROTO :DWORD,:DWORD,:DWORD
FpuTexpX    PROTO :DWORD,:DWORD,:DWORD

FpuSin      PROTO :DWORD,:DWORD,:DWORD
FpuCos      PROTO :DWORD,:DWORD,:DWORD
FpuTan      PROTO :DWORD,:DWORD,:DWORD
FpuArcsin   PROTO :DWORD,:DWORD,:DWORD
FpuArccos   PROTO :DWORD,:DWORD,:DWORD
FpuArctan   PROTO :DWORD,:DWORD,:DWORD

FpuSinh     PROTO :DWORD,:DWORD,:DWORD
FpuCosh     PROTO :DWORD,:DWORD,:DWORD
FpuTanh     PROTO :DWORD,:DWORD,:DWORD
FpuArcsinh  PROTO :DWORD,:DWORD,:DWORD
FpuArccosh  PROTO :DWORD,:DWORD,:DWORD
FpuArctanh  PROTO :DWORD,:DWORD,:DWORD

FpuSize     PROTO :DWORD,:DWORD,:DWORD
FpuComp     PROTO :DWORD,:DWORD,:DWORD
FpuExam     PROTO :DWORD,:DWORD
FpuState    PROTO :DWORD,:DWORD

SRC1_FPU    EQU   1
SRC1_REAL   EQU   2
SRC1_DMEM   EQU   4
SRC1_DIMM   EQU   8
SRC1_CONST  EQU   16

ANG_DEG     EQU   0
ANG_RAD     EQU   32

DEST_MEM    EQU   0
DEST_IMEM   EQU   64
DEST_FPU    EQU   128

SRC2_FPU    EQU   256
SRC2_REAL   EQU   512
SRC2_DMEM   EQU   1024
SRC2_DIMM   EQU   2048
SRC2_CONST  EQU   4096

STR_REG     EQU   0
STR_SCI     EQU   32768

FPU_PI      EQU   1
FPU_NAPIER  EQU   2

XAM_VALID   EQU   1
XAM_ZERO    EQU   2
XAM_NEG     EQU   4
XAM_SMALL   EQU   8
XAM_INFINIT EQU   16

CMP_EQU     EQU   1
CMP_GREATER EQU   2
CMP_LOWER   EQU   4


FpuAbs

; #########################################################################
;
;                             FpuAbs
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           |Src| -> Dest
  ;
  ; This FpuAbs function changes the sign of an 80-bit REAL number (Src)
  ; to positive with the FPU and returns the result at the specified
  ; destination (the FPU itself or a memory location), unless an invalid
  ; operation is reported by the FPU or the definition of the parameters
  ; (with uID) is invalid.
  ;
  ; The source can only be an 80-bit REAL number from the FPU itself or
  ; from memory. (The absolute value of integers can be easily obtained
  ; with CPU instructions.)
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF the source is specified to be the FPU top data register, it would be
  ; removed. It would then be replaced by the result only if the FPU is
  ; specified as the destination.
  ;
  ; IF the source is from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuAbs proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jnz   @F                ;valid ID of source

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      mov   eax,lpSrc
      fld   tbyte ptr[eax]

dest0:
      fabs
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the other FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuAbs endp

; #########################################################################

end


FpuAdd

; #########################################################################
;
;                             FpuAdd
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU.
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           Src1 + Src2 -> Dest
  ;
  ; This FpuAdd function adds the numbers from two sources (Src1 and Src2)
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; Either of the two sources can be an 80-bit REAL number from the FPU
  ; itself or from memory, an immediate DWORD value or one in memory,
  ; or one of the FPU constants.
  ;
  ; None of the sources are checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuAdd proc public lpSrc1:DWORD, lpSrc2:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU or SRC2_FPU      ;is any data taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   src2

   @@:
      mov   eax,lpSrc1
      test  uID,SRC1_REAL     ;is Src1 an 80-bit REAL in memory?
      jz    @F                ;check next source for Src1
      fld   tbyte ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DMEM     ;is Src1 a 32-bit integer in memory?
      jz    @F                ;check next source for Src1
      fild  dword ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DIMM     ;is Src1 an immediate 32-bit integer?
      jz    @F                ;check next source for Src1
      fild  lpSrc1
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_CONST    ;is Src1 one of the FPU constants?
      jnz   @F                ;otherwise no valid ID for Src1

srcerr:
      frstor content
srcerr1:
      xor   eax,eax           ;error code
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi
      jmp   src2
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src1
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

;----------------------------------------
;check source for Src2 and load it to FPU
;----------------------------------------

src2:
      test  uID,SRC2_FPU      ;is Src2 taken from FPU?
      jz    @F                ;check next source for Src2
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process
   @@:
      mov   eax,lpSrc2
      test  uID,SRC2_REAL     ;is Src2 an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DMEM     ;is Src2 a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DIMM     ;is Src2 an immediate 32-bit integer?
      jz    @F
      fild  lpSrc2
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_CONST    ;is Src2 one of the FPU constants?
      jz    srcerr            ;no correct flag for Src2
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src2
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fadd
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU or SRC2_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuAdd endp

; #########################################################################

end


FpuArccos

; #########################################################################
;
;                             FpuArccos
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                                sqrt(1-Src^2)
  ;               acos(Src) = atan -------------  -> Dest
  ;                                     Src
  ;
  ; This FpuArccos function computes the angle in degrees or radians
  ; with the FPU corresponding to the cosine value provided in the source
  ; parameter (Src) and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory. Its absolute value must be equal to or less than 1.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuArccos proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is data taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jnz   @F                ;last valid ID of source

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      mov   eax,lpSrc
      fld   tbyte ptr[eax]

dest0:
      fld   st                ;copy cosine value
      fmul  st,st             ;cos^2
      fld1
      fsubr                   ;1-cos^2 = sin^2
      fsqrt                   ;->sin
      fxch
      fpatan                  ;i.e. arctan(sin/cos)

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,ANG_RAD
      jnz   @F                ;jump if angle is required in radians
      pushd 180
      fimul dword ptr[esp]    ;*180 degrees
      fldpi                   ;load pi (3.14159...) on FPU
      fdiv                    ;*180/pi, angle now in degrees
      pop   eax               ;clean CPU stack
@@:
      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuArccos endp

; #########################################################################

end


FpuArccosh

; #########################################################################
;
;                             FpuArccosh
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;        acosh(Src) = ln[Src + sqrt(Src^2 - 1)] -> Dest
  ;
  ; This FpuArccosh function computes the number corresponding to the
  ; hyperbolic cosine value provided in the source parameter (Src) and
  ; returns the result as an 80-bit REAL number at the specified destination
  ; (the FPU itself or a memory location), unless an invalid operation is
  ; reported by the FPU or the definition of the parameters (with uID) is
  ; invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory. The source must not
  ; be lower than 1, otherwise an invalid operation is reported.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuArccosh proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jnz   @F                ;last valid ID for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      fild  lpSrc

dest0:
      fld   st(0)             ;copy it
      fmul  st,st(0)          ;Src^2
      fld1
      fsub                    ;Src^2-1
      fsqrt                   ;sqrt(Src^2-1)
      fadd                    ;Src+sqrt(Src^2-1)
      fldln2                  ;->ln(2)=1/[log2(e)]
      fxch
      fyl2x                   ;->[log2(Src+sqrt(Src^2-1))]/[log2(e)]
                              ; = ln[Src+sqrt(Src^2-1)]

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuArccosh endp

; #########################################################################

end


FpuArcsin

; #########################################################################
;
;                             FpuArcsin
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                                  Src
  ;            asin(Src) = atan -------------  -> Dest
  ;                             sqrt(1-Src^2)
  ;
  ; This FpuArcsin function computes the angle in degrees or radians
  ; with the FPU corresponding to the sine value provided in the source
  ; parameter (Src) and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory. Its absolute value must be equal to or less than 1.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuArcsin proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0                   ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jnz   @F                ;valid ID of source

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      mov   eax,lpSrc
      fld   tbyte ptr[eax]

dest0:
      fld   st(0)             ;copy sine value
      fmul  st,st(0)          ;sin^2
      fld1
      fsubr                   ;1-sin^2 = cos^2
      fsqrt                   ;->cos
      fpatan                  ;i.e. arctan(sin/cos) = arcsin

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,ANG_RAD
      jnz   @F                ;jump if angle is required in radians
      pushd 180
      fimul dword ptr[esp]    ;*180 degrees
      fldpi                   ;load pi (3.14159...) on FPU
      fdiv                    ;*180/pi, angle now in degrees
      pop   eax               ;clean CPU stack

      ftst                    ;check for negative value
      fstsw ax                ;retrieve status word from FPU
      fwait
      sahf
      jnc   @F                ;jump if positive number
      pushd 360
      fiadd dword ptr[esp]    ;angle now 0-360
      fwait
      pop   eax               ;clean CPU stack

@@:
      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuArcsin endp

; #########################################################################

end


FpuArcsinh

; #########################################################################
;
;                             FpuArcsinh
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;        asinh(Src) = ln[Src + sqrt(Src^2 + 1)] -> Dest
  ;
  ; This FpuArcsinh function computes the number corresponding to the
  ; hyperbolic sine value provided in the source parameter (Src) and
  ; returns the result as an 80-bit REAL number at the specified destination
  ; (the FPU itself or a memory location), unless an invalid operation is
  ; reported by the FPU or the definition of the parameters (with uID) is
  ; invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuArcsinh proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jnz   @F                ;last valid ID for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      fild  lpSrc

dest0:
      fld   st(0)             ;copy it
      fmul  st,st(0)          ;Src^2
      fld1
      fadd                    ;Src^2+1
      fsqrt                   ;sqrt(Src^2+1)
      fadd                    ;Src+sqrt(Src^2+1)
      fldln2                  ;->ln(2)=1/[log2(e)]
      fxch
      fyl2x                   ;->[log2(Src+sqrt(Src^2+1))]/[log2(e)]
                              ; = ln[Src+sqrt(Src^2+1)]

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU or SRC2_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuArcsinh endp

; #########################################################################

end


FpuArctan

; #########################################################################
;
;                             FpuArctan
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                       atan(Src) -> Dest
  ;
  ; This FpuArctan function computes the angle in degrees or radians
  ; with the FPU corresponding to the tangent value provided in the source
  ; parameter (Src) and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuArctan proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jnz   @F                ;last valid ID of source

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      mov   eax,lpSrc
      fld   tbyte ptr[eax]

dest0:
      fld1
      fpatan                  ;i.e. arctan(Src/1)

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,ANG_RAD
      jnz   @F                ;jump if angle is required in radians
      pushd 180
      fimul dword ptr[esp]    ;*180 degrees
      fldpi                   ;load pi (3.14159...) on FPU
      fdiv                    ;*180/pi, angle now in degrees
      pop   eax               ;clean CPU stack

      ftst                    ;check for negative angle
      fstsw ax                ;retrieve status word from FPU
      fwait
      sahf
      jnc   @F                ;jump if positive number
      pushd 360
      fiadd dword ptr[esp]    ;angle now 0-360
      fwait
      pop   eax               ;clean CPU stack

@@:
      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuArctan endp

; #########################################################################

end


FpuArctanh

; #########################################################################
;
;                             FpuArctanh
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;         atanh(Src) = 0.5 * ln[(1+Src)/(1-Src)] -> Dest
  ;
  ; This FpuArctanh function computes the number corresponding to the
  ; hyperbolic tangent value provided in the source parameter (Src) and
  ; returns the result as an 80-bit REAL number at the specified destination
  ; (the FPU itself or a memory location), unless an invalid operation is
  ; reported by the FPU or the definition of the parameters (with uID) is
  ; invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory. The absolute value of the source must be lower than 1,
  ; otherwise an invalid operation is reported.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuArctanh proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jnz   @F                ;last valid ID of source

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      mov   eax,lpSrc
      fld   tbyte ptr[eax]

dest0:
      fld   st(0)             ;copy it
      fld1
      fadd                    ;1+Src
      fxch
      fld1
      fsubr                   ;1-Src
      fdiv                    ;(1+Src)/(1-Src)
      fldln2                  ;->ln(2)=1/[log2(e)]
      fxch
      fyl2x                   ;->[log2((1+Src)/(1-Src))]/[log2(e)]
                              ; = ln[(1+Src)/(1-Src)]
      fld1
      fchs
      fxch
      fscale                  ;->0.5*ln[(1+Src)/(1-Src)] = atanh(Src)
      fstp  st(1)             ;store over scaling factor

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operations
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuArctanh endp

; #########################################################################

end


FpuAtoFL

; #########################################################################
;
;                             FpuAtoFL
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January, 2004, to eliminate .data section and remove some
  ; redundant code.
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ; Revised December 2006 to avoid a minuscule error when processing strings
  ; which do not have any decimal digit.
  ;
  ; This FpuAtoFL function converts a decimal number from a zero terminated
  ; alphanumeric string format (Src) to an 80-bit REAL number and returns
  ; the result as an 80-bit REAL number at the specified destination (the
  ; FPU itself or a memory location), unless an invalid operation is
  ; reported by the FPU or the definition of the parameters (with uID) is
  ; invalid.
  ;
  ; The source can be a string in regular numeric format or in scientific
  ; notation. The number of digits (excluding all leading 0's and trailing
  ; decimal 0's) must not exceed 18. If in scientific format, the exponent
  ; must be within +/-4931
  ;
  ; The source is checked for validity. The procedure returns an error if
  ; a character other than those acceptable is detected prior to the
  ; terminating zero or the above limits are exceeded.
  ;
  ; This procedure is based on converting the digits into a specific packed
  ; decimal format which can be used by the FPU and then adjusted for an
  ; exponent of 10.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuAtoFL proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE
LOCAL bcdstrf      :TBYTE
LOCAL bcdstri      :TBYTE

      fsave content

      push  ebx
      push  ecx
      push  edx
      push  esi
      push  edi
      xor   eax,eax
      xor   ebx,ebx
      xor   edx,edx
      lea   edi,bcdstri
      stosd
      stosd
      stosd
      stosd
      stosd
      lea   edi,bcdstri+8
      mov   esi,lpSrc
      mov   ecx,18
   @@:
      lodsb
      cmp   al," "
      jz    @B                ;eliminate leading spaces
      or    al,al             ;is string empty?
      jnz   @F

atoflerr:
      frstor content
atoflerr1:
      xor   eax,eax
      pop   edi
      pop   esi
      pop   edx
      pop   ecx
      pop   ebx
      ret

;----------------------
;check for leading sign
;----------------------

   @@:
      cmp   al,"+"
      jz    @F
      cmp   al,"-"
      jnz   integer
      mov   ah,80h
   @@:
      mov   [edi+1],ah        ;put sign byte in bcd strings
      mov   [edi+11],ah
      xor   eax,eax
      lodsb

;--------------------------------------------
;convert the integer digits to packed decimal
;--------------------------------------------

integer:
      cmp   al,"."
      jnz   @F
      lea   edi,bcdstri
      call  load_integer
      lodsb
      lea   edi,bcdstrf+8
      mov   cl,18
      and   bh,4
      jmp   decimals

   @@:
      cmp   al,"e"
      jnz   @F
      .if   cl == 18
            jmp   atoflerr    ;error if no digit other than 0 before e
      .endif
      lea   edi,bcdstri
      call  load_integer
      jmp   scient

   @@:
      cmp   al,"E"
      jnz   @F
      .if   cl == 18
            jmp   atoflerr    ;error if no digit other than 0 before E
      .endif
      lea   edi,bcdstri
      call  load_integer
      jmp   scient

   @@:
      or    al,al
      jnz   @F
      test  bh,4
      jz    atoflerr          ;error if no numerical digit before terminating 0
      lea   edi,bcdstri
      call  load_integer
      jmp   laststep

   @@:
      sub   al,"0"
      jc    atoflerr          ;unacceptable character
      jnz   @F
      test  bh,2
      jnz   @F
      or    bh,4              ;at least 1 numerical character
      lodsb
      jmp   integer
   @@:
      cmp   al,9
      ja    atoflerr          ;unacceptable character
      or    bh,6              ;at least 1 non-zero numerical character
      sub   ecx,1
      jc    atoflerr          ;more than 18 integer digits
      mov   ah,al

      lodsb
      cmp   al,"."
      jnz   @F
      mov   al,0
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstri
      call  load_integer
      lea   edi,bcdstrf+8
      mov   cl,18
      and   bh,4
      lodsb
      jmp   decimals

   @@:
      cmp   al,"e"
      jnz   @F
      mov   al,0
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstri
      call  load_integer
      jmp   scient

   @@:
      cmp   al,"E"
      jnz   @F
      mov   al,0
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstri
      call  load_integer
      jmp   scient

   @@:
      or    al,al
      jnz   @F
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstri
      call  load_integer
      jmp   laststep

   @@:
      sub   al,"0"
      jc    atoflerr          ;unacceptable character
      cmp   al,9
      ja    atoflerr          ;unacceptable character
      dec   ecx
      rol   al,4
      ror   ax,4
      mov   [edi],al
      dec   edi
      lodsb
      jmp   integer

;--------------------------------------------
;convert the decimal digits to packed decimal
;--------------------------------------------

decimals:
      cmp   al,"e"
      jnz   @F
      lea   edi,bcdstrf
      call  load_decimal
      jmp   scient

   @@:
      cmp   al,"E"
      jnz   @F
      lea   edi,bcdstrf
      call  load_decimal
      jmp   scient

   @@:
      or    al,al
      jnz   @F
      test  bh,4
      jz    atoflerr          ;error if no numerical digit before terminating 0
      lea   edi,bcdstrf
      call  load_decimal
      jmp   laststep

   @@:
      sub   al,"0"
      jc    atoflerr          ;unacceptable character
      cmp   al,9
      ja    atoflerr          ;unacceptable character
      or    bh,4              ;at least 1 numerical character
      .if   al != 0
            or    bh,2
      .endif
      sub   ecx,1
      jnc   @F
      .if   al == 0           ;if trailing decimal 0
            inc   ecx
            lodsb
            jmp   decimals
      .endif
      jmp   atoflerr
   @@:
      mov   ah,al

decimal1:
      lodsb
      cmp   al,"e"
      jnz   @F
      mov   al,0
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstrf
      call  load_decimal
      jmp   scient

   @@:
      cmp   al,"E"
      jnz   @F
      mov   al,0
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstrf
      call  load_decimal
      jmp   scient

   @@:
      or    al,al
      jnz   @F
      test  bh,4
      jz    atoflerr          ;error if no numerical digit before terminating 0
      mov   al,0
      ror   ax,4
      mov   [edi],al
      lea   edi,bcdstrf
      call  load_decimal
      jmp   laststep

   @@:
      sub   al,"0"
      jc    atoflerr          ;unacceptable character
      cmp   al,9
      ja    atoflerr          ;unacceptable character
      .if   al != 0
            or    bh,2        ;at least one non-zero decimal digit
      .endif
      dec   ecx
      rol   al,4
      ror   ax,4
      mov   [edi],al
      dec   edi
      lodsb
      jmp   decimals

laststep:
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    atoflerr          ;clean-up and return error

laststep2:
      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store result temporarily

restore:
      frstor  content         ;restore all previous FPU registers
      jz    @F
      ffree st(7)             ;free it if not already empty
      fld   tempst
   @@:
      or    al,1              ;to insure EAX!=0
   @@:
      pop   edi
      pop   esi
      pop   edx
      pop   ecx
      pop   ebx
      ret

scient:
      xor   eax,eax
      xor   edx,edx
      lodsb
      cmp   al,"+"
      jz    @F
      cmp   al,"-"
      jnz   scient1
      stc
      rcr   eax,1     ;keep sign of exponent in most significant bit of EAX
   @@:
      lodsb                   ;get next digit after sign

scient1:
      push  eax
      and   eax,0ffh
      jnz   @F        ;continue if 1st byte of exponent is not terminating 0

scienterr:
      pop   eax
      jmp   atoflerr          ;no exponent

   @@:
      sub   al,30h
      jc    scienterr         ;unacceptable character
      cmp   al,9
      ja    scienterr         ;unacceptable character
      add   edx,edx           ;x2
      lea   edx,[edx+edx*4]   ;x2x5=x10
      add   edx,eax
      cmp   edx,4931
      ja    scienterr         ;exponent too large
      lodsb
      or    al,al
      jnz   @B
      pop   eax               ;retrieve exponent sign flag
      rcl   eax,1             ;is most significant bit set?
      jnc   @F
      neg   edx
   @@:
      call  XexpY
      fmul
      jmp   laststep

FpuAtoFL endp

; #########################################################################

;put 10 to the proper exponent (value in EDX) on the FPU

XexpY:
      push  edx
      fild  dword ptr[esp]    ;load the exponent
      fldl2t                  ;load log2(10)
      fmul                    ;->log2(10)*exponent
      pop   edx

;at this point, only the log base 2 of the 10^exponent is on the FPU
;the FPU can compute the antilog only with the mantissa
;the characteristic of the logarithm must thus be removed

      fld   st(0)             ;copy the logarithm
      frndint                 ;keep only the characteristic
      fsub  st(1),st          ;keeps only the mantissa
      fxch                    ;get the mantissa on top

      f2xm1                   ;->2^(mantissa)-1
      fld1
      fadd                    ;add 1 back

;the number must now be readjusted for the characteristic of the logarithm

      fscale                  ;scale it with the characteristic

;the characteristic is still on the FPU and must be removed

      fstp  st(1)             ;clean-up the register
      ret

;##########################################################################

;shifts the packed BCD string of the integers to the integer position
;EDI points to the BCD string
;ECX = count of positions for shifting the BCD string

load_integer:
      push  esi
      .if   cl == 18
            fldz
      .else
            mov   esi,edi
            sub   ecx,18
            neg   ecx
            shr   ecx,1
            push  edi
            .if   !CARRY?     ;even number of integer digits
                  mov   edx,9
                  sub   edx,ecx
                  add   esi,edx
                  rep   movsb
            .else             ;odd number of integer digits
                  mov   edx,8
                  sub   edx,ecx
                  add   esi,edx
                  xor   eax,eax
                  lodsb
                  rol   ax,4
                  test  ecx,ecx
                  .if   !ZERO?
                     @@:
                        rol   ah,4
                        lodsb
                        rol   ax,4
                        stosb
                        dec   ecx
                        jnz   @B
                  .endif
                  mov   [edi],ah
                  inc   edi
            .endif
            mov   ecx,edx
            xor   eax,eax
            rep   stosb
            pop   edi
            fbld  tbyte ptr[edi]
      .endif
      pop   esi
      ret

;###########################################################################

;converts the decimal portion in the packed BCD string to binary
;EDI points to the BCD string

load_decimal:
      test  bh,2
      jnz   @F
      ret
   @@:
      .if   cl == 18
            fldz
      .else
            fbld  tbyte ptr[edi]
            mov   edx,-18
            call  XexpY
            fmul
      .endif
      fadd
      ret

;###########################################################################

end


FpuChs

; #########################################################################
;
;                             FpuChs
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           -(Src) -> Dest
  ;
  ; This FpuChs function changes the sign of an 80-bit REAL number (Src)
  ; and returns the result at the specified destination (the FPU itself
  ; or a TBYTE memory variable), unless an invalid operation is reported
  ; by the FPU or the definition of the parameters (with uID) is invalid.
  ;
  ; The source can only be an 80-bit REAL number from the FPU itself or
  ; from memory. (The sign of integers can be changed easily with a CPU
  ; instruction.)
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuChs proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jnz   @F                ;valid ID of source

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      mov   eax,lpSrc
      fld   tbyte ptr[eax]

dest0:
      fchs
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuChs endp

; #########################################################################

end


FpuComp

; #########################################################################
;
;                             FpuComp
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ;
  ; This FpuComp function compares one number (Src1) to another (Src2)
  ; with the FPU and returns the result in EAX as coded bits:
  ;         EAX = 0     comparison impossible
  ;         bit 0       1 = Src1 = Src2
  ;         bit 1       1 = Src1 > Src2
  ;         bit 2       1 = Src1 < Src2
  ;
  ; Either of the two sources can be an 80-bit REAL number from the FPU
  ; itself or from memory, an immediate DWORD value or one in memory,
  ; or one of the FPU constants. If one of the sources is from the FPU,
  ; its value will be preserved if no error is reported.
  ;
  ; None of the sources are checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return the result. All other CPU registers are
  ; preserved. All FPU registers are also preserved.
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuComp proc public lpSrc1:DWORD, lpSrc2:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU or SRC2_FPU      ;is data taken from FPU?
      jz    @F                ;continue if not

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   @F                ;not empty if Carry flag not set
      jpe   @F                ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

   @@:
      fsave content

;----------------------------------------
;check source for Src1 and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   src2

   @@:
      mov   eax,lpSrc1
      test  uID,SRC1_REAL     ;is Src1 an 80-bit REAL in memory?
      jz    @F                ;check next source for Src1
      fld   tbyte ptr [eax]
      jmp   src2              ;check next parameter for Src2

@@:
      test  uID,SRC1_DMEM     ;is Src1 a 32-bit integer in memory?
      jz    @F                ;check next source for Src1
      fild  dword ptr [eax]
      jmp   src2              ;check next parameter for Src2

@@:
      test  uID,SRC1_DIMM     ;is Src1 an immediate 32-bit integer?
      jz    @F                ;check next source for Src1
      fild  lpSrc1
      jmp   src2              ;check next parameter for Src2

@@:
      test  uID,SRC1_CONST    ;is Src1 one of the FPU constants?
      jnz   @F                ;otherwise no valid ID for Src1

srcerr:
      frstor content
srcerr1:
      xor   eax,eax           ;error code
      ret

@@:
      test  eax,FPU_PI
      jz    @F
      fldpi
      jmp   src2
@@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src1
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

;----------------------------------------
;check source for Src2 and load it to FPU
;----------------------------------------

src2:
      test  uID,SRC2_FPU      ;is Src2 taken from FPU?
      jz    @F                ;check next source for Src2
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

@@:
      mov   eax,lpSrc2
      test  uID,SRC2_REAL     ;is Src2 an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC2_DMEM     ;is Src2 a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC2_DIMM     ;is Src2 an immediate 32-bit integer?
      jz    @F
      fild  lpSrc2
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC2_CONST    ;is Src2 one of the FPU constants?
      jz    srcerr            ;no correct flag for Src2
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
@@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src2
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fxch
      fcom
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return result
      sahf                    ;transfer to the CPU flags
      jpe   srcerr            ;error if non comparable
      ja    greater
      jc    @F
      mov   eax,CMP_EQU       ;Src2 = Src1
      jmp   finish

   @@:
      mov   eax,CMP_LOWER     ;Src1 < Src2
      jmp   finish

greater:
      mov   eax,CMP_GREATER   ;Src1 > Src2

finish:
      frstor content

      ret

FpuComp endp

; #########################################################################

end


FpuCos

; #########################################################################
;
;                             FpuCos
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January 2004 to prevent stack faults and to adjust
  ; angles outside the acceptable range if necessary.
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           cos(Src) -> Dest
  ;
  ; This FpuCos function computes the cosine of an angle in degrees or radians
  ; (Src) with the FPU and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuCos proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process
   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]               ;lpSrc2
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      fild  lpSrc

dest0:
      test  uID,ANG_RAD
      jnz   @F                ;jump if angle already in radians
      fldpi                   ;load pi (3.14159...) on FPU
      fmul
      pushd 180
      fidiv word ptr[esp]     ;value now in radians
      fwait
      pop   eax               ;clean the stack
   @@:
      fldpi
      fadd  st,st             ;->2pi
      fxch
   @@:
      fprem                   ;reduce the angle
      fcos
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error
      sahf                    ;transfer to the CPU flags
      jpe   @B                ;reduce angle again if necessary
      fstp  st(1)             ;get rid of the 2pi

   @@:
      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuCos endp

; #########################################################################

end


FpuCosh

; #########################################################################
;
;                             FpuCosh
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;     cosh(Src) = [e^(Src) + e^(-Src)]/2 -> Dest    (see FpuEexpX for e^x)
  ;
  ; This FpuCosh function computes the hyperbolic cosine of a number (Src)
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuCosh proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process
   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      fild  lpSrc

dest0:
      fldl2e
      fmul                    ;log2(e)*Src
      fld   st(0)
      frndint
      fxch
      fsub  st,st(1)
      f2xm1
      fld1
      fadd
      fscale                  ;-> antilog[log2(e)*Src] = e^(Src)
      fstp  st(1)             ;get rid of scaling factor

      fld   st                ;copy it to get the reciprocal
      fld1
      fdivrp st(1),st         ;1/e^(Src) = e^(-Src)
      fadd                    ;e^(Src) + e^(-Src)
      fld1
      fchs                    ;-1
      fxch
      fscale                  ;-> [e^(Src) + e^(-Src)]/2 = cosh(Src)
      fstp  st(1)             ;get rid of scaling factor

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuCosh endp

; #########################################################################

end


FpuDiv

; #########################################################################
;
;                             FpuDiv
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           Src1 / Src2 -> Dest
  ;
  ; This FpuDiv function divides the Src1 number by the Src2 number
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; Either of the two sources can be an 80-bit REAL number from the FPU
  ; itself or from memory, an immediate DWORD value or one in memory,
  ; or one of the FPU constants.
  ;
  ; None of the sources are checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuDiv proc public lpSrc1:DWORD, lpSrc2:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU or SRC2_FPU      ;is data taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   src2

   @@:
      mov   eax,lpSrc1
      test  uID,SRC1_FPU      ;is Src1 taken from FPU?
      jnz   src2              ;check next parameter for Src2

      test  uID,SRC1_REAL     ;is Src1 an 80-bit REAL in memory?
      jz    @F                ;check next source for Src1
      fld   tbyte ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DMEM     ;is Src1 a 32-bit integer in memory?
      jz    @F                ;check next source for Src1
      fild  dword ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DIMM     ;is Src1 an immediate 32-bit integer?
      jz    @F                ;check next source for Src1
      fild  lpSrc1
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_CONST    ;is Src1 one of the FPU constants?
      jnz   @F                ;otherwise no valid ID for Src1

srcerr:
      frstor content
srcerr1:
      xor   eax,eax           ;error code
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi
      jmp   src2
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src1
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

;----------------------------------------
;check source for Src2 and load it to FPU
;----------------------------------------

src2:
      test  uID,SRC2_FPU      ;is Src2 taken from FPU?
      jz    @F                ;check next source for Src2
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process
   @@:
      mov   eax,lpSrc2
      test  uID,SRC2_REAL     ;is Src2 an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DMEM     ;is Src2 a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DIMM     ;is Src2 an immediate 32-bit integer?
      jz    @F
      fild  lpSrc2
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_CONST    ;is Src2 one of the FPU constants?
      jz    srcerr            ;no correct flag for Src2
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src2
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fdiv
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;destination is the FPU
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU or SRC2_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuDiv endp

; #########################################################################

end


FpuEexpX

; #########################################################################
;
;                             FpuEexpX
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;              e^(Src) = antilog2[ log2(e) * Src ] -> Dest
  ;
  ; This FpuEexpX function computes the Naperian antilogarithm of a number.
  ; It raises the Naperian constant to the power of the Src number
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The exponent can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory, or one of the FPU
  ; constants.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuEexpX proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    @F
      fild  lpSrc
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_CONST    ;is Src one of the FPU constants?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fldl2e                  ;->log2(e)
      fmul                    ;->log2(e)*Src

;the FPU can compute the antilog only with the mantissa
;the characteristic of the logarithm must thus be removed

      fld   st(0)             ;copy the logarithm
      frndint                 ;keep only the characteristic
      fsub  st(1),st          ;keeps only the mantissa
      fxch                    ;get the mantissa on top

      f2xm1                   ;->2^(mantissa)-1
      fld1
      fadd                    ;add 1 back

;the number must now be readjusted for the characteristic of the logarithm

      fscale                  ;scale it with the characteristic

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return if error

;the characteristic is still on the FPU and must be removed

      fstp  st(1)             ;get rid of the characteristic

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuEexpX endp

; #########################################################################

end


FpuExam

; #########################################################################
;
;                             FpuExam
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ;
  ; This FpuExam function examines an 80-bit REAL number (Src) for its
  ; validity, its sign, a value of zero, an absolute value less than 1, and
  ; a value of infinity.
  ; The result is returned in EAX as coded bits:
  ;         EAX = 0     invalid number
  ;         bit 0       1 = valid number
  ;         bit 1       1 = number is equal to zero
  ;         bit 2       1 = number is negative
  ;         bit 3       1 = number less than 1 but not zero
  ;         bit 4       1 = number is infinity
  ; If the source was on the FPU, it will be preserved if no error is
  ; reported.
  ;
  ; The source can only be an 80-bit REAL number from the FPU itself or
  ; from memory.
  ;
  ; Only EAX is used to return the result. All other CPU registers are
  ; preserved. All FPU registers are also preserved.
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuExam proc public lpSrc:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      push  edx
      test  uID,SRC1_FPU      ;is data taken from FPU?
      jz    @F                ;continue if not

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   @F                ;not empty if Carry flag not set
      jpe   @F                ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

   @@:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    srcerr            ;no proper source identificaiton
      mov   eax,lpSrc
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      xor   edx,edx
      ftst                    ;test number
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;invalid operation?
      jc    srcerr

      sahf                    ;transfer flags to CPU flag register
      jnz   @F                ;if not 0 value or NAN
      jc    examine           ;go check for infinity or NAN value
      or    edx,XAM_ZERO or XAM_SMALL
      jmp   finish            ;no need for checking for sign or size if 0

   @@:
      jnc   @F                ;number is not negative
      or    edx,XAM_NEG

;check for size smaller than 1 by comparing the absolute value to 1

   @@:
      fabs                    ;make sure it is positive
      fld1                    ;for comparing to 1
      fcompp                  ;compare 1 to absolute value and pop both
      fstsw ax                ;retrieve result flags from FPU
      fwait
      sahf                    ;transfer flags to CPU flag register
      jc    finish            ;src>1
      jz    finish            ;src=1
      or    edx,XAM_SMALL     ;value less than 1

finish:
      frstor content
      mov   eax,edx
      pop   edx               ;retrieve its original value
      or    al,1              ;to indicate source was a valid number
      ret

examine:                      ;strictly for infinity value
      fxam
      fstsw ax                ;retrieve result of fxam
      fwait
      sahf                    ;transfer flags to CPU flag register
      jpo   srcerr            ;must be NAN
      or    edx,XAM_INFINIT
      jmp   finish

FpuExam endp

; #########################################################################

end


FpuFLtoA

; #########################################################################
;
;                             FpuFLtoA
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; and modified April 2003. A minor flaw was corrected in July 2003.
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ;
  ; This FpuFLtoA function converts an 80-bit REAL number (Src) to its
  ; decimal representation as a zero terminated alphanumeric string which
  ; is returned at the specified memory destination unless an invalid
  ; operation is reported by the FPU or the definition of the parameters
  ; (with uID) is invalid.
  ;
  ; The format of the string can be specified as regular (default) or
  ; scientific notation. The number of decimal places returned must also be
  ; specified but the total number of significant digits must not exceed 16.
  ; When the regular format is specified, the integer portion can also be
  ; padded with preceding spaces to position the decimal point at a
  ; specified location from the start of the string.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; This procedure is based on using an FPU instruction to convert the
  ; REAL number into a specific packed decimal format. After unpacking,
  ; the decimal point is positioned as required.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved. All FPU registers are preserved.
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

      .code

; #########################################################################

FpuFLtoA proc public lpSrc:DWORD, lpDecimal:DWORD, lpDest:DWORD, uID:DWORD

LOCAL tempdw       :DWORD
LOCAL esize        :DWORD
LOCAL Padding      :DWORD
LOCAL Decimal      :DWORD
LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE
LOCAL bcdstr       :TBYTE
LOCAL oldcw        :WORD
LOCAL truncw       :WORD
LOCAL unpacked[20] :BYTE

;get the specified number of decimals for result
;and make corrections if necessary

      mov   eax,lpDecimal
      test  uID,SRC2_DMEM
      jz    @F
      mov   eax,[eax]         ;get the decimals from memory
   @@:
      push  eax
      movzx eax,al            ;low byte - number of decimal digits
      cmp   eax,15
      jbe   @F
      mov   eax,15            ;a maximum of 15 decimals is allowed
   @@:
      mov   Decimal,eax
      pop   eax
      movzx eax,ah            ;2nd byte - number of char before decimal point
      cmp   eax,17
      jbe   @F
      mov   eax,17            ;a maximum of 17 characters is allowed
   @@:
      mov   Padding,eax

      test  uID,SRC1_FPU      ;is data taken from FPU?
      jz    @F                ;continue if not

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   @F                ;not empty if Carry flag not set
      jpe   @F                ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

   @@:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    srcerr            ;no proper source identificaiton
      mov   eax,lpSrc
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      push  edi
      mov   edi,lpDest
      mov   eax,"ORRE"
      stosd
      mov   ax,"R"
      stosw
      pop   edi
      xor   eax,eax
      ret

dest0:

;-------------------------------------------
;first examine the value on FPU for validity
;-------------------------------------------

      fxam                    ;examine value on FPU
      fstsw ax                ;get result
      fwait
      sahf                    ;transfer to CPU flags
      jz    maybezero
      jpo   srcerr            ;C3=0 and C2=0 would be NAN or unsupported
      jnc   getnumsize        ;continue if normal finite number

;--------------------------------
;value to be converted = INFINITY
;--------------------------------

      push  ecx
      push  esi
      push  edi
      mov   edi,lpDest
      mov   al,"+"
      test  ah,2              ;C1 field = sign
      jz    @F
      mov   al,"-"
   @@:
      stosb
      mov   eax,"IFNI"
      stosd
      mov   eax,"YTIN"
      stosd
      jmp   finish

;-------------------------
;value to be converted = 0
;-------------------------

maybezero:
      jpe   getnumsize        ;would be denormalized number
      fstp  st(0)             ;flush that 0 value off the FPU
      push  ecx
      push  esi
      push  edi
      mov   edi,lpDest
      test  uID,STR_SCI       ;scientific notation?
      jnz   @F                ;no padding
      mov   ecx,Padding
      sub   ecx,2
      jle   @F                ;no padding specified or necessary
      mov   al," "
      rep   stosb
   @@:
      mov   ax,3020h          ;" 0" szstring
      stosw                   ;write it
      jmp   finish

;---------------------------
; get the size of the number
;---------------------------

getnumsize:
      fldlg2                  ;log10(2)
      fld   st(1)             ;copy Src
      fabs                    ;insures a positive value
      fyl2x                   ;->[log2(Src)]*[log10(2)] = log10(Src)

      fstcw oldcw             ;get current control word
      fwait
      mov   ax,oldcw
      or    ax,0c00h          ;code it for truncating
      mov   truncw,ax
      fldcw truncw            ;insure rounding code of FPU to truncating

      fist  esize             ;store characteristic of logarithm
      fldcw oldcw             ;load back the former control word

      ftst                    ;test logarithm for its sign
      fstsw ax                ;get result
      fwait
      sahf                    ;transfer to CPU flags
      sbb   esize,0           ;decrement esize if log is negative
      fstp  st(0)             ;get rid of the logarithm

;-----------------------------------------------------------------------
; get the power of 10 required to generate an integer with the specified
; number of significant digits
;-----------------------------------------------------------------------

      mov   eax,uID
      and   eax,STR_SCI
      .if   eax == 0          ;regular decimal notation
            mov   eax,esize
            or    eax,eax     ;check if number is < 1
            js    @F
            .if   eax > 15    ;if number is >= 10^16
                  or    uID,STR_SCI ;switch to scientific notation
                  mov   Decimal,15  ;insures 15 decimal places in result
                  jmp   scific
            .endif
            add   eax,Decimal
            .if   eax > 15    ;if integer + decimal digits > 16
                  sub   eax,15
                  sub   Decimal,eax ;reduce decimal digits as required
            .endif
         @@:
            push  Decimal
            pop   tempdw
      .else                   ;scientific notation
      scific:
            mov   eax,Decimal
            sub   eax,esize
            mov   tempdw,eax
      .endif

;----------------------------------------------------------------------------------------
; multiply the number by the power of 10 to generate required integer and store it as BCD
;----------------------------------------------------------------------------------------

.if   tempdw != 0
      fild  tempdw
      fldl2t
      fmul                    ;->log2(10)*exponent
      fld   st(0)
      frndint                 ;get the characteristic of the log
      fxch
      fsub st,st(1)           ;get only the fractional part but keep the characteristic
      f2xm1                   ;->2^(fractional part)-1
      fld1
      fadd                    ;add 1 back
      fscale                  ;re-adjust the exponent part of the REAL number
      fstp  st(1)             ;get rid of the characteristic of the log
      fmul                    ;->16-digit integer
.endif

      fbstp bcdstr            ;->TBYTE containing the packed digits
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

;------------------------------------------------------------------------------
; unpack BCD, the 10 bytes returned by the FPU being in the little-endian style
;------------------------------------------------------------------------------

      push  ecx
      push  esi
      push  edi
      lea   esi,bcdstr+9      ;go to the most significant byte (sign byte)
      lea   edi,unpacked
      mov   eax,3020h
      mov   cl,byte ptr[esi]  ;sign byte
      .if   cl == 80h
            mov   al,"-"      ;insert sign if negative number
      .endif
      stosw
      mov   ecx,9
   @@:
      dec   esi
      movzx eax,byte ptr[esi]
      ror   ax,4
      ror   ah,4
      add   ax,3030h
      stosw
      dec   ecx
      jnz   @B

      mov   edi,lpDest
      lea   esi,unpacked
      test  uID,STR_SCI       ;scientific notation?
      jnz   scientific

;************************
; REGULAR STRING NOTATION
;************************

;------------------------------
; check if padding is specified
;------------------------------

      mov   ecx,Padding
      or    ecx,ecx           ;check if padding is specified
      jz    nopadding

      mov   edx,2             ;at least 1 integer + sign
      mov   eax,esize
      or    eax,eax
      js    @F                ;only 1 integer digit if size is < 1
      add   edx,eax           ;->number of integer digits
   @@:
      sub   ecx,edx
      jle   nopadding
      mov   al," "
      rep   stosb

nopadding:
      pushfd                  ;save padding flags
      movsb                   ;insert sign
      mov   ecx,1             ;at least 1 integer digit
      mov   eax,esize
      or    eax,eax           ;is size negative (i.e. number smaller than 1)
      js    @F
      add   ecx,eax
   @@:
      mov   eax,Decimal
      add   eax,ecx           ;->total number of digits to be displayed
      sub   eax,19
      sub   esi,eax           ;address of 1st digit to be displayed
      pop   eax               ;retrieve padding flags in EAX
      .if   byte ptr[esi-1] == "1"
            dec   esi
            inc   ecx
            push  eax         ;transfer padding flags through stack
            popfd             ;retrieve padding flags
            jle   @F          ;no padding was necessary
            dec   edi         ;adjust for one less padding byte
      .endif
   @@:
      rep   movsb             ;copy required integer digits
      mov   ecx,Decimal
      or    ecx,ecx
      jz    @F
      mov   al,"."
      stosb
      rep   movsb             ;copy required decimal digits
   @@:
      jmp   finish

;********************
; SCIENTIFIC NOTATION
;********************

scientific:
      movsb                   ;insert sign
      mov   ecx,Decimal
      mov   eax,18
      sub   eax,ecx
      add   esi,eax
      cmp   byte ptr[esi-1],"1"
      pushfd                  ;save flags for extra "1"
      jnz   @F
      dec   esi
   @@:
      movsb                   ;copy the integer
      mov   al,"."
      stosb
      rep   movsb             ;copy the decimal digits

      mov   al,"E"
      stosb
      mov   al,"+"
      mov   ecx,esize
      popfd                   ;retrieve flags for extra "1"
      jnz   @F                ;no extra "1"
      inc   ecx               ;adjust exponent
   @@:
      or    ecx,ecx
      jns   @F
      mov   al,"-"
      neg   ecx               ;make number positive
   @@:
      stosb                   ;insert proper sign

;Note: the absolute value of the size could not exceed 4931

      mov   eax,ecx
      mov   cl,100
      div   cl                ;->thousands & hundreds in AL, tens & units in AH
      push  eax
      and   eax,0ffh          ;keep only the thousands & hundreds
      mov   cl,10
      div   cl                ;->thousands in AL, hundreds in AH
      add   ax,3030h          ;convert to characters
      stosw                   ;insert them
      pop   eax
      shr   eax,8             ;get the tens & units in AL
      div   cl                ;tens in AL, units in AH
      add   ax,3030h          ;convert to characters
      stosw                   ;insert them

finish:
      xor   eax,eax
      stosb                   ;string terminating 0
      pop   edi
      pop   esi
      pop   ecx

      frstor content

      or    al,1              ;to insure EAX!=0
      ret

FpuFLtoA endp

; #########################################################################

end


FpuLnx

; #########################################################################
;
;                             FpuLnx
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                ln(Src) = log2(Src) * ln(2) -> Dest
  ;
  ; This FpuLnx function computes the natural logarithm of a number (Src)
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory, or one of the
  ; FPU constants. Negative or zero values of Src will return an error.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuLnx proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    @F
      fild  lpSrc
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_CONST    ;is Src one of the FPU constants?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

@@:
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
@@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fldln2
      fxch
      fyl2x                   ;->[log2(Src)]*ln(2) = ln(Src)

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuLnx endp

; #########################################################################

end


FpuLogx

; #########################################################################
;
;                             FpuLogx
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;               log(Src) = log2(Src) * log10(2) -> Dest
  ;
  ; This FpuLog function computes the logarithm base 10 of a number (Src)
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory, or one of the
  ; FPU constants. Negative or zero values of Src will return an error.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuLogx proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    @F
      fild  lpSrc
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_CONST    ;is Src one of the FPU constants?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fldlg2
      fxch
      fyl2x                   ;->[log2(Src)]*log10(2) = log(Src) base 10

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
@@:
      or    al,1              ;to insure EAX!=0
      ret

FpuLogx endp

; #########################################################################

end


FpuMul

; #########################################################################
;
;                             FpuMul
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           Src1 * Src2 -> Dest
  ;
  ; This FpuMul function multiplies the numbers from two sources (Src1 and
  ; Src2) with the FPU and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; Either of the two sources can be an 80-bit REAL number from the FPU
  ; itself or from memory, an immediate DWORD value or one in memory,
  ; or one of the FPU constants.
  ;
  ; None of the sources are checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuMul proc public lpSrc1:DWORD, lpSrc2:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU or SRC2_FPU      ;is data taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   src2

   @@:
      mov   eax,lpSrc1
      test  uID,SRC1_REAL     ;is Src1 an 80-bit REAL in memory?
      jz    @F                ;check next source for Src1
      fld   tbyte ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DMEM     ;is Src1 a 32-bit integer in memory?
      jz    @F                ;check next source for Src1
      fild  dword ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DIMM     ;is Src1 an immediate 32-bit integer?
      jz    @F                ;check next source for Src1
      fild  lpSrc1
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_CONST    ;is Src1 one of the FPU constants?
      jnz   @F                ;otherwise no valid ID for Src1

srcerr:
      frstor content
srcerr1:
      xor   eax,eax           ;error code
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi
      jmp   src2
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src1
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

;----------------------------------------
;check source for Src2 and load it to FPU
;----------------------------------------

src2:
      test  uID,SRC2_FPU      ;is Src2 taken from FPU?
      jz    @F                ;check next source for Src2
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process
   @@:
      mov   eax,lpSrc2
      test  uID,SRC2_REAL     ;is Src2 an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DMEM     ;is Src2 a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DIMM     ;is Src2 an immediate 32-bit integer?
      jz    @F
      fild  lpSrc2
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_CONST    ;is Src2 one of the FPU constants?
      jz    srcerr            ;no correct flag for Src2
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src2
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fmul
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU or SRC2_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuMul endp

; #########################################################################

end


FpuRound

; #########################################################################
;
;                             FpuRound
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January 2004 to remove data section
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ; This FpuRound function rounds an 80-bit REAL number (Src) to the
  ; nearest integer and returns the integer portion at the specified
  ; destination (the FPU itself, a TBYTE memory variable, or a LONG integer
  ; memory variable), unless an invalid operation is reported by the FPU
  ; or the definition of the parameters (with uID) is invalid.
  ;
  ; The source can only be an 80-bit REAL number from the FPU itself or
  ; from memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuRound proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    srcerr
      mov   eax,lpSrc
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      push  eax               ;reserve space on CPU stack
      fstcw [esp]             ;get current control word
      fwait
      mov   ax,[esp]
      and   ax,0f3ffh         ;code it for rounding
      push  eax
      fldcw [esp]             ;change rounding code of FPU to round

      frndint                 ;round the number
      pop   eax               ;get rid of last push
      fldcw [esp]             ;load back the former control word

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      pop   eax               ;clean CPU stack
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jz    @F                ;destination is not the FPU
      fstp  tempst            ;store it temporarily
      jmp   restore
   @@:
      mov   eax,lpDest
      test  uID,DEST_IMEM
      jnz   @F
      fstp  tbyte ptr [eax]   ;store result as a REAL10
      jmp   restore
   @@:
      fistp dword ptr [eax]   ;store result as an integer

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuRound endp

; #########################################################################

end


FpuSin

; #########################################################################
;
;                             FpuSin
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January 2004 to prevent stack faults and to adjust
  ; angles outside the acceptable range if necessary.
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                       sin(Src) -> Dest
  ;
  ; This FpuSin function computes the sine of an angle in degrees or radians
  ; (Src) with the FPU and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuSin proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    srcerr            ;no correct flag for Src
      fild  lpSrc
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      test  uID,ANG_RAD
      jnz   @F                ;jump if angle already in radians
      fldpi                   ;load pi (3.14159...) on FPU
      fmul
      pushd 180
      fidiv word ptr[esp]     ;value now in radians
      fwait
      pop   eax               ;clean the stack
   @@:
      fldpi
      fadd  st,st             ;->2pi
      fxch
   @@:
      fprem                   ;reduce the angle
      fsin
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error
      sahf                    ;transfer to the CPU flags
      jpe   @B                ;reduce angle again if necessary
      fstp  st(1)             ;get rid of the 2pi

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuSin endp

; #########################################################################

end


FpuSize

; #########################################################################
;
;                             FpuSize
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002.
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ;
  ; This FpuSize function computes the exponent of a number (Src) as if it
  ; were expressed in scientific notation and returns the result as a LONG
  ; integer at the specified destination, unless an invalid operation
  ; is reported by the FPU or the definition of the parameters (with uID)
  ; is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, or an immediate DWORD value or one in memory. The FPU
  ; constants are not allowed as input. If the source is taken from the
  ; FPU, its value will be preserved. The destination must be a pointer
  ; to a 32-bit integer memory variable.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; This function simply computes the common logarithm (base 10) of the
  ; absolute value of the number and returns the characteristic
  ; (i.e. power of 10), adjusted if necessary for a negative log value.
  ; For example,
  ; 5432    would return +3  (log =  3.735)
  ; 5.432   would return  0  (log =  0.735)
  ; 0.05432 would return -2  (log = -1.265)
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved. All FPU registers are preserved.
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuSize proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue          ;go check for potential overflow

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
@@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    srcerr            ;no correct flag for Src
      fild  lpSrc
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      fabs                    ;insures a positive value
      ftst                    ;check the value on the FPU
      fstsw ax                ;store the result flags in AX
      fwait
      sahf                    ;transfer flags to CPU flag register
      jnz   @F                ;not NAN or zero
      jc    srcerr            ;invalid number or infinity
      mov   eax,lpDest
      mov   dword ptr[eax],80000000h     ;code it for 0 value
      jmp   finish            ;this avoids an invalid operation if computing
                              ;the logarithm of zero was attempted

   @@:
      fldlg2                  ;load log10(2)
      fxch                    ;set up registers for next operation
      fyl2x                   ;->[log2(x)]*[log10(2)] = log(x) base 10
      push  eax               ;reserve space on CPU stack
      fstcw [esp]             ;get current control word
      fwait
      mov   ax,[esp]
      and   ax,0f3ffh         ;clear RC field
      or    ax,0400h          ;code it for rounding down
      push  eax
      fldcw [esp]             ;change rounding code of FPU to rounding down
                              ;towards -INFINITY
      mov   eax,lpDest
      fistp dword ptr[eax]    ;store integer result at specified address
      fldcw [esp+4]           ;load back the former control word
      add   esp,8             ;restore stack pointer

finish:
      frstor content
      or    al,1              ;to insure EAX!=0
      ret

FpuSize endp

; #########################################################################

end


FpuSqrt

; #########################################################################
;
;                             FpuSqrt
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January 2004 to remove data section
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           sqrt(Src) -> Dest
  ;
  ; This FpuSqrt function extracts the square root of a number (Src)
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.

  ; (If the source is negative, an invalid operation will obviously
  ; be reported.)
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory, or one of the FPU
  ; constants.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuSqrt proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    @F
      fild  lpSrc
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_CONST    ;is Src one of the FPU constants?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fsqrt                   ;get the square root of the number

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuSqrt endp

; #########################################################################

end


FpuState

; #########################################################################
;
;                             FpuState
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, March 2004
  ; Modified April, 2005, to include an ID of the call in the report
  ;
  ; This FpuState function converts the content of all the FPU registers
  ; to a null-terminated alphanumeric string at the specified destination
  ; address.
  ;
  ; All the CPU and FPU registers are preserved.
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc
    includelib Fpu.lib

    .code

; #########################################################################

FpuState proc public lpDest:DWORD, uID:DWORD

LOCAL content[108] :BYTE
LOCAL buffer[4]    :BYTE

      pushfd
      pushad
      fsave content
      fwait
      lea   esi,content
      mov   edi,lpDest

;caller ID

      mov   eax,"  DI"
      stosd                   ;write "ID  " to destination
      mov   eax,uID           ;get ID parameter
      xor   ecx,ecx
      push  ecx               ;to be used as a terminator
      mov   cl,10

;convert ID from binary to ASCII

   @@:
      xor   edx,edx
      div   ecx
      add   dl,30h            ;convert remainder to ASCII
      push  edx               ;store it on the stack
      or    eax,eax           ;is conversion completed
      jnz   @B                ;continue if not

;recover ASCII characters one by one and store in buffer

   @@:
      pop   eax
      or    eax,eax           ;is it the end
      jz    @F                ;jump out if it is
      stosb
      jmp   @B

   @@:
      mov   ax,0A0Dh
      stosw                   ;crlf

;Control Word

      mov   eax,"  WC"
      stosd                   ;write "CW  " to destination
      lodsd                   ;get Control Word in AX
      shl   eax,16            ;shift it to the H.O. word
      mov   ecx,4
   @@:
      mov   al,0
      rol   eax,1
      mov   ah," "
      add   al,"0"
      stosw
      dec   ecx
      jnz   @B                ;last one is the IC field

      xor   ax,ax
      rol   eax,2
      ror   ax,1
      rol   ah,1
      add   ax,3030h
      stosw                   ;write the RC field
      mov   al," "
      stosb

      xor   ax,ax
      rol   eax,2
      ror   ax,1
      rol   ah,1
      add   ax,3030h
      stosw                   ;write the PC field
      mov   ax,"  "
      stosw

      mov   ecx,8
   @@:
      mov   al,0
      rol   eax,1
      mov   ah," "
      add   al,"0"
      stosw
      dec   ecx
      jnz   @B                ;write the interrupt masks
      dec   edi
      mov   ax,0A0Dh
      stosw                   ;crlf

;Status Word

      mov   eax,"  WS"
      stosd                   ;write "SW  " to destination
      lodsd                   ;get Status Word in AX
      shl   eax,16            ;shift it to the H.O. word
      mov   ecx,2
   @@:
      mov   al,0
      rol   eax,1
      mov   ah," "
      add   al,"0"
      stosw
      dec   ecx
      jnz   @B                ;last one is the C3 field

      xor   ax,ax
      rol   eax,3
      mov   buffer,al         ;TOP field
      push  eax
      and   eax,7
      ror   ax,1
      rol   eax,1
      ror   ax,2
      rol   ah,1
      add   eax,20303030h
      stosd                   ;write the TOP field
      pop   eax

      mov   ecx,3
   @@:
      mov   al,0
      rol   eax,1
      mov   ah," "
      add   al,"0"
      stosw
      dec   ecx
      jnz   @B                ;write the C2, C1, C0 field
      mov   al," "
      stosb

      mov   al,0
      rol   eax,1
      add   al,30h
      mov   ah," "
      stosw
      mov   ecx,7
      jmp   @F
      szflags     db    " IDZOUPS"
   @@:
      mov   al,0
      rol   eax,1
      mov   ah," "
      .if   al == 1
            mov   al,szflags[ecx]
      .else
            mov   al,szflags[ecx]
            or    al,20h
      .endif
      stosw
      dec   ecx
      jnz   @B                ;write the interrupt flags
      dec   edi
      mov   ax,0A0Dh
      stosw                   ;crlf

;Tag Word

      mov   eax,"  WT"
      stosd                   ;write "TW  " to destination
      lodsd                   ;get Tag Word in AX
      mov   cl,buffer
      shl   cl,1
      ror   ax,cl
      shl   eax,16            ;shift it to the H.O. word
      mov   ecx,8
   @@:
      mov   al,0
      ror   eax,2
      rol   ax,2
      push  eax
      .if   al == 0
            mov   eax," LAV"
      .elseif al == 1
            mov   eax," LUN"
      .elseif al == 2
            mov   eax," NaN"
      .else
            mov   eax," ERF"
      .endif
      stosd
      pop   eax
      dec   ecx
      jnz   @B
      dec   edi
      mov   ax,0A0Dh
      stosw                   ;crlf

;Instruction pointer

      mov   eax,"  PI"
      stosd                   ;write "IP  " to destination
      lodsd                   ;get Instruction pointer in EAX
      mov   ecx,8
   @@:
      rol   eax,4
      push  eax
      and   al,0fh
      add   al,30h
      .if   al > "9"
            add   al,7
      .endif
      stosb                   ;write each nibble as a hex number
      pop   eax
      dec   ecx
      jnz   @B
      mov   ax,0A0Dh
      stosw                   ;crlf

;Code segment

      mov   eax,"  SC"
      stosd                   ;write "CS  " to destination
      lodsd                   ;get Code segment in AX
      shl   eax,16            ;shift it to the H.O. word
      mov   ecx,4
   @@:
      rol   eax,4
      push  eax
      and   al,0fh
      add   al,30h
      .if   al > "9"
            add   al,7
      .endif
      stosb                   ;write each nibble as a hex number
      pop   eax
      dec   ecx
      jnz   @B
      mov   ax,0A0Dh
      stosw                   ;crlf

;Operand address

      mov   eax,"  AO"
      stosd                   ;write "OA  " to destination
      lodsd                   ;get Operand address in EAX
      mov   ecx,8
   @@:
      rol   eax,4
      push  eax
      and   al,0fh
      add   al,30h
      .if   al > "9"
            add   al,7
      .endif
      stosb                   ;write each nibble as a hex number
      pop   eax
      dec   ecx
      jnz   @B
      mov   ax,0A0Dh
      stosw                   ;crlf

;Data segment

      mov   eax,"  SD"
      stosd                   ;write "DS  " to destination
      lodsd                   ;get Data segment in AX
      shl   eax,16            ;shift it to the H.O. word
      mov   ecx,4
   @@:
      rol   eax,4
      push  eax
      and   al,0fh
      add   al,30h
      .if   al > "9"
            add   al,7
      .endif
      stosb                   ;write each nibble as a hex number
      pop   eax
      dec   ecx
      jnz   @B

      mov   ax,0A0Dh
      stosw                   ;crlf

;Data registers

      xor   ecx,ecx           ;count for registers
datareg:
      mov   ax,0A0Dh
      stosw                   ;crlf

      push  ecx
      mov   eax," 0TS"
      shl   ecx,16
      add   eax,ecx
      stosd                   ;write "STx " to destination

      shr   ecx,16
      add   cl,buffer
      and   cl,7
      shl   cl,1
      lea   eax,content+8
      mov   ax,[eax]          ;get Tag Word in AX
      shr   eax,cl
      and   al,3              ;get Tag of register in AL
      .if   al == 3           ;register is FREE
            mov   eax,"PME "
            stosd
            mov   ax,"YT"
            stosw
      .elseif al == 1         ;register == 0
            invoke FpuExam,esi,SRC1_REAL
            mov   dl,"+"
            test  eax,XAM_NEG
            jz    @F
            mov   dl,"-"
         @@:
            mov   eax," 0  "
            mov   ah,dl
            stosd
      .elseif al == 0         ;valid non-zero number
            invoke FpuFLtoA,esi,15,edi,SRC1_REAL or SRC2_DIMM or STR_SCI
            add   edi,24
      .else
            invoke FpuExam,esi,SRC1_REAL
            test  eax,XAM_VALID
            jnz   @F          ;valid = INFINITY, if not = INDEFINITE
            mov   eax,"DNI "
            stosd
            mov   eax,"NIFE"
            stosd
            mov   eax," ETI"
            stosd
            jmp   nextST
         @@:
            mov   dx,"+ "
            test  eax,XAM_NEG
            jz    @F
            mov   dh,"-"
         @@:
            mov   ax,dx
            stosw
            mov   eax,"IFNI"
            stosd
            mov   eax,"YTIN"
            stosd
      .endif

nextST:
      add   esi,10
      pop   ecx
      inc   ecx
      cmp   cl,8
      jb    datareg

      mov   byte ptr[edi],0

      frstor content
      fwait
      popad
      popfd
      ret

FpuState endp

; #########################################################################

end


FpuSub

; #########################################################################
;
;                             FpuSub
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           Src1 - Src2 -> Dest
  ;
  ; This FpuSub function subtracts the Src2 number from the Src1 number
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless an
  ; invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; Either of the two sources can be an 80-bit REAL number from the FPU
  ; itself or from memory, an immediate DWORD value or one in memory,
  ; or one of the FPU constants.
  ;
  ; None of the sources are checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuSub proc public lpSrc1:DWORD, lpSrc2:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU or SRC2_FPU      ;is data taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

      test  uID,SRC1_FPU
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   src2

   @@:
      mov   eax,lpSrc1
      test  uID,SRC1_REAL     ;is Src1 an 80-bit REAL in memory?
      jz    @F                ;check next source for Src1
      fld   tbyte ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DMEM     ;is Src1 a 32-bit integer in memory?
      jz    @F                ;check next source for Src1
      fild  dword ptr [eax]
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_DIMM     ;is Src1 an immediate 32-bit integer?
      jz    @F                ;check next source for Src1
      fild  lpSrc1
      jmp   src2              ;check next parameter for Src2

   @@:
      test  uID,SRC1_CONST    ;is Src1 one of the FPU constants?
      jnz   @F                ;otherwise no valid ID for Src1

srcerr:
      frstor content
srcerr1:
      xor   eax,eax           ;error code
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi
      jmp   src2
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src1
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

;----------------------------------------
;check source for Src2 and load it to FPU
;----------------------------------------

src2:
      test  uID,SRC2_FPU      ;is Src2 taken from FPU?
      jz    @F                ;check next source for Src2
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process
   @@:
      mov   eax,lpSrc2
      test  uID,SRC2_REAL     ;is Src2 an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DMEM     ;is Src2 a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_DIMM     ;is Src2 an immediate 32-bit integer?
      jz    @F
      fild  lpSrc2
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC2_CONST    ;is Src2 one of the FPU constants?
      jz    srcerr            ;no correct flag for Src2
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src2
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fsub
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   eax,1             ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU or SRC2_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuSub endp

; #########################################################################

end


FpuTan

; #########################################################################
;
;                             FpuTan
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January 2004 to prevent stack faults and to adjust
  ; angles outside the acceptable range if necessary.
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           a = tan(x)
  ;
  ; This FpuTan function computes the tangent of an angle in degrees or radians
  ; (Src) with the FPU and returns the result as an 80-bit REAL number at
  ; the specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuTan proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]               ;lpSrc2
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    srcerr            ;no correct flag for Src
      fild  lpSrc
      jmp   dest0             ;go complete process
srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      fldpi                   ;load pi (3.14159...) on FPU
      fadd  st,st             ;->2pi
      fxch
      test  uID,ANG_RAD
      jnz   @F                ;jump if angle already in radians
      fmul  st,st(1)
      pushd 360
      fidiv word ptr[esp]     ;value now in radians
      fwait
      pop   eax               ;clean the stack
   @@:
      fprem                   ;reduce the angle
      fptan
      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error
      sahf                    ;transfer to the CPU flags
      jpe   @B                ;reduce angle again if necessary
      fstp  st                ;get rid of the 1
      fstp  st(1)             ;get rid of the 2pi

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuTan endp

; #########################################################################

end


FpuTanh

; #########################################################################
;
;                             FpuTanh
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;                           sinh(Src)
  ;               tanh(Src) = ---------  -> Dest     (see related functions)
  ;                           cosh(Src)
  ;
  ; This FpuTanh function computes the hyperbolic tangent of a number (Src)
  ; with the FPU and returns the result as an 80-bit REAL number at the
  ; specified destination (the FPU itself or a memory location), unless
  ; an invalid operation is reported by the FPU or the definition of the
  ; parameters (with uID) is invalid.
  ;
  ; The source can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc
    includelib Fpu.lib

    .code

; #########################################################################

FpuTanh proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    srcerr            ;no correct flag for Src
      fild  lpSrc
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      fld   st(0)

      invoke FpuSinh,0,0,SRC1_FPU or DEST_FPU
      or    eax,eax
      jz    srcerr
      fxch
      invoke FpuCosh,0,0,SRC1_FPU or DEST_FPU
      or    eax,eax
      jz    srcerr
      fdiv                    ;sinh/cosh=tanh

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuTanh endp

; #########################################################################

end


FpuTexpX

; #########################################################################
;
;                             FpuTexpX
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ;            10^(Src) = antilog2[ log2(10) * Src ] -> Dest
  ;
  ; This FpuTexpX function computes the base 10 antilogarithm of a number.
  ; It raises 10 to the power of the Src number with the FPU and returns
  ; the result as an 80-bit REAL number at the specified destination
  ; (the FPU itself or a memory location), unless an invalid operation is
  ; reported by the FPU or the definition of the parameters (with uID) is
  ; invalid.
  ;
  ; The exponent can be an 80-bit REAL number from the FPU itself or from
  ; memory, an immediate DWORD value or one in memory, or one of the FPU
  ; constants.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuTexpX proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      mov   eax,lpSrc
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    @F
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DMEM     ;is Src a 32-bit integer in memory?
      jz    @F
      fild  dword ptr [eax]
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_DIMM     ;is Src an immediate 32-bit integer?
      jz    @F
      fild  lpSrc
      jmp   dest0             ;go complete process
   @@:
      test  uID,SRC1_CONST    ;is Src one of the FPU constants?
      jnz   @F                ;otherwise no correct flag for Src

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

   @@:
      test  eax,FPU_PI
      jz    @F
      fldpi                   ;load pi (3.14159...) on FPU
      jmp   dest0             ;go complete process
   @@:
      test  eax,FPU_NAPIER
      jz    srcerr            ;no correct CONST flag for Src
      fld1
      fldl2e
      fsub  st,st(1)
      f2xm1
      fadd  st,st(1)
      fscale
      fstp  st(1)

dest0:
      fldl2t                  ;->log2(10)
      fmul                    ;->log2(10)*Src

;the FPU can compute the antilog only with the mantissa
;the characteristic of the logarithm must thus be removed

      fld   st                ;copy the logarithm
      frndint                 ;keep only the characteristic
      fsub  st(1),st          ;keeps only the mantissa
      fxch                    ;get the mantissa on top

      f2xm1                   ;->2^(mantissa)-1
      fld1
      fadd                    ;add 1 back

;the number must now be readjusted for the characteristic of the logarithm

      fscale                  ;scale it with the characteristic

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

;the characteristic is still on the FPU and must be removed

      fstp  st(1)             ;overwrite the characteristic

      test  uID,DEST_FPU      ;check where result should be stored
      jnz   @F                ;leave result on FPU if so indicated
      mov   eax,lpDest
      fstp  tbyte ptr[eax]    ;store result at specified address
      jmp   restore
   @@:
      fstp  tempst            ;store it temporarily

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was Src taken from FPU
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuTexpX endp

; #########################################################################

end


FpuTrunc

; #########################################################################
;
;                             FpuTrunc
;
;##########################################################################

  ; -----------------------------------------------------------------------
  ; This procedure was written by Raymond Filiatreault, December 2002
  ; Modified January 2004 to eliminate data section
  ; Modified March 2004 to avoid any potential data loss from the FPU
  ; Revised January 2005 to free the FPU st7 register if necessary.
  ;
  ; This FpuTrunc function discards the fractional part of an 80-bit REAL
  ; number (Src) and returns the integer portion at the specified
  ; destination (the FPU itself, a TBYTE memory variable, or a LONG integer
  ; memory variable), unless an invalid operation is reported by the FPU
  ; or the definition of the parameters (with uID) is invalid.
  ;
  ; The source can only be an 80-bit REAL number from the FPU itself or
  ; from memory.
  ;
  ; The source is not checked for validity. This is the programmer's
  ; responsibility.
  ;
  ; Only EAX is used to return error or success. All other CPU registers
  ; are preserved.
  ;
  ; IF a source is specified to be the FPU top data register, it would be
  ; removed from the FPU. It would be replaced by the result only if the
  ; FPU is specified as the destination.
  ;
  ; IF source data is only from memory
  ; AND the FPU is specified as the destination for the result,
  ;       the st7 data register will become the st0 data register where the
  ;       result will be returned (any valid data in that register would
  ;       have been trashed).
  ;
  ; -----------------------------------------------------------------------

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive

    include Fpu.inc

    .code

; #########################################################################

FpuTrunc proc public lpSrc:DWORD, lpDest:DWORD, uID:DWORD

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;
; Because a library is assembled before its functions are called, all
; references to external memory data must be qualified for the expected
; size of that data so that the proper code is generated.
;
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

LOCAL content[108] :BYTE
LOCAL tempst       :TBYTE

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    continue

;-------------------------------
;check if top register is empty
;-------------------------------

      fxam                    ;examine its content
      fstsw ax                ;store results in AX
      fwait                   ;for precaution
      sahf                    ;transfer result bits to CPU flag
      jnc   continue          ;not empty if Carry flag not set
      jpe   continue          ;not empty if Parity flag set
      jz    srcerr1           ;empty if Zero flag set

continue:
      fsave content

;----------------------------------------
;check source for Src and load it to FPU
;----------------------------------------

      test  uID,SRC1_FPU      ;is Src taken from FPU?
      jz    @F
      lea   eax,content
      fld   tbyte ptr[eax+28]
      jmp   dest0             ;go complete process

   @@:
      test  uID,SRC1_REAL     ;is Src an 80-bit REAL in memory?
      jz    srcerr
      mov   eax,lpSrc
      fld   tbyte ptr [eax]
      jmp   dest0             ;go complete process

srcerr:
      frstor content
srcerr1:
      xor   eax,eax
      ret

dest0:
      push  eax               ;reserve space on stack
      fstcw [esp]             ;get current control word
      fwait
      mov   ax,[esp]
      or    ax,0c00h          ;code it for truncating
      push  eax
      fldcw [esp]             ;change rounding code of FPU to truncate

      frndint                 ;truncate the number
      pop   eax               ;remove modified CW from CPU stack
      fldcw [esp]             ;load back the former control word
      fwait
      pop   eax               ;clean CPU stack

      fstsw ax                ;retrieve exception flags from FPU
      fwait
      shr   al,1              ;test for invalid operation
      jc    srcerr            ;clean-up and return error

      test  uID,DEST_FPU      ;check where result should be stored
      jz    @F                ;destination is not the FPU
      fstp  tempst            ;store it temporarily
      jmp   restore
   @@:
      mov   eax,lpDest
      test  uID,DEST_IMEM
      jnz   @F
      fstp  tbyte ptr [eax]   ;store result as a REAL10
      jmp   restore
   @@:
      fistp dword ptr [eax]   ;store result as an integer

restore:
      frstor  content         ;restore all previous FPU registers

      test  uID,SRC1_FPU      ;was any data taken from FPU?
      jz    @F
      fstp  st                ;remove source

   @@:
      test  uID,DEST_FPU
      jz    @F                ;the new value has been stored in memory
                              ;none of the FPU data was modified

      ffree st(7)             ;free it if not already empty
      fld   tempst            ;load the new value on the FPU
   @@:
      or    al,1              ;to insure EAX!=0
      ret

FpuTrunc endp

; #########################################################################

end


Intel x86 FPU Instruction Reference

Intel x86 FPU Instruction Reference

This help file provides a list of FPU assembly instructions and a short description of the function of each one.

It is not intended to be exhaustive documentation on the fine details of the instructions' function, such as which exceptions they can trigger etc. - for such documentation, you should consult Intel's documentation Intel's Web site is here http://www.intel.com

This is intended primarily to provide documentation on the way the instructions may be used within the LCC-Win32 compiler and using AT&T's syntax.

F2XM1: Calculate 2^X-1

FABS: Floating-Point Absolute Value
FADD, FADDP: Floating-Point Addition
FBLD, FBSTP: BCD Floating-Point Load and Store
FCHS: Floating-Point Change Sign
FCLEX, FNCLEX: Clear Floating-Point Exceptions
FCMOVcc: Floating-Point Conditional Move
FCOM, FCOMP, FCOMPP, FCOMI, FCOMIP: Floating-Point Compare
FCOS: Cosine
FDECSTP: Decrement Floating-Point Stack Pointer
FDIV, FDIVP, FDIVR, FDIVRP: Floating-Point Division
FFREE: Flag Floating-Point Register as Unused
FICOM, FICOMP: Floating-Point/Integer Compare
FILD, FIST, FISTP: Floating-Point/Integer Conversion
FIMUL: Floating-Point/Integer Multiplication
FINCSTP: Increment Floating-Point Stack Pointer
FINIT, FNINIT: Initialise Floating-Point Unit
FISUB: Floating-Point/Integer Subtraction
FLD: Floating-Point Load
FLDxx: Floating-Point Load Constants
FLDCW: Load Floating-Point Control Word
FLDENV: Load Floating-Point Environment
FMUL, FMULP: Floating-Point Multiply
FNOP: Floating-Point No Operation
FPATAN, FPTAN: Arctangent and Tangent
FPREM, FPREM1: Floating-Point Partial Remainder
FRNDINT: Floating-Point Round to Integer
FSAVE, FRSTOR: Save/Restore Floating-Point State
FSCALE: Scale Floating-Point Value by Power of Two
FSIN, FSINCOS: Sine and Cosine
FSQRT: Floating-Point Square Root
FST, FSTP: Floating-Point Store
FSTCW: Store Floating-Point Control Word
FSTENV: Store Floating-Point Environment
FSTSW: Store Floating-Point Status Word
FSUB, FSUBP, FSUBR, FSUBRP: Floating-Point Subtract
FTST: Test ST0 Against Zero
FUCOMxx: Floating-Point Unordered Compare
FXAM: Examine Class of Value in ST0
FXCH: Floating-Point Exchange
FXTRACT: Extract Exponent and Significand
FYL2X, FYL2XP1: Compute Y times Log2(X) or Log2(X+1)


F2XM1: Calculate 2^X-1

f2xm1

F2XM1 raises 2 to the power of st(0), subtracts one, and stores the result back into st(0). The initial contents of st(0) must be a number in the range –1 to +1.


FABS: Floating-Point Absolute Value

fabs

FABS computes the absolute value of st(0), storing the result back in st(0).


FADD, FADDP, FIADD: Floating-Point Addition

fadd m32real      m32real + st(0) and store result in st(0)
fadd m64real      m64real + st(0) and store result in st(0)
fadd st(i),st(0)  st(0) + st(i) and store result in st(0)
fadd st(0),st(i)  st(i) + st(0) and store result in st(i)
faddp st(0),st(i) st(0) + st(i), store result in st(i), and pop stack
faddp             st(0) + st(1), store result in st(1), and pop stack
fiadd m32int      m32int + st(0) and store result in st(0)
fiadd m16int      m16int + st(0) and store result in st(0)

This instruction adds the destination and source operands and stores the sum in the destination location. The destination operand is always an FPU register; the source operand can be a register or a memory location. Source operands in memory can be in single-real, double-real, word-integer, or short-integer formats.

The no-operand version of the instruction adds the contents of the st(0) register to the st(1) register. The one-operand version adds the contents of a memory location (either a real or an integer value) to the contents of the st(0) register. The two-operand version, adds the contents of the st(0) register to the stST(i) register or vice versa. The value in st(0) can be doubled by coding:

fadd st(0), st(0);

The FADDP instructions perform the additional operation of popping the FPU register stack after storing the result. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. (The no-operand version of the floating-point add instructions always results in the register stack being popped. In some assemblers, the mnemonic for this instruction is FADD rather than FADDP.)

The FIADD instructions convert an integer source operand to extended-real format before performing the addition.

When the sum of two operands with opposite signs is 0, the result is +0, except for the round toward (-INF) mode, in which case the result is -0. When the source operand is an integer 0, it is treated as a +0.

When both operand are infinities of the same sign, the result is (INF) of the expected sign. If both operands are infinities of opposite signs, an invalid operation exception is generated.


FBLD, FBSTP: BCD Floating-Point Load and Store

fbld  mem80
fbstp mem80

FBLD loads an 80-bit (ten-byte) packed binary-coded decimal number from the given memory address, converts it to a real, and pushes it on the register stack. FBSTP stores the value of st(0), in packed BCD, at the given address and then pops the register stack.


FCHS: Floating-Point Change Sign

fchs

FCHS negates the number in st(0): negative numbers become positive, and vice versa.


FCLEX, FNCLEX: Clear Floating-Point Exceptions

fclex
fnclex

FCLEX clears any floating-point exceptions which may be pending. FNCLEX does the same thing but doesn't wait for previous floating-point operations (including the handling of pending exceptions) to finish first.


FCMOVcc: Floating-Point Conditional Move

fcmovb   st(0),st(i)   Move if below (CF=1)
fcmovb   st(i)         Move if below (CF=1)
fcmovbe  st(0),st(i)   Move if below or equal (CF=1 or ZF=1)
fcmovbe  st(i)         Move if below or equal (CF=1 or ZF=1)
fcmove   st(0),st(i)   Move if equal (ZF=1)
fcmove   st(i)         Move if equal (ZF=1)
fcmovnb  st(0),st(i)   Move if not below (CF=0)
fcmovnb  st(i)         Move if not below (CF=0)
fcmovne  st(0),st(i)   Move if not equal (ZF=0)
fcmovne  st(i)         Move if not equal (ZF=0)
fcmovnu  st(0),st(i)   Move if unordered (PF=0)
fcmovnu  st(i)         Move if unordered (PF=0)
fcmovu   st(0),st(i)   Move if unordered (PF=1)
fcmovu   st(i)         Move if unordered (PF=1)
fcmovnbe st(0),st(i)   Move if not below or equal (CF=0 and ZF=0)
fcmovnbe st(i)         Move if not below or equal (CF=0 and ZF=0)

The FCMOV instructions perform conditional move operations: each of them moves the contents of the given register into st(0) if its condition is satisfied, and does nothing if not.

The conditions are not the same as the standard condition codes used with conditional jump instructions. The conditions B, BE, NB, NBE, E and NE are exactly as normal, but none of the other standard ones are supported. Instead, the condition U and its counterpart NU are provided; the U condition is satisfied if the last two floating-point numbers compared were unordered, i.e. they were not equal but neither one could be said to be greater than the other, for example if they were NaNs. (The flag state which signals this is the setting of the parity flag: so the U condition is notionally equivalent to PE, and NU is equivalent to PO.)

The FCMOV conditions test the main processor's status flags, not the FPU status flags, so using FCMOV directly after FCOM will not work. Instead, you should either use FCOMI which writes directly to the main CPU flags word, or use FSTSW to extract the FPU flags.

Although the FCMOV instructions are flagged P6 above, they may not be supported by all Pentium Pro processors; the CPUID instruction will return a bit which indicates whether conditional moves are supported.


FCOM, FCOMP, FCOMPP, FCOMI, FCOMIP: Floating-Point Compare

fcom   mem32
fcom   mem64
fcom   st(i)
fcom   st(i),st(0)
fcomp  mem32
fcomp  mem64
fcomp  st(i)
fcomp  st(i),st(0)
fcompp
fcomi  st(i)
fcomi  st(i),st(0)
fcomip st(i)
fcomip st(i),st(0)

FCOM compares st(0) with the given operand, and sets the FPU flags accordingly. st(0) is treated as the right-hand side of the comparison, so that the carry flag is set (for a 'less-than' result) if st(0) is less than the given operand.

FCOMP does the same as FCOM, but pops the register stack afterwards. FCOMPP compares st(0) with ST1 and then pops the register stack twice.

FCOMI and FCOMIP work like the corresponding forms of FCOM and FCOMP, but write their results directly to the CPU flags register rather than the FPU status word, so they can be immediately followed by conditional jump or conditional move instructions.

The FCOM instructions differ from the FUCOM instructions only in the way they handle quiet NaNs: FUCOM will handle them silently and set the condition code flags to an 'unordered' result, whereas FCOM will generate an exception.


FCOS: Cosine

fcos

FCOS computes the cosine of st(0) (in radians), and stores the result in st(0). See also FSINCOS


FDECSTP: Decrement Floating-Point Stack Pointer

fdecstp

FDECSTP decrements the 'top' field in the floating-point status word. This has the effect of rotating the FPU register stack by one, as if the contents of st(7) had been pushed on the stack. See also FINCSTP


FDIV, FDIVP, FDIVR, FDIVRP: Floating-Point Division

fdiv m32real;       st(0)/m32real and store result in st(0)
fdiv m64real;       st(0)/m64real and store result in st(0)
fdiv st(i),st(0);  st(0)/st(i) and store result in st(0)
fdiv st(0),st(i);  st(i)/st(0) and store result in st(i)
fdivp st(0),st(i); st(i)/st(0), store result in st(i), and pop
fdivp               st(1)/by st(0), store result in st(1), and pop
fidiv m32int;       st(0)/m32int and store result in st(0)
fidiv m16int;       st(0)/m16int and store result in st(0)

These instructions divide the destination operand by the source operand and stores the result in the destination location. The destination operand (dividend) is always in an FPU register; the source operand (divisor) can be a register or a memory location. Source operands in memory can be in single-real, double-real, word-integer, or short-integer formats.

The no-operand version of the instruction divides the contents of the st(1) register by the contents of the st(0) register. The one-operand version divides the contents of the st(0) register by the contents of a memory location (either a real or an integer value). The two-operand version, divides the contents of the st(0) register by the contents of the st(i) register or vice versa.

The FDIVP instructions perform the additional operation of popping the FPU register stack after storing the result. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. The no-operand version of the floating-point divide instructions always results in the register stack being popped. In some assemblers, the mnemonic for this instruction is FDIV rather than FDIVP.

The FIDIV instructions convert an integer source operand to extended-real format before performing the division. When the source operand is an integer 0, it is treated as a +0. If an unmasked divide-by-zero exception (#Z) is generated, no result is stored; if the exception is masked, an (Inf) of the appropriate sign is stored in the destination operand.


FFREE: Flag Floating-Point Register as Unused

ffree st(i)

This instruction sets the tag in the FPU tag register associated with register st(i) to empty. The contents of st(i) and the FPU stack-top pointer (TOP) are not affected.


FICOM, FICOMP: Floating-Point/Integer Compare

ficom  mem16
ficom  mem32
ficomp mem16
ficomp mem32

FICOM compares st(0) with the 16-bit or 32-bit integer stored in the given memory location, and sets the FPU flags accordingly. FICOMP does the same, but pops the register stack afterwards.

These instruction compare the value in st(0) with an integer source operand and sets the condition code flags C0, C2, and C3 in the FPU status word according to the results (refer to table below). The integer value is converted to extended-real format before the comparison is made.

These instructions perform an “unordered comparison.” An unordered comparison also checks the class of the numbers being compared (refer to “FXAM—Examine” in this chapter). If either operand is a NaN or is in an undefined format, the condition flags are set to “unordered.”

The sign of zero is ignored, so that –0.0 = +0.0.

The FICOMP instructions pop the register stack following the comparison. To pop the register stack, the processor marks the st(0) register empty and increments the stack pointer (TOP) by 1.

Condition | C3 | C2 | C0
st(0) > SRC | 0 | 0 | 0
st(0) < SRC | 0 | 0 | 1
st(0) = SRC | 1 | 0 | 0
Unordered | 1 | 1 | 1


FILD, FIST, FISTP: Floating-Point/Integer Conversion

filds  m16int  Push m16int onto the FPU register stack
fildl  m32int  Push m32int onto the FPU register stack
fildq  m64int  Push m64int onto the FPU register stack
fists  m16int  Store st(0) in m16int
fistl  m32int  Store st(0) in m32int

fistps m16int  Store st(0) in m16int and pop register stack
fistpl m32int  Store st(0) in m32int and pop register stack
fistpq m64int  Store st(0) in m64int and pop register stack

The FILD instruction converts the signed-integer source operand into extended-real format and pushes the value onto the FPU register stack. The source operand can be a word, short, or long integer value. It is loaded without rounding errors. The sign of the source operand is preserved.

The FIST instruction converts the value in the st(0) register to a signed integer and stores the result in the destination operand. Values can be stored in word- or short-integer format. The destination operand specifies the address where the first byte of the destination value is to be stored.

The FISTP instruction performs the same operation as the fist instruction and then pops the register stack. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. The FISTP instruction can also stores values in long-integer format.


FIMUL: Floating-Point/Integer Multiplication

fimuls m16int
fimull m32int

FIMUL multiplies st(0) by the 16-bit or 32-bit integer stored in the given memory location, and stores the result in st(0).


FINCSTP: Increment Floating-Point Stack Pointer

fincstp

FINCSTP increments the 'top' field in the floating-point status word. This has the effect of rotating the FPU register stack by one, as if the register stack had been popped; however, unlike the popping of the stack performed by many FPU instructions, it does not flag the new st(7) (previously st(0)) as empty. See also FDECSTP


FINIT, FNINIT: Initialise Floating-Point Unit

finit
fninit

These instructions set the FPU control, status, tag, instruction pointer, and data pointer registers to their default states. The FPU control word is set to 037FH (round to nearest, all exceptions masked, 64-bit precision). The status word is cleared (no exception flags set, TOP is set to 0).

The data registers in the register stack are left unchanged, but they are all tagged as empty.

Both the instruction and data pointers are cleared.

The FINIT instruction checks for and handles any pending unmasked floating-point exceptions before performing the initialization; the FNINIT instruction does not.


FISUB: Floating-Point/Integer Subtraction

fisubs  mem16
fisubl  mem32
fisubrs mem16
fisubrl mem32

FISUB subtracts the 16-bit or 32-bit integer stored in the given memory location from st(0), and stores the result in st(0). FISUBR does the subtraction the other way round, i.e. it subtracts st(0) from the given integer, but still stores the result in st(0).


FLD: Floating-Point Load

flds mem32
fldl mem64
fldt mem80
fld  st(i)

FLD loads a floating-point value out of the given register or memory location, and pushes it on the FPU register stack.

FLDS loads a float

FLDL loads a double

FLDT loads a long double


FLDxx: Floating-Point Load Constants

fld1
fldl2e
fldl2t
fldlg2
fldln2
fldpi
fldz

These instructions push specific standard constants on the FPU register stack.

FLD1 pushes the value 1

FLDL2E pushes the base-2 logarithm of e

FLDL2T pushes the base-2 log of 10

FLDLG2 pushes the base-10 log of 2

FLDLN2 pushes the base-e log of 2

FLDPI pushes PI

FLDZ pushes zero.


FLDCW: Load Floating-Point Control Word

fldcw mem16

FLDCW loads a 16-bit value out of memory and stores it into the FPU control word (governing things like the rounding mode, the precision, and the exception masks). See also FSTCW


FLDENV: Load Floating-Point Environment

fldenv mem

FLDENV loads the FPU operating environment (control word, status word, tag word, instruction pointer, data pointer and last opcode) from memory. The memory area is 14 or 28 bytes long, depending on the CPU mode at the time. See also FSTENV


FMUL, FMULP: Floating-Point Multiply

fmuls m32real         st(0) * m32real and store result in st(0)
fmull m64real         st(0) * m64real and store result in st(0)
fmul  st(0), st(i)    st(0) * st(i) and store result in st(0)
fmul  st(i), st(0)    st(i) * st(0) and store result in st(i)
fmulp st(i), st(0)    st(i) * st(0), store result in st(i), and pop
fmulp                 st(1) * st(0), store result in st(1), and pop
fmul                  st(1) * st(0), store result in st(1)

These instructions multiply the destination and source operands and stores the product in the destination location. The destination operand is always an FPU data register; the source operand can be an FPU data register or a memory location. Source operands in memory can be in single-real or double-real formats.

The no-operand version of the instruction multiplies the contents of the st(1) register by the contents of the st(0) register and stores the product in the st(1) register. The one-operand version multiplies the contents of the st(0) register by the contents of a memory location (either a real or an integer value) and stores the product in the st(0) register. The two-operand version, multiplies the contents of the st(0) register by the contents of the st(i) register, or vice versa, with the result being stored in the register specified with the second operand (the destination operand).

The FMULP instructions perform the additional operation of popping the FPU register stack after storing the product. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. The no-operand version of the floating-point multiply instructions always results in the register stack being popped. In some assem-blers, the mnemonic for this instruction is FMUL rather than FMULP.


FNOP: Floating-Point No Operation

fnop

This instruction performs no FPU operation. This instruction takes up space in the instruction stream but does not affect the FPU or machine context, except the EIP register.


FPATAN, FPTAN: Arctangent and Tangent

fpatan
fptan

The FPATAN instruction computes the arctangent of the source operand in register st(1) divided by the source operand in register st(0), stores the result in st(1), and pops the FPU register stack. The result in register st(0) has the same sign as the source operand st(1) and a magnitude less than +PI.

The FPATAN instruction returns the angle between the X axis and the line from the origin to the point (X,Y), where Y (the ordinate) is st(1) and X (the abscissa) is st(0). The angle depends on the sign of X and Y independently, not just on the sign of the ratio Y/X. This is because a point (-X,Y) is in the second quadrant, resulting in an angle between PI/2 and PI, while a point (X,-Y) is in the fourth quadrant, resulting in an angle between 0 and -PI/2. A point (-X,-Y) is in the third quadrant, giving an angle between -PI/2 and -PI.

The FPTAN instruction This instruction computes the tangent of the source operand in register st(0), stores the result in st(0), and pushes a 1.0 onto the FPU register stack. The source operand must be given in radians and must be less than ±2^63 .


FPREM, FPREM1: Floating-Point Partial Remainder

fprem
fprem1

These instructions both produce the remainder obtained by dividing st(0) by st(1). This is calculated, notionally, by dividing st(0) by st(1), rounding the result to an integer, multiplying by st(1) again, and computing the value which would need to be added back on to the result to get back to the original value in st(0).

The two instructions differ in the way the notional round-to-integer operation is performed. FPREM does it by rounding towards zero, so that the remainder it returns always has the same sign as the original value in st(0); FPREM1 does it by rounding to the nearest integer, so that the remainder always has at most half the magnitude of st(1).

Both instructions calculate partial remainders, meaning that they may not manage to provide the final result, but might leave intermediate results in st(0) instead. If this happens, they will set the C2 flag in the FPU status word; therefore, to calculate a remainder, you should repeatedly execute FPREM or FPREM1 until C2 becomes clear.


FRNDINT: Floating-Point Round to Integer

frndint

FRNDINT rounds the contents of st(0) to an integer, according to the current rounding mode set in the FPU control word, and stores the result back in st(0).


FSAVE, FRSTOR: Save/Restore Floating-Point State

fsave  mem
fnsave mem
frstor mem

FSAVE saves the entire floating-point unit state, including all the information saved by FSTENV plus the contents of all the registers, to a 94 or 108 byte area of memory (depending on the CPU mode). FRSTOR restores the floating-point state from the same area of memory.

FNSAVE does the same as FSAVE, without first waiting for pending floating-point exceptions to clear.


FSCALE: Scale Floating-Point Value by Power of Two

fscale

FSCALE scales a number by a power of two, it rounds st(1) towards zero to obtain an integer, then multiplies st(0) by two to the power of that integer, and stores the result in st(0).


FSIN, FSINCOS: Sine and Cosine

fsin
fsincos

FSIN calculates the sine of st(0) (in radians) and stores the result in st(0). FSINCOS does the same, but then pushes the cosine of the same value on the register stack, so that the sine ends up in st(1) and the cosine in st(0). FSINCOS is faster than executing FSIN and FCOS in succession.


FSQRT: Floating-Point Square Root

fsqrt

FSQRT calculates the square root of st(0) and stores the result in st(0).


FST, FSTP: Floating-Point Store

fsts   mem32
fstps  mem32
fstl   mem64
fstpl  mem64
fstpt  mem80
fst    st(i)
fstp   st(i)

The FST instruction copies the value in the st(0) register to the destination operand, which can be a memory location or another register in the FPU register stack. When storing the value in memory, the value is converted to single (float) or double format.

The FSTP instruction performs the same operation as the FST instruction and then pops the register stack. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. The FSTP instruction can also store values in memory in extended-real format.

If the destination operand is a memory location, the operand specifies the address where the first byte of the destination value is to be stored. If the destination operand is a register, the operand specifies a register in the register stack relative to the top of the stack.

If the destination size is single or double, the significand of the value being stored is rounded to the width of the destination (according to rounding mode specified by the RC field of the FPU control word), and the exponent is converted to the width and bias of the destination format. If the value being stored is too large for the destination format, a numeric overflow exception (#O) is generated and, if the exception is unmasked, no value is stored in the destination operand. If the value being stored is a denormal value, the denormal exception (#D) is not generated. This condition is simply signaled as a numeric underflow exception (#U) condition.

If the value being stored is ±0, ±INF, or a NaN, the least-significant bits of the significand and the exponent are truncated to fit the destination format. This operation preserves the value’s identity as a 0, INF, or NaN.


FSTCW: Store Floating-Point Control Word

fstcw  mem16
fnstcw mem16

FSTCW stores the FPU control word (governing things like the rounding mode, the precision, and the exception masks) into a 2-byte memory area. See also FLDCW

FNSTCW does the same thing as FSTCW, without first waiting for pending floating-point exceptions to clear.


FSTENV: Store Floating-Point Environment

fstenv  mem
fnstenv mem

FSTENV stores the FPU operating environment (control word, status word, tag word, instruction pointer, data pointer and last opcode) into memory. The memory area is 14 or 28 bytes long, depending on the CPU mode at the time. See also FLDENV.

FNSTENV does the same thing as FSTENV, without first waiting for pending floating-point exceptions to clear.


FSTSW: Store Floating-Point Status Word

fstsw  mem16
fstsw  ax
fnstsw mem16
fnstsw ax

FSTSW stores the FPU status word into AX or into a 2-byte memory area.

FNSTSW does the same thing as FSTSW, without first waiting for pending floating-point exceptions to clear.


FSUB, FSUBP, FSUBR, FSUBRP: Floating-Point Subtract

fsubs  mem32
fsubl  mem64
fsub   st(i)
fsub   st(i),st(0)
fsub
fsub   st(0),st(i)
fsubp  st(0),st(i)
fsubp  st(i)
fsubrs mem32
fsubrl mem64
fsubr  st(i)
fsubr  st(i),st(0)
fsubr
fsubr  st(0),st(i)
fsubrp st(i)
fsubrp st(0),st(i)

FSUB: These instructions subtract the source operand from the destination operand and stores the difference in the destination location. The destination operand is always an FPU data register; the source operand can be a register or a memory location. Source operands in memory can be in single, double format.

The no-operand version of the instruction subtracts the contents of the st(0) register from the st(1) register and stores the result in st(1). The one-operand version subtracts the contents of a memory location from the contents of the st(0) register and stores the result in st(0). The two-operand version, subtracts the contents of the st(0) register from the st(i) register or vice versa.

The FSUBP instructions perform the additional operation of popping the FPU register stack following the subtraction. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. The no-operand version of the floating-point subtract instructions always results in the register stack being popped. In some assemblers, the mnemonic for this instruction is FSUB rather than FSUBP.

When the difference between two operands of like sign is 0, the result is +0, except for the round toward -INF mode, in which case the result is -0. This instruction also guarantees that +0 - (-0) = +0, and that -0 - (+0) = -0. When the source operand is an integer 0, it is treated as a +0.

When one operand is INF, the result is INF of the expected sign. If both operands are INF of the same sign, an invalid operation exception is generated.

FSUBR: These instructions subtract the destination operand from the source operand and stores the difference in the destination location. The destination operand is always an FPU register; the source operand can be a register or a memory location. Source operands in memory can be in single and double formats.

These instructions perform the reverse operations of the FSUB, FSUBP, and FISUB instructions.

They are provided to support more efficient coding.

The no-operand version of the instruction subtracts the contents of the st(1) register from the st(0) register and stores the result in st(1). The one-operand version subtracts the contents of the st(0) register from the contents of a memory location and stores the result in st(0). The two-operand version, subtracts the contents of the st(i) register from the st(0) register or vice versa.

The FSUBRP instructions perform the additional operation of popping the FPU register stack following the subtraction. To pop the register stack, the processor marks the st(0) register as empty and increments the stack pointer (TOP) by 1. The no-operand version of the floating-point reverse subtract instructions always results in the register stack being popped. In some assemblers, the mnemonic for this instruction is FSUBR rather than FSUBRP.

When the difference between two operands of like sign is 0, the result is +0, except for the round toward -INF mode, in which case the result is -0. This instruction also guarantees that +0 - (-0) = +0, and that -0 - (+0) = -0. When the source operand is an integer 0, it is treated as a +0.


FTST: Test ST0 Against Zero

ftst

FTST compares st(0) with zero and sets the FPU flags accordingly. st(0) is treated as the left-hand side of the comparison, so that a 'less-than' result is generated if st(0) is negative.


FUCOMxx: Floating-Point Unordered Compare

fucom st(i)        Compare st(0) with st(i)
fucom              Compare st(0) with st(1)
fucomp st(i)       Compare st(0) with st(i) and pop register stack
fucomp             Compare st(0) with st(1) and pop register stack
fucompp            Compare st(0) with st(1) and pop register stack twice
fucomi st(i),st(0) Compare and check for ordered values, and set status flags
fucomip st(i),st(0) and pop register stack

FUCOM compares st(0) with the given operand, and sets the FPU flags accordingly. st(0) is treated as the left-hand side of the comparison, so that the carry flag is set (for a 'less-than' result) if st(0) is less than the given operand.

FUCOMP does the same as FUCOM, but pops the register stack afterwards. FUCOMPP compares st(0) with st(1) and then pops the register stack twice.

FUCOMI and FUCOMIP work like the corresponding forms of FUCOM and FUCOMP, but write their results directly to the CPU flags register rather than the FPU status word, so they can be immediately followed by conditional jump or conditional move instructions.

The FUCOM instructions differ from the FCOM instructions only in the way they handle quiet NaNs: FUCOM will handle them silently and set the condition code flags to an 'unordered' result, whereas FCOM will generate an exception.


FXAM: Examine Class of Value in ST0

fxam

FXAM sets the FPU flags C3, C2 and C0 depending on the type of value stored in st(0):

000 (respectively) for an unsupported format

001 for a NaN

010 for a normal finite number

011 for an infinity

100 for a zero

101 for an empty register

110 for a denormal.

It also sets the C1 flag to the sign of the number.

Class | C3 | C2 | C0
Unsupported | 0 | 0 | 0
Nan | 0 | 1 | 0
Normal Finite Number | 0 | 1 | 0
Infinity | 0 | 1 | 1
Zer | 1 | 0 | 0
Empty | 1 | 0 | 1
Denormal Number | 1 | 1 | 0


FXCH: Floating-Point Exchange

fxch st(i)

FXCH exchanges st(0) with a given FPU register.


FXTRACT: Extract Exponent and Significand

fxtract

FXTRACT separates the number in st(0) into its exponent and significand (mantissa), stores the exponent back into st(0), and then pushes the significand on the register stack so that the significand ends up in st(0), and the exponent in st(1).


FYL2X, FYL2XP1: Compute Y times Log2(X) or Log2(X+1)

fyl2x
fyl2xp1

FYL2X multiplies st(1) by the base-2 logarithm of st(0), stores the result in st(1), and pops the register stack (so that the result ends up in st(0)). st(0) must be non-zero and positive.

FYL2XP1 works the same way, but replacing the base-2 log of st(0) with that of st(0) plus one. This time, st(0) must have magnitude no greater than 1 minus half the square root of two.


Intel x86 MMX Instruction Reference

This help file provides a list of assembly instructions and a short description of the function of each one.

It is not intended to be exhaustive documentation on the fine details of the instructions' function, such as which exceptions they can trigger etc. - for such documentation, you should go to Intel's Web site, http://www.intel.com

This is intended primarily to provide documentation on the way the instructions may be used within the LCC-Win32 compiler and using AT&T's syntax.

EMMS: Empty MMX State
MASKMOVQ: Byte Mask Write
MOVNTQ: Move 64 Bits Non Tempora
MOVD: Move 32 Bits
MOVQ: Move 64 Bits
PACKSSDW, PACKSSWB, PACKUSWB: Pack Data
PADDSIW: MMX Packed Addition to Implicit Destination
PADDxx: MMX Packed Addition
PAND, PANDN: MMX Bitwise AND and AND-NOT
PCMPxx: MMX Packed Comparison
PMADDWD: MMX Packed Multiply and Add
PMULHW, PMULLW: MMX Packed Multiply
POR: MMX Bitwise OR
PSLLx, PSRLx, PSRAx: MMX Bit Shifts
PSUBxx: MMX Packed Subtraction
PUNPCKxxx: Unpack Data
PXOR: MMX Bitwise XOR


EMMS: Empty MMX State

emms

EMMS sets the FPU tag word (marking which floating-point registers are available) to all ones, meaning all registers are available for the FPU to use. It should be used after executing MMX instructions and before executing any subsequent floating-point operations.


MASKMOVQ: Byte Mask Write

maskmovq mm2,mm1

Move 64-bits representing integer data from MM1 register to memory location specified by the edi register, using the byte mask in MM2 register.

Data is stored from the mm1 register to the location specified by the edi register. The size of the store depends on the address-size attribute. The most significant bit in each byte of the mask register mm2 is used to selectively write the data (0 = no write, 1 = write) on a per-byte basis. Behavior with a mask of all zeroes is as follows:

No data will be written to memory. However, transition from FP to MMX™ technology state (if necessary) will occur, irrespective of the value of the mask.

For memory references, a zero byte mask does not prevent addressing faults (i.e., #GP, SS) from being signaled.

Signaling of page faults (#PG) is implementation-specific.

#UD, #NM, #MF, and #AC faults are signaled irrespective of the value of the mask.

Signaling of breakpoints (code or data) is not guaranteed; different processor implementations may signal or not signal these breakpoints.

If the destination memory region is mapped as UC or WP, enforcement of associated semantics for these memory types is not guaranteed (i.e., is reserved) and is implementation specific. Dependence on the behavior of a specific implementation in this case is not recommended, and may lead to future incompatibility.


MOVNTQ: Move 64 Bits Non Temporal

movntq mmx,mm64

MOVNTQ Moves 64 bits representing integer operands (8b, 16b, 32b) from MM register to memory, minimizing pollution within cache hierarchy.

The linear address corresponds to the address of the least-significant byte of the referenced memory data. This store instruction minimizes cache pollution.


MOVD: Move 32 Bits

movd r/mm32,mmxreg
movd mmxreg,r/mm32

The MOVD instruction moves 32 bits from a memory or 32 bit register into an MMX register or moves from an MMX register to a 32 bit register or memory.


MOVQ: Move Quadword to/from MMX Register

movq r/m64,mmxreg
movq mmxreg,r/m64

MOVQ copies 64 bits from its source (first) operand into its destination (second) operand.


PACKSSDW, PACKSSWB, PACKUSWB: Pack Data

packssdw r/m64,mmxreg ; 0F 6B /r [PENT,MMX]
packsswb r/m64,mmxreg ; 0F 63 /r [PENT,MMX]
packuswb r/m64,mmxreg ; 0F 67 /r [PENT,MMX]

All these instructions start by forming a notional 128-bit word by placing the source (second) operand on the left of the destination (first) operand. PACKSSDW then splits this 128-bit word into four doublewords, converts each to a word, and loads them side by side into the destination register; PACKSSWB and PACKUSWB both split the 128-bit word into eight words, converts each to a byte, and loads those side by side into the destination register.

PACKSSDW and PACKSSWB perform signed saturation when reducing the length of numbers: if the number is too large to fit into the reduced space, they replace it by the largest signed number (7FFFh or 7Fh) that will fit, and if it is too small then they replace it by the smallest signed number (8000h or 80h) that will fit. PACKUSWB performs unsigned saturation: it treats its input as unsigned, and replaces it by the largest unsigned number that will fit.


PADDxx: MMX Packed Addition

paddb   r/m64,mmxreg
paddw   r/m64,mmxreg
paddd   r/m64,mmxreg
paddsb  r/m64,mmxreg
paddsw  r/m64,mmxreg
paddusb r/m64,mmxreg
paddusw r/m64,mmxreg

PADDxx all perform packed addition between their two 64-bit operands, storing the result in the destination (second) operand. The PADDxB forms treat the 64-bit operands as vectors of eight bytes, and add each byte individually; PADDxW treat the operands as vectors of four words; and PADDD treats its operands as vectors of two doublewords.

PADDSB and PADDSW perform signed saturation on the sum of each pair of bytes or words: if the result of an addition is too large or too small to fit into a signed byte or word result, it is clipped (saturated) to the largest or smallest value which will fit. PADDUSB and PADDUSW similarly perform unsigned saturation, clipping to 0FFh or 0FFFFh if the result is larger than that.


PAND, PANDN: MMX Bitwise AND and AND-NOT

pand  r/m64,mmxreg
pandn r/m64,mmxreg

PAND performs a bitwise AND operation between its two operands (i.e. each bit of the result is 1 if and only if the corresponding bits of the two inputs were both 1), and stores the result in the destination (second) operand.

PANDN performs the same operation, but performs a one's complement operation on the destination (second) operand first.


PCMPxx: MMX Packed Comparison

pcmpeqb r/m64,mmxreg
pcmpeqw r/m64,mmxreg
pcmpeqd r/m64,mmxreg
pcmpgtb r/m64,mmxreg
pcmpgtw r/m64,mmxreg
pcmpgtd r/m64,mmxreg

The PCMPxx instructions all treat their operands as vectors of bytes, words, or doublewords; corresponding elements of the source and destination are compared, and the corresponding element of the destination (second) operand is set to all zeros or all ones depending on the result of the comparison.

PCMPxxB treats the operands as vectors of eight bytes, PCMPxxW treats them as vectors of four words, and PCMPxxD as two doublewords.

PCMPEQx sets the corresponding element of the destination operand to all ones if the two elements compared are equal; PCMPGTx sets the destination element to all ones if the element of the second (destination) operand is greater (treated as a signed integer) than that of the first (source) operand.


PMADDWD: MMX Packed Multiply and Add

pmaddwd r/m64,mmxreg

PMADDWD treats its two inputs as vectors of four signed words. It multiplies corresponding elements of the two operands, giving four signed doubleword results. The top two of these are added and placed in the top 32 bits of the destination (second) operand; the bottom two are added and placed in the bottom 32 bits.


PMULHW, PMULLW: MMX Packed Multiply

pmulhw r/m64,mmxreg
pmullw r/m64,mmxreg

PMULxW treats its two inputs as vectors of four signed words. It multiplies corresponding elements of the two operands, giving four signed doubleword results.

PMULHW then stores the top 16 bits of each doubleword in the destination (second) operand; PMULLW stores the bottom 16 bits of each doubleword in the destination operand.


POR: MMX Bitwise OR

por r/m64,mmxreg

POR performs a bitwise OR operation between its two operands (i.e. each bit of the result is 1 if and only if at least one of the corresponding bits of the two inputs was 1), and stores the result in the destination (second) operand.


PSLLx, PSRLx, PSRAx: MMX Bit Shifts

psllw r/m64,mmxreg
psllw imm8,mmxreg
pslld r/m64,mmxreg
pslld imm8,mmxreg
psllq r/m64,mmxreg
psllq imm8,mmxreg
psraw r/m64,mmxreg
psraw imm8,mmxreg
psrad r/m64,mmxreg
psrad imm8,mmxreg
psrlw r/m64,mmxreg
psrlw imm8,mmxreg
psrld r/m64,mmxreg
psrld imm8,mmxreg
psrlq r/m64,mmxreg
psrlq imm8,mmxreg

PSxxQ perform simple bit shifts on the 64-bit MMX registers: the destination (second) operand is shifted left or right by the number of bits given in the source (first) operand, and the vacated bits are filled in with zeros (for a logical shift) or copies of the original sign bit (for an arithmetic right shift).

PSxxW and PSxxD perform packed bit shifts: the destination operand is treated as a vector of four words or two doublewords, and each element is shifted individually, so bits shifted out of one element do not interfere with empty bits coming into the next.

PSLLx and PSRLx perform logical shifts: the vacated bits at one end of the shifted number are filled with zeros. PSRAx performs an arithmetic right shift: the vacated bits at the top of the shifted number are filled with copies of the original top (sign) bit.


PSUBxx: MMX Packed Subtraction

psubb   r/m64,mmxreg
psubw   r/m64,mmxreg
psubd   r/m64,mmxreg
psubsb  r/m64,mmxreg
psubsw  r/m64,mmxreg
psubusb r/m64,mmxreg
psubusw r/m64,mmxreg

PSUBxx all perform packed subtraction between their two 64-bit operands, storing the result in the destination (second) operand. The PSUBxB forms treat the 64-bit operands as vectors of eight bytes, and subtract each byte individually; PSUBxW treat the operands as vectors of four words; and PSUBD treats its operands as vectors of two doublewords.

In all cases, the elements of the operand on the right are subtracted from the corresponding elements of the operand on the left, not the other way round.

PSUBSB and PSUBSW perform signed saturation on the sum of each pair of bytes or words: if the result of a subtraction is too large or too small to fit into a signed byte or word result, it is clipped (saturated) to the largest or smallest value which will fit. PSUBUSB and PSUBUSW similarly perform unsigned saturation, clipping to 0x0FF or 0x0FFFF if the result is larger than that.


PUNPCKxxx: Unpack Data

punpckhbw r/m64,mmxreg
punpckhwd r/m64,mmxreg
punpckhdq r/m64,mmxreg
punpcklbw r/m64,mmxreg
punpcklwd r/m64,mmxreg
punpckldq r/m64,mmxreg

PUNPCKxx all treat their operands as vectors, and produce a new vector generated by interleaving elements from the two inputs. The PUNPCKHxx instructions start by throwing away the bottom half of each input operand, and the PUNPCKLxx instructions throw away the top half.

The total remaining elements of 64 bits are then interleaved into the destination, alternating elements from the first (source) operand and the second (destination) operand: so the leftmost element in the result always comes from the second operand, and the rightmost from the destination.

PUNPCKxBW works a byte at a time, PUNPCKxWD a word at a time, and PUNPCKxDQ a doubleword at a time.

So, for example, if the first operand held 0x7A6A5A4A3A2A1A0A and the second held 0x7B6B5B4B3B2B1B0B, then:

PUNPCKHBW would return 0x7B7A6B6A5B5A4B4A.

PUNPCKHWD would return 0x7B6B7A6A5B4B5A4A.

PUNPCKHDQ would return 0x7B6B5B4B7A6A5A4A.

PUNPCKLBW would return 0x3B3A2B2A1B1A0B0A.

PUNPCKLWD would return 0x3B2B3A2A1B0B1A0A.

PUNPCKLDQ would return 0x3B2B1B0B3A2A1A0A.


PXOR: MMX Bitwise XOR

pxor r/m64,mmxreg

PXOR performs a bitwise XOR operation between its two operands (i.e. each bit of the result is 1 if and only if exactly one of the corresponding bits of the two inputs was 1), and stores the result in the destination (first) operand.


Appendix

Flags Register

FPU Control Register

FPU Status Register


Flags Register

The flags register, also known as the condition codes register is comprised of 16 bits shown below. Actually for the 16 bits shown below it's called FLAGS when 32 bits it's called EFLAGS!

The carry, parity, zero, sign, and overflow flags are used primarily with conditional jump instructions but you can also use the SETcc instructions and pushf/popf instructions to manage these flags.

Flag Register Instructions

clc | Clears the Carry Flag
cld | Clears the Direction Flag causing string instructions to increment the ESI and EDI index registers.
std | Sets the Direction Flag to 1 causing string instructions to decrement ESI and EDI instead of increment.
clts | Clears the Task Switched Flag in the Machine Status Register. This is a privileged operation and is generally used only by operating system code.
cmc | Toggles (inverts) the Carry Flag
lahf | Copies bits 0-7 of the flags register into AH. This includes flags AF, CF, PF, SF and ZF other bits are undefined.
sahf | Transfers bits 0-7 of AH into the Flags Register. This includes AF, CF, PF, SF and ZF.
pushf | Transfers the Flags Register onto the stack.
popf | Pops word from stack into the Flags Register and then increments ESP by 2
stc | Sets the Carry Flag to 1.
cli | Disables the mask-able hardware interrupts by clearing the Interrupt flag. NMI's and software interrupts are not inhibited.
sti | Sets the Interrupt Flag to 1, which enables recognition of all hardware interrupts.


FPU Control Register

The FPU Control Register

Bits 0-5 are the exception masks. If any of these bits are set, the corresponding condition is ignored by the FPU. If any bit is zero, and the corresponding condition occurs, then the FPU immediately generates an interrupt so the program can handle the exception condition.

Bit 0 is the invalid operation mask. Problems which arise generating an invalid exception include pushing more than eight values onto the stack or attempting to pop an item off an empty stack, trying to take the square root of a negative number, or loading a non-empty register.

Bit 1 masks the denormalised interrupt which occurs if an attemp is made to use a denormalised value. A denormalised value is generally a very small number just beyond the range of the FPU's precision capabilities.

Bit 2 masks the zero divide exception. The FPU will generate an interrupt for a divide by zero or will produce a NaN (Not A Number) depending on this mask.

Bit 3 three masks the overflow exception. This can occur for example if one tries to store a long double in a float variable that is too large for the float.

Bit 4 masks the underflow exception. This can occur for example if one tries to store a long double in a float variable that is too small for the float.

Bit 5 controls whether the precision exception can occur. Floating point values are often an approximation and not an exact representation of the precise figure. This precision exception occurs if the FPU produces an imprecise result, generally the result of an internal rounding operation. Dividing one by ten (1/10) will produce an imprecise result, therefore this exception often occurs since imprecise results are very common.

Bits 6-7, 12-15 are reserved.


FPU Status Register

The FPU Status Register

Bits 0-5 the exception flags. These bits are in the same order as the exception masks in the control register. If the corresponding condition exists, then the bit is set. These bits are independent of the exception masks in the control register. The FPU sets or clears these bits regardless of the corresponding mask setting in the control register.

Bit 6 indicates a stack fault. A stack fault occurs whenever there is a stack overflow or underflow. When this bit is set, the C1 condition code bit determines whether there was a stack overflow (C1=1) or stack underflow (C1=0) condition.

Bit 7 is set if any error condition bit is set. It is the logical OR of bits zero through five. A program can test this bit to quickly determine if an error condition exists.

Bits eight, nine, ten, and fourteen are the coprocessor condition code bits.

Status Register Condition Codes
Class | C3 | C2 | C0
Unsupported | 0 | 0 | 0
NaN | 0 | 0 | 1
Normal Finite Number | 0 | 1 | 0
Infinity | 0 | 1 | 1
Zero | 1 | 0 | 0
Empty | 1 | 0 | 1
Denormal Number | 1 | 1 | 0

The condition code C1 (bit 9) is the sign bit and can be tested using BT (bit test) instruction.

The FPU instruction FXAM tests the value in st(0) and sets these condition codes accordingly. Here is a routine to test if the value in st(0) is a normal finite number.

int is_normal(double a)
{
_asm("fldl 8(%ebp)");
_asm("fxam");
_asm("fstsw %ax");
_asm("andw $0x4500, %ax"); // mask out all but
relevant bits
_asm("cmpw $0x400, %ax");  // check for bit 10
only
_asm("jnz notnorm4");
// not normal
_asm("movl $1, %eax");     //
yes normal
_asm("jmp exit5");
_asm("notnorm5:");
_asm("xorl %eax, %eax");   // clear eax
_asm("exit5:");
_asm("fstp %st(0)");
}


ASM Mode Reel

comment¤
           Affichage des aiguilles d'une pendule ... heure minute seconde via GetLocalTime
c------¤

.data

sec2_Aig   REAL4  -20.0  ; Leng Aiguille seconde  arriére
sec2_C     DWORD  000    ; OrigineX+Cosinus
sec2_S     DWORD  000    ; OrigineY+Sinus

sec_Aig    REAL4   80.0  ; Leng Aiguille seconde
sec_C      DWORD  000    ; OrigineX+Cosinus
sec_S      DWORD  000    ; OrigineY+Sinus

min2_Aig   REAL4  -10.0  ; Leng Aiguille minute    arriére
min2_C     DWORD  000    ; OrigineX+Cosinus
min2_S     DWORD  000    ; OrigineY+Sinus

min_Aig    REAL4   60.0  ; Leng Aiguille minute
min_C      DWORD  000    ; OrigineX+Cosinus
min_S      DWORD  000    ; OrigineY+Sinus

hre2_Aig   REAL4  -10.0  ; Leng Aiguille heure    arriére
hre2_C     DWORD  000    ; OrigineX+Cosinus
hre2_S     DWORD  000    ; OrigineY+Sinus

hre_Aig    REAL4   40.0  ; Leng Aiguille heure
hre_C      DWORD  000    ; OrigineX+Cosinus
hre_S      DWORD  000    ; OrigineY+Sinus

time_A    SYSTEMTIME  <>

Px = 100                 ; Centre horizontale des aiguilles   (OrigineX)
Py = 100                 ; Centre verticale   des aiguilles   (OrigineY)

.code

    invoke StretchBlt,hdcMontre,000,000,200,200,DCm501,Flg_Selec,000,200,200,0CC0020h    ;  Effacement des aiguilles

 ;  invoke BitBlt,hdcMontre, 000, 000, 200,200, DCm501,      000, 000, SRCAND
 ;  invoke BitBlt,hdcMontre, 000, 000, 200,200, DCm501,Flg_Selec, 000, SRCPAINT

    invoke GetLocalTime,ADDR time_A

;--------------------------------------------------------------------  SECONDE

         xor   eax,eax
         mov    ax, time_A.wSecond
         mov   ebx,6                ; 1 seconde = 6 degres
         mul   ebx
         sub   eax,90               ; decalage quadratique
         pushd eax
        fild   dword PTR [esp]
         pushd 180
        fidiv  dword PTR [esp]
        fldpi
        fmul
         add   esp,8                ; adjust the stack pointer
        fsincos                     ; compute the sin cos of the angle ST(0)
        fmul   sec_Aig
        fistp  DWORD PTR  sec_C     ; cosinus x rayon
        fmul   sec_Aig
        fistp  DWORD PTR  sec_S     ; sinus x rayon
         add   sec_C,Px
         add   sec_S,Py

;-------------------------------------------------------------------- Seconde ARRIERE

         xor   eax,eax
         mov    ax, time_A.wSecond
         mov   ebx,6                ; 1 seconde = 6 degres
         mul   ebx
         sub   eax,90               ; decalage quadratique
         pushd eax
        fild   dword PTR [esp]
         pushd 180
        fidiv  dword PTR [esp]
        fldpi
        fmul
         add   esp,8                ; adjust the stack pointer
        fsincos                     ; compute the sin cos of the angle
        fmul   sec2_Aig
        fistp  DWORD PTR  sec2_C    ; cosinus x rayon
        fmul   sec2_Aig
        fistp  DWORD PTR  sec2_S    ; sinus x rayon
         add   sec2_C,Px
         add   sec2_S,Py

;-------------------------------------------------------------------  MINUTE

         xor  eax,eax
         mov   ax,time_A.wMinute
         mov  ebx,6                 ; 1 seconde = 6 degres
         mul  ebx
         sub  eax,90                ; decalage quadratique
         pushd eax
        fild   dword ptr [esp]
         pushd 180
        fidiv  dword ptr [esp]
        fldpi
        fmul
         add   esp,8                ; adjust the stack pointer
        fsincos                     ; compute the sin cos of the angle
        fmul   min_Aig
        fistp  DWORD PTR  min_C     ; cosinus x rayon
        fmul   min_Aig
        fistp  DWORD PTR  min_S     ; sinus x rayon
         add   min_C,Px
         add   min_S,Py

;-------------------------------------------------------------------- Minute ARRIERE

         xor  eax,eax
         mov   ax,time_A.wMinute
         mov  ebx,6                 ; 1 seconde = 6 degres
         mul  ebx
         sub  eax,90                ; decalage quadratique
         pushd eax
        fild   dword ptr [esp]
         pushd 180
        fidiv  dword ptr [esp]
        fldpi
        fmul
         add   esp,8                ; adjust the stack pointer
        fsincos                     ; compute the sin cos of the angle
        fmul   min2_Aig
        fistp  DWORD PTR  min2_C    ; cosinus x rayon
        fmul   min2_Aig
        fistp  DWORD PTR  min2_S    ; sinus x rayon
         add   min2_C,Px
         add   min2_S,Py
;-------------------------------------------------------------------  HEURE
         xor   eax,eax
         mov    ax,time_A.wHour
         mov   ebx,60               ; 1 heure = 30 degres
         mul   ebx
         xor   ebx,ebx              ;  nombre heure x 60 minutes + les minutes en cours
         mov    bx,time_A.wMinute
         add   eax,ebx
         sub   eax,180              ; decalage quadratique
         push  eax
        fild    dword ptr [esp]
         pushd 360                  ; 12 heures (360 degres) idem à 24 heures  (720 degres)
        fidiv   dword ptr [esp]
        fldpi
        fmul
         add  esp,8                 ; adjust the stack pointer = 2 pop
        fsincos                     ; compute the sin cos of the angle
        fmul   hre_Aig
        fistp  DWORD PTR  hre_C     ; cosinus x rayon
        fmul   hre_Aig
        fistp  DWORD PTR  hre_S     ; sinus x rayon
         add   hre_C,Px
         add   hre_S,Py
;-------------------------------------------------------------------- Heure ARRIERE
         xor   eax,eax
         mov   ax,time_A.wHour
         mov   ebx,60               ; 1 heure = 30 degres
         mul   ebx
         xor   ebx,ebx              ;  nombre heure x 60 minutes + les minutes en cours
         mov   bx,time_A.wMinute
         add   eax,ebx
         sub   eax,180              ; decalage quadratique
         push  eax
        fild    dword ptr [esp]
         pushd 360                  ; 12 heures (360 degres) idem à 24 heures  (720 degres)
        fidiv   dword ptr [esp]
        fldpi
        fmul
         add  esp,8                 ; adjust the stack pointer = 2 pop
        fsincos                     ; compute the sin cos of the angle
        fmul   hre2_Aig
        fistp  DWORD PTR  hre2_C    ; cosinus x rayon
        fmul   hre2_Aig
        fistp  DWORD PTR  hre2_S    ; sinus x rayon
         add   hre2_C,Px
         add   hre2_S,Py

;---------------------------------------------------------------------------------  Display aiguilles

        invoke SelectObject,hdcMontre,hPen2
        invoke     MoveToEx,hdcMontre,Px,Py,NULL
        invoke       LineTo,hdcMontre,min2_C,min2_S
        invoke     MoveToEx,hdcMontre,Px,Py,NULL
        invoke       LineTo,hdcMontre,min_C,min_S

        invoke  Ellipse,hdcMontre,Px-4,Py-4,Px+4,Py+4 ;      94,94,106,106

        invoke SelectObject,hdcMontre,hPen3
        invoke     MoveToEx,hdcMontre,Px,Py,NULL
        invoke       LineTo,hdcMontre,hre2_C,hre2_S
        invoke     MoveToEx,hdcMontre,Px,Py,NULL
        invoke       LineTo,hdcMontre,hre_C,hre_S

        invoke SelectObject,hdcMontre,hPen1
        invoke     MoveToEx,hdcMontre,Px,Py,NULL
        invoke       LineTo,hdcMontre,sec2_C,sec2_S
        invoke     MoveToEx,hdcMontre,Px,Py,NULL
        invoke       LineTo,hdcMontre,sec_C,sec_S

        invoke SelectObject,hdcMontre,hPen11
        invoke  Ellipse,hdcMontre,Px-2,Py-2,Px+2,Py+2 ;    98,98,102,102

;---------------------------------------------------------------------------------
        invoke  StretchBlt,hDC,000,000,200,200,hdcMontre,000,000,200,200,0CC0020h ; Reactualisation  dans le screen
;---------------------------------------------------------------------------------


ASM Mode Reel

TITLE fpu387.asm (C) 1989, Mark C. Peterson, CompuServe [70441,3353]

SUBTTL All rights reserved.
;
;  Code may be used in any program provided the author is credited
;    either during program execution or in the documentation.  Source
;    code may be distributed only in combination with public domain or
;    shareware source code.  Source code may be modified provided the
;    copyright notice and this message is left unchanged and all
;    modifications are clearly documented.
;
;    I would appreciate a copy of any work which incorporates this code,
;    however this is optional.
;
;    Mark C. Peterson
;    405-C Queen St., Suite #181
;    Southington, CT 06489
;    (203) 276-9721
;
;  Note: Remark statements following floating point commands generally indicate
;     the FPU stack contents after the command is completed.
;
;  References:
;     80386/80286 Assembly Language Programming
;        by William H. Murray, III and Chris H. Pappas
;        Published by Osborne McGraw-Hill, 1986
;
;
;

IFDEF ??version
MASM51
QUIRKS
ENDIF

.model medium, c

.code

.386
.387

_Loaded387sincos   PROC
   fsincos
   fwait
   ret
_Loaded387sincos   ENDP

FPU387sin      PROC     x:word, sin:word
   mov   bx, x
   fld   QWORD PTR [bx]    ; x
   fsin                    ; sin
   mov   bx, sin
   fstp  QWORD PTR [bx]    ;
   ret
FPU387sin      ENDP

FPU387cos      PROC     x:word, cos:word
   mov   bx, x
   fld   QWORD PTR [bx]    ; x
   fcos                    ; cos
   mov   bx, cos
   fstp  QWORD PTR [bx]    ;
   ret
FPU387cos      ENDP

FPUaptan387    PROC     x:word, y:word, z:word
   mov   bx, y
   fld   QWORD PTR [bx]    ; y
   mov   bx, x
   fld   QWORD PTR [bx]    ; x, y
   fpatan                  ; ArtTan
   mov   bx, z
   fstp  QWORD PTR [bx]    ;
   ret
FPUaptan387    ENDP

FPUcplxexp387  PROC     x:word, z:word
   mov   bx, x
   fld   QWORD PTR [bx+8]  ; x.y
   fsincos                 ; cos, sin
   fldln2                  ; ln2, cos, sin
   fdivr QWORD PTR [bx]    ; x.x/ln2, cos, sin
   fld1                    ; 1, x.x/ln2, cos, sin
   fld   st(1)             ; x.x/ln2, 1, x.x/ln2, cos, sin
   fprem                   ; prem, 1, x.x/ln2, cos, sin
   f2xm1                   ; e**prem-1, 1, x.x/ln2, cos, sin
   fadd                    ; e**prem, x.x/ln2, cos, sin
   fscale                  ; e**x.x, x.x/ln2, cos, sin
   fstp  st(1)             ; e**x.x, cos, sin
   fmul  st(2), st         ; e**x.x, cos, z.y
   fmul                    ; z.x, z.y
   mov   bx, z
   fstp  QWORD PTR [bx]    ; z.y
   fstp  QWORD PTR [bx+8]  ;
   ret
FPUcplxexp387  ENDP

.data

extrn overflow:WORD, TrigLimit:DWORD

PiFg13         dw       6487h
InvPiFg17      dw       0a2f9h
InvPiFg33      dd       0a2f9836eh
InvPiFg65      dq       0a2f9836e4e44152ah
InvPiFg16      dw       517ch
Ln2Fg16        dw       0b172h
Ln2Fg32        dd       0b17217f7h
One            dd       ?
ExpSign        dd       ?
Exp            dd       ?
SinNeg         dd       ?
CosNeg         dd       ?

.code

TaylorTerm  MACRO
LOCAL Ratio
   add   Factorial, One
   jnc   SHORT Ratio

   rcr   Factorial, 1
   shr   Num, 1
   shr   One, 1

Ratio:
   mul   Num
   div   Factorial
ENDM

Term           equ
HiTerm         equ
Num            equ
Factorial      equ
Sin            equ
Cos            equ
e              equ
Inve           equ
LoPtr          equ
HiPtr          equ

_sincos386   PROC
   xor   Factorial, Factorial
   mov   SinNeg, Factorial
   mov   CosNeg, Factorial
   mov   Exp, Factorial
   or    HiTerm, HiTerm
   jns   AnglePositive

   not   Term
   not   HiTerm
   add   Term, 1
   adc   HiTerm, Factorial
   mov   SinNeg, 1

AnglePositive:
   mov   Sin, Term
   mov   Cos, HiTerm
   mul   DWORD PTR InvPiFg65
   mov   Num, HiTerm
   mov   Term, Cos
   mul   DWORD PTR InvPiFg65
   add   Num, Term
   adc   Factorial, HiTerm
   mov   Term, Sin
   mul   InvPiFg33
   add   Num, Term
   adc   Factorial, HiTerm
   mov   Term, Cos
   mul   InvPiFg33
   add   Term, Factorial
   adc   HiTerm, 0

   and   HiTerm, 3
   mov   Exp, HiTerm

   mov   Num, Term
   mov   Factorial, InvPiFg33
   mov   One, Factorial
   mov   Cos, Factorial          ; Cos = 1
   mov   Sin, Num                  ; Sin = Num

LoopIntSinCos:
   TaylorTerm                    ; Term = Num * (x/2) * (x/3) * (x/4) * . . .
   sub   Cos, Term               ; Cos = 1 - Num*(x/2) + (x**4)/4! - . . .
   cmp   Term, TrigLimit
   jbe   SHORT ExitIntSinCos

   TaylorTerm
   sub   Sin, Term               ; Sin = Num - Num*(x/2)*(x/3) + (x**5)/5! - . . .
   cmp   Term, TrigLimit
   jbe   SHORT ExitIntSinCos

   TaylorTerm
   add   Cos, Term
   cmp   Term, TrigLimit
   jbe   SHORT ExitIntSinCos

   TaylorTerm                    ; Term = Num * (x/2) * (x/3) * . . .
   add   Sin, Term
   cmp   Term, TrigLimit
   jnbe  LoopIntSinCos

ExitIntSinCos:
   xor   Term, Term
   mov   Factorial, Term
   cmp   Cos, InvPiFg33
   jb    CosDivide               ; Cos < 1.0

   inc   Factorial                      ; Cos == 1.0
   jmp   StoreCos

CosDivide:
   mov   HiTerm, Cos
   div   InvPiFg33

StoreCos:
   mov   Cos, Term                 ; Factorial:Cos

   xor   Term, Term
   mov   Num, Term
   cmp   Sin, InvPiFg33
   jb    SinDivide               ; Sin < 1.0

   inc   Num                      ; Sin == 1.0
   jmp   StoreSin

SinDivide:
   mov   HiTerm, Sin
   div   InvPiFg33

StoreSin:
   mov   Sin, Term                 ; Num:Sin

   test  Exp, 1
 -  jz    ChkNegCos

   xchg  Factorial, Num
   xchg  Sin, Cos
   mov   Term, SinNeg
   xchg  Term, CosNeg
   mov   CosNeg, Term

ChkNegCos:
   mov   Term, Exp
   shr   Term, 1
   mov   Term, 0
   rcr   Term, 1
   xor   Term, Exp
   jz    ChkNegSin

   xor   CosNeg, 1

ChkNegSin:
   test  Exp, 2
   jz    CorrectQuad

   xor   SinNeg, 1

CorrectQuad:
   ret
_sincos386     ENDP

SinCos386   PROC     LoNum:DWORD, HiNum:DWORD, SinAddr:DWORD, CosAddr:DWORD
   mov   Term, LoNum
   mov   HiTerm, HiNum

   call  _sincos386

   cmp   CosNeg, 1
   jne   CosPolarized

   not   Cos
   not   Factorial
   add   Cos, 1
   adc   Factorial, 0

CosPolarized:
   mov   HiTerm, Num
   mov   Num, CosAddr
   mov   LoPtr, Cos
   mov   HiPtr, Factorial

   cmp   SinNeg, 1
   jne   SinPolarized

   not   Sin
   not   HiTerm
   add   Sin, 1
   adc   HiTerm, 0

SinPolarized:
   mov   Num, SinAddr
   mov   LoPtr, Sin
   mov   HiPtr, HiTerm
   ret
SinCos386      ENDP

_e2y386   PROC                 ; eTerm =: Num * 2**16, 0 < Num < Ln2
   mov   ExpSign, 0
   or    HiTerm, HiTerm
   jns   CalcExp

   mov   ExpSign, 1
   not   Term
   not   HiTerm
   add   Term, 1
   adc   HiTerm, 0

CalcExp:
   div   Ln2Fg32
   mov   Exp, Term
   mov   Num, HiTerm

   xor   Factorial, Factorial
   stc
   rcr   Factorial, 1
   mov   One, Factorial
   mov   e, Num
   mov   Term, Num
   shr   Num, 1

Loop_e2y386:
   TaylorTerm
   add   e, Term                 ; e = 1 + Num + Num*x/2 + (x**3)/3! + . . .
   cmp   Term, TrigLimit
   jnbe  SHORT Loop_e2y386

ExitIntSinhCosh:
   stc
   rcr   e, 1
   ret                           ; return e**y * (2**32), 1 < e**y < 2
_e2y386   ENDP

Exp386    PROC     LoNum:DWORD, HiNum:DWORD
   mov   Term, LoNum
   mov   HiTerm, HiNum

   call  _e2y386

   cmp   Exp, 32
   jae   Overflow

   cmp   ExpSign, 0
   jnz   NegNumber

   mov   Factorial, Exp
   shld  HiTerm, Term, cl
   shl   Term, cl
   jmp   ExitExp386

Overflow:
   xor   Term, Term
   xor   HiTerm, HiTerm
   mov   overflow, 1
   jmp   ExitExp386

NegNumber:
   cmp   e, 80000000h
   jne   DivideE

   mov   Term, e
   dec   Exp
   jmp   ShiftE

DivideE:
   xor   Term, Term
   mov   HiTerm, Term
   stc
   rcr   HiTerm, 1
   div   e

ShiftE:
   xor   HiTerm, HiTerm
   mov   Factorial, Exp
   shr   Term, cl

ExitExp386:
   ret
Exp386    ENDP

SinhCosh386 PROC  LoNum:DWORD, HiNum:DWORD, SinhAddr:DWORD, CoshAddr:DWORD
   mov   Term, LoNum
   mov   HiTerm, HiNum

   call  _e2y386

   cmp   e, 80000000h
   jne   InvertE              ; e > 1

   mov   HiTerm, 1
   xor   Term, Term
   cmp   Exp, 0
   jne   ShiftOne

   mov   e, Term
   mov   Factorial, Term
   jmp   ChkSinhSign

ShiftOne:
   mov   Factorial, Exp
   shl   HiTerm, cl
   dec   Factorial
   shr   e, cl
   shr   HiTerm, 1
   shr   e, 1
   mov   Factorial, HiTerm
   sub   Term, e
   sbb   HiTerm, 0
   xchg  Term, e
   xchg  HiTerm, Factorial
   jmp   ChkSinhSign

InvertE:
   xor   Term, Term               ; calc 1/e
   mov   HiTerm, 80000000h
   div   e

   mov   Inve, Term

ShiftE:
   mov   Factorial, Exp
   shr   Inve, cl
   inc   cl
   shld  HiTerm, e, cl
   mov   Factorial, HiTerm      ; Factorial:e == e**Exp

   mov   Term, e                ; HiTerm:e == e**Exp
   add   Term, Inve
   adc   HiTerm, 0
   shr   HiTerm, 1
   rcr   Term, 1                ; cosh(Num) = (e**Exp + 1/e**Exp) / 2

   sub   e, Inve
   sbb   Factorial, 0
   sar   Factorial, 1
   rcr   e, 1

ChkSinhSign:
   or    HiNum, 0
   jns   StoreHyperbolics

   not   e
   not   Factorial
   add   e, 1
   adc   Factorial, 0

StoreHyperbolics:
   mov   Num, CoshAddr
   mov   LoPtr, Term
   mov   HiPtr, HiTerm

   mov   Num, SinhAddr
   mov   LoPtr, e
   mov   HiPtr, Factorial

   ret
SinhCosh386    ENDP

END


ASM Mode Reel

TITLE fpu087.asm (C) 1989, Mark C. Peterson, CompuServe [70441,3353]
SUBTTL All rights reserved.

;
;  Code may be used in any program provided the author is credited
;    either during program execution or in the documentation.  Source
;    code may be distributed only in combination with public domain or
;    shareware source code.  Source code may be modified provided the
;    copyright notice and this message is left unchanged and all
;    modifications are clearly documented.
;
;    I would appreciate a copy of any work which incorporates this code.
;
;    Mark C. Peterson
;    405-C Queen St., Suite #181
;    Southington, CT 06489
;    (203) 276-9721
;
;  References:
;     The VNR Concise Encyclopedia of Mathematics
;        by W. Gellert, H. Hustner, M. Hellwich, and H. Kastner
;        Published by Van Nostrand Reinhold Comp, 1975
;
;     80386/80286 Assembly Language Programming
;        by William H. Murray, III and Chris H. Pappas
;        Published by Osborne McGraw-Hill, 1986
;
;  History since Fractint 16.3:
;     CJLT changed e2x and Log086 algorithms for more speed
;     CJLT corrected SinCos086 for -ve angles in 2nd and 4th quadrants
;     CJLT speeded up SinCos086 for angles >45 degrees in any quadrant
;     (See comments containing the string `CJLT')
; 14 Aug 91 CJLT removed r16Mul - not called from anywhere
; 21 Aug 91 CJLT corrected Table[1] from 6 to b
;                improved bx factors in Log086 for more accuracy
;                corrected Exp086 overflow detection to 15 from 16 bits.
; 07 Sep 92 MP   corrected problem in FPUcplxlog
; 07 Sep 92 MP   added argument test for FPUcplxdiv
; 06 Nov 92 CAE  made some varibles public for PARSERA.ASM
; 07 Dec 92 CAE  sped up FPUsinhcosh function
;
; CJLT=      Chris J Lusby Taylor
;            32 Turnpike Road
;            Newbury, England (where's that?)
;            Contactable via Compuserve user Stan Chelchowski [100016,351]
;            or Tel 011 44 635 33270 (home)
;
; CAE=   Chuck Ebbert  CompuServe [76306,1226]
;
;
;PROCs in this module:
;FPUcplxmul     PROC     x:word, y:word, z:word
;FPUcplxdiv     PROC     x:word, y:word, z:word
;FPUcplxlog     PROC     x:word, z:word
;FPUsinhcosh    PROC     x:word, sinh:word, cosh:word
;FPUsincos      PROC     x:word, sinx:word, cosx:word
;r16Mul         PROC     uses si di, x1:word, x2:word, y1:word, y2:word
;RegFloat2Fg    PROC     x1:word, x2:word, Fudge:word
;RegFg2Float    PROC     x1:word, x2:word, FudgeFact:byte
;ExpFudged      PROC     uses si, x_low:word, x_high:word, Fudge:word
;LogFudged      PROC     uses si di, x_low:word, x_high:word, Fudge:word
;LogFloat14     PROC     x1:word, x2:word
;RegSftFloat    PROC     x1:word, x2:word, Shift:byte
;RegDivFloat    PROC     uses si di, x1:word, x2:word, y1:word, y2:word
;SinCos086      PROC     uses si di, LoNum:WORD, HiNum:WORD, SinAddr:WORD, \
;_e2xLT         PROC     ;argument in dx.ax (bitshift=16 is hard coded here)
;Exp086         PROC     uses si di, LoNum:WORD, HiNum:WORD
;SinhCosh086    PROC     uses si di, LoNum:WORD, HiNum:WORD, SinhAddr:WORD, \
;Log086         PROC     uses si di, LoNum:WORD, HiNum:WORD, Fudge:WORD

IFDEF ??version
MASM51
QUIRKS
ENDIF

.model medium, c

extrn cos:far
extrn _Loaded387sincos:far
extrn compiled_by_turboc:word

.data

extrn cpu:WORD
extrn overflow:word
extrn save_release:word
PUBLIC TrigLimit

; CAE 6Nov92 made these public for PARSERA.ASM */
PUBLIC _1_, _2_, PointFive, infinity

PiFg13         dw       6487h
InvPiFg33      dd       0a2f9836eh
InvPiFg16      dw       517ch
Ln2Fg16        dw       0b172h ;ln(2) * 2^16 . True value is b172.18
Ln2Fg15        dw       058b9h ;used by e2xLT. True value is 58b9.0C
TrigLimit      dd       0
;
;Table of 2^(n/16) for n=0 to 15. All entries fg15.
;Used by e2xLT
;
Table           dw      08000h,085abh,08b96h,091c4h
                dw      09838h,09ef5h,0a5ffh,0ad58h
                dw      0b505h,0bd09h,0c567h,0ce25h
                dw      0d745h,0e0cdh,0eac1h,0f525h
one            dw       ?
expSign        dw       ?
a              dw       ?
SinNeg         dw       ?       ;CJLT - not now needed by SinCos086, but by
CosNeg         dw       ?       ;       ArcTan086
Ans            dq       ?
fake_es        dw       ?         ;  Windows can't use ES for storage

TaylorTerm  MACRO
LOCAL Ratio
   add   Factorial, one
   jnc   SHORT Ratio

   rcr   Factorial, 1
   shr   Num, 1
   shr   one, 1

Ratio:
   mul   Num
   div   Factorial
ENDM

_4_         dq    4.0
_2_         dq    2.0
_1_         dq    1.0
PointFive   dq    0.5
temp        dq     ?
Sign        dw     ?

extrn fpu:word

infinity          dq    1.0E+300

.code

FPUcplxmul     PROC     x:word, y:word, z:word
   mov   bx, x
   fld   QWORD PTR [bx]       ; x.x
   fld   QWORD PTR [bx+8]     ; x.y, x.x
   mov   bx, y
   fld   QWORD PTR [bx]       ; y.x, x.y, x.x
   fld   QWORD PTR [bx+8]     ; y.y, y.x, x.y, x.x
   mov   bx, z
   fld   st                   ; y.y, y.y, y.x, x.y, x.x
   fmul  st, st(3)            ; y.y*x.y, y.y. y.x, x.y, x.x
   fld   st(2)                ; y.x, y.y*x.y, y.y, y.x, x.y, x.x
   fmul  st, st(5)            ; y.x*x.x, y.y*x.y, y.y, y.x, x.y, x.x
   fsubr                      ; y.x*x.x - y.y*x.y, y.y, y.x, x.y, x.x
   fstp  QWORD PTR [bx]       ; y.y, y.x, x.y, x.x
   fmulp st(3), st            ; y.x, x.y, x.x*y.y
   fmul                       ; y.x*x.y, x.x*y.y
   fadd                       ; y.x*x.y + x.x*y.y
   fstp  QWORD PTR [bx+8]
   ret
FPUcplxmul     ENDP

FPUcplxdiv     PROC     x:word, y:word, z:word
LOCAL Status:WORD
   mov   bx, x
   fld   QWORD PTR [bx]       ; x.x
   fld   QWORD PTR [bx+8]     ; x.y, x.x
   mov   bx, y
   fld   QWORD PTR [bx]       ; y.x, x.y, x.x
   fld   QWORD PTR [bx+8]     ; y.y, y.x, x.y, x.x
   fld   st                   ; y.y, y.y, y.x, x.y, x.x
   fmul  st, st               ; y.y*y.y, y.y, y.x, x.y, x.x
   fld   st(2)                ; y.x, y.y*y.y, y.y, y.x, x.y, x.x
   fmul  st, st               ; y.x*y.x, y.y*y.y, y.y, y.x, x.y, x.x
   fadd                       ; mod, y.y, y.x, x.y, x.x

   ftst                       ; test whether mod is (0,0)
   fstsw Status
   mov   ax, Status
   and   ah, 01000101b
   cmp   ah, 01000000b
   jne   NotZero

   fstp  st
   fstp  st
   fstp  st
   fstp  st
   fstp  st

   fld   infinity
   fld   st
   mov   bx, z
   fstp  QWORD PTR [bx]
   fstp  QWORD PTR [bx+8]
   mov   ax,save_release
   cmp   ax,1920
   jle   ExitDiv       ; before 19.20 overflow wasn't set
    mov   overflow, 1
   jmp   ExitDiv

NotZero:
   fdiv  st(1), st            ; mod, y.y=y.y/mod, y.x, x.y, x.x
   fdivp st(2), st            ; y.y, y.x=y.x/mod, x.y, x.x
   mov   bx, z
   fld   st                   ; y.y, y.y, y.x, x.y, x.x
   fmul  st, st(3)            ; y.y*x.y, y.y. y.x, x.y, x.x
   fld   st(2)                ; y.x, y.y*x.y, y.y, y.x, x.y, x.x
   fmul  st, st(5)            ; y.x*x.x, y.y*x.y, y.y, y.x, x.y, x.x
   fadd                       ; y.x*x.x - y.y*x.y, y.y, y.x, x.y, x.x
   fstp  QWORD PTR [bx]       ; y.y, y.x, x.y, x.x
   fmulp st(3), st            ; y.x, x.y, x.x*y.y
   fmul                       ; y.x*x.y, x.x*y.y
   fsubr                      ; y.x*x.y + x.x*y.y
   fstp  QWORD PTR [bx+8]

ExitDiv:
   ret
FPUcplxdiv     ENDP

FPUcplxlog     PROC     x:word, z:word
LOCAL Status:word, ImagZero:WORD
   mov   bx, x

   mov   ax, WORD PTR [bx+8+6]
   mov   ImagZero, ax
   or    ax, WORD PTR [bx+6]
   jnz   NotBothZero

   fldz
   fldz
   jmp   StoreZX

NotBothZero:
   fld   QWORD PTR [bx+8]        ; x.y
   fld   QWORD PTR [bx]          ; x.x, x.y
   mov   bx, z
   fldln2                        ; ln2, x.x, x.y
   fdiv  _2_                     ; ln2/2, x.x, x.y
   fld   st(2)                   ; x.y, ln2/2, x.x, x.y
   fmul  st, st                  ; sqr(x.y), ln2/2, x.x, x.y
   fld   st(2)                   ; x.x, sqr(x.y), ln2/2, x.x, x.y
   fmul  st, st                  ; sqr(x.x), sqr(x.y), ln2/2, x.x, x.y
   fadd                          ; mod, ln2/2, x.x, x.y
   fyl2x                         ; z.x, x.x, x.y
   fxch  st(2)                   ; x.y, x.x, z.x
   fxch                          ; x.x, x.y, z.x
   cmp   fpu, 387
   jne   Restricted

   fpatan                        ; z.y, z.x
   jmp   StoreZX

Restricted:
   mov   bx, x
   mov   dh, BYTE PTR [bx+7]
   or    dh, dh
   jns   ChkYSign

   fchs                          ; |x.x|, x.y, z.x

ChkYSign:
   mov   dl, BYTE PTR [bx+8+7]
   or    dl, dl
   jns   ChkMagnitudes

   fxch                          ; x.y, |x.x|, z.x
   fchs                          ; |x.y|, |x.x|, z.x
   fxch                          ; |x.x|, |x.y|, z.x

ChkMagnitudes:
   fcom  st(1)                   ; x.x, x.y, z.x
   fstsw Status                  ; x.x, x.y, z.x
   test  Status, 4500h
   jz    XisGTY

   test  Status, 4000h
   jz    XneY

   fstp  st                      ; x.y, z.x
   fstp  st                      ; z.x
   fldpi                         ; Pi, z.x
   fdiv  _4_                     ; Pi/4, z.x
   jmp   ChkSignZ

XneY:
   fxch                          ; x.y, x.x, z.x
   fpatan                        ; Pi/2 - Angle, z.x
   fldpi                         ; Pi, Pi/2 - Angle, z.x
   fdiv  _2_                     ; Pi/2, Pi/2 - Angle, z.x
   fsubr                         ; Angle, z.x
   jmp   ChkSignZ

XisGTY:
   fpatan                        ; Pi-Angle or Angle+Pi, z.x

ChkSignZ:
   or    dh, dh
   js    NegX

   or    dl, dl
   jns   StoreZX

   fchs
   jmp   StoreZX

NegX:
   or    dl, dl
   js    QuadIII

   fldpi                        ; Pi, Pi-Angle, z.x
   fsubr                        ; Angle, z.x
   jmp   StoreZX

QuadIII:
   fldpi                        ; Pi, Angle+Pi, z.x
   fsub                         ; Angle,  z.x

StoreZX:
   mov   bx, z
   fstp  QWORD PTR [bx+8]       ; z.x
   fstp  QWORD PTR [bx]         ;
   ret
FPUcplxlog     ENDP

FPUsinhcosh    PROC     x:word, sinh:word, cosh:word
LOCAL Control:word
   fstcw Control
   push  Control                       ; Save control word on the stack
   or    Control, 0000110000000000b
   fldcw Control                       ; Set control to round towards zero

   mov   Sign, 0              ; Assume the sign is positive
   mov   bx, x

   fldln2                     ; ln(2)
   fdivr QWORD PTR [bx]       ; x/ln(2)

   cmp   BYTE PTR [bx+7], 0
   jns   DuplicateX

   fchs                       ; x = |x|

DuplicateX:
   fld   st                   ; x/ln(2), x/ln(2)
   frndint                    ; int = integer(|x|/ln(2)), x/ln(2)
   fxch                       ; x/ln(2), int
   fsub  st, st(1)            ; rem < 1.0, int
   fmul  PointFive            ; rem/2 < 0.5, int
      ; CAE 7Dec92 changed above from divide by 2 to multiply by .5
   f2xm1                      ; (2**rem/2)-1, int
   fadd  _1_                  ; 2**rem/2, int
   fmul  st, st               ; 2**rem, int
   fscale                     ; e**|x|, int
   fstp  st(1)                ; e**|x|

   cmp   BYTE PTR [bx+7], 0
   jns   ExitFexp

   fdivr _1_                  ; e**x

ExitFexp:
   fld   st                   ; e**x, e**x
   fdivr PointFive            ; e**-x/2, e**x
   fld   st                   ; e**-x/2, e**-x/2, e**x
   fxch  st(2)                ; e**x, e**-x/2, e**-x/2
   fmul  PointFive            ; e**x/2,  e**-x/2, e**-x/2
      ; CAE 7Dec92 changed above from divide by 2 to multiply by .5
   fadd  st(2), st            ; e**x/2,  e**-x/2, cosh(x)
   fsubr                      ; sinh(x), cosh(x)

   mov   bx, sinh             ; sinh, cosh
   fstp  QWORD PTR [bx]       ; cosh
   mov   bx, cosh
   fstp  QWORD PTR [bx]       ;

   pop   Control
   fldcw Control              ; Restore control word
   ret
FPUsinhcosh    ENDP

FPUsincos  PROC  x:word, sinx:word, cosx:word
LOCAL Status:word
   mov   bx, x
   fld   QWORD PTR [bx]       ; x

   cmp   fpu, 387
   jne   Use387FPUsincos

   call  _Loaded387sincos     ; cos(x), sin(x)
   mov   bx, cosx
   fstp  QWORD PTR [bx]       ; sin(x)
   mov   bx, sinx
   fstp  QWORD PTR [bx]       ;
   ret

Use387FPUsincos:

   sub   sp, 8                ; save 'x' on the CPU stack
   mov   bx, sp
   fstp  QWORD PTR [bx]       ; FPU stack:

   call  cos

   add   sp, 8                ; take 'cos(x)' off the CPU stack
   mov   bx, ax
   cmp   compiled_by_turboc,0
   jne   turbo_c1

   fld   QWORD PTR [bx]       ; FPU stack:  cos(x)

turbo_c1:
   fld   st                   ; FPU stack:  cos(x), cos(x)
   fmul  st, st               ; cos(x)**2, cos(x)
   fsubr _1_                  ; sin(x)**2, cos(x)
   fsqrt                      ; +/-sin(x), cos(x)

   mov   bx, x
   fld   QWORD PTR [bx]       ; x, +/-sin(x), cos(x)
   fldpi                      ; Pi, x, +/-sin(x), cos(x)
   fadd  st, st               ; 2Pi, x, +/-sin(x), cos(x)
   fxch                       ; |x|, 2Pi, +/-sin(x), cos(x)
   fprem                      ; Angle, 2Pi, +/-sin(x), cos(x)
   fstp  st(1)                ; Angle, +/-sin(x), cos(x)
   fldpi                      ; Pi, Angle, +/-sin(x), cos(x)

   cmp   BYTE PTR [bx+7], 0
   jns   SignAlignedPi

   fchs                       ; -Pi, Angle, +/-sin(x), cos(x)

SignAlignedPi:
   fcompp                     ; +/-sin(x), cos(x)
   fstsw Status               ; +/-sin(x), cos(x)

   mov   ax, Status
   and   ah, 1
   jz    StoreSinCos          ; Angle <= Pi

   fchs                       ; sin(x), cos(x)

StoreSinCos:
   mov   bx, sinx
   fstp  QWORD PTR [bx]       ; cos(x)
   mov   bx, cosx
   fstp  QWORD PTR [bx]       ;
   ret
FPUsincos   ENDP

PUBLIC r16Mul
r16Mul     PROC    uses si di, x1:word, x2:word, y1:word, y2:word
      mov   si, x1
      mov   bx, x2
      mov   di, y1
      mov   cx, y2

      xor   ax, ax
      shl   bx, 1
      jz    Exitr16Mult          ; Destination is zero

      rcl   ah, 1
      shl   cx, 1
      jnz   Chkr16Exp
      xor   bx, bx               ; Source is zero
      xor   si, si
      jmp   Exitr16Mult

   Chkr16Exp:
      rcl   al, 1
      xor   ah, al               ; Resulting sign in ah
      stc                        ; Put 'one' bit back into number
      rcr   bl, 1
      stc
      rcr   cl, 1

      sub   ch, 127              ; Determine resulting exponent
      add   bh, ch
      mov   al, bh
      mov   fake_es, ax          ; es has the resulting exponent and sign

      mov   ax, di
      mov   al, ah
      mov   ah, cl

      mov   dx, si
      mov   dl, dh
      mov   dh, bl

      mul   dx
      mov   cx, fake_es

      shl   ax, 1
      rcl   dx, 1
      jnc   Remr16MulOneBit      ; 'One' bit is the next bit over

      inc   cl                   ; 'One' bit removed with previous shift
      jmp   Afterr16MulNorm

   Remr16MulOneBit:
      shl   ax, 1
      rcl   dx, 1

   Afterr16MulNorm:
      mov   bl, dh               ; Perform remaining 8 bit shift
      mov   dh, dl
      mov   dl, ah
      mov   si, dx
      mov   bh, cl               ; Put in the exponent
      rcr   ch, 1                ; Get the sign
      rcr   bx, 1                ; Normalize the result
      rcr   si, 1
   Exitr16Mult:
      mov   ax, si
      mov   dx, bx
      ret
r16Mul      ENDP

PUBLIC RegFloat2Fg
RegFloat2Fg     PROC    x1:word, x2:word, Fudge:word
      mov   ax, WORD PTR x1
      mov   dx, WORD PTR x2
      mov   bx, ax
      or    bx, dx
      jz    ExitRegFloat2Fg

      xor   bx, bx
      mov   cx, bx

      shl   ax, 1
      rcl   dx, 1
      rcl   bx, 1                   ; bx contains the sign

      xchg  cl, dh                  ; cx contains the exponent

      stc                           ; Put in the One bit
      rcr   dl, 1
      rcr   ax, 1

      sub   cx, 127 + 23
      add   cx, Fudge
      jz    ChkFgSign
      jns   ShiftFgLeft

      neg   cx
   ShiftFgRight:
      shr   dx, 1
      rcr   ax, 1
      loop  ShiftFgRight
      jmp   ChkFgSign

   ShiftFgLeft:
      shl   ax, 1
      rcl   dx, 1
      loop  ShiftFgLeft

   ChkFgSign:
      or    bx, bx
      jz    ExitRegFloat2Fg

      not   ax
      not   dx
      add   ax, 1
      adc   dx, 0

   ExitRegFloat2Fg:
      ret
RegFloat2Fg    ENDP

PUBLIC RegFg2Float
RegFg2Float     PROC   x1:word, x2:word, FudgeFact:byte
      mov   ax, x1
      mov   dx, x2

      mov   cx, ax
      or    cx, dx
      jz    ExitFudgedToRegFloat

      mov   ch, 127 + 32
      sub   ch, FudgeFact
      xor   cl, cl
      shl   ax, 1       ; Get the sign bit
      rcl   dx, 1
      jnc   FindOneBit

      inc   cl          ; Fudged < 0, convert to postive
      not   ax
      not   dx
      add   ax, 1
      adc   dx, 0

   FindOneBit:
      shl   ax, 1
      rcl   dx, 1
      dec   ch
      jnc   FindOneBit
      dec   ch

      mov   al, ah
      mov   ah, dl
      mov   dl, dh
      mov   dh, ch

      shr   cl, 1       ; Put sign bit in
      rcr   dx, 1
      rcr   ax, 1

   ExitFudgedToRegFloat:
      ret
RegFg2Float      ENDP

PUBLIC ExpFudged
ExpFudged      PROC     uses si, x_low:word, x_high:word, Fudge:word
LOCAL exp:WORD
      xor   ax, ax
      mov   WORD PTR Ans, ax
      mov   WORD PTR Ans + 2, ax
      mov   ax, WORD PTR x_low
      mov   dx, WORD PTR x_high
      or    dx, dx
      js    NegativeExp

      div   Ln2Fg16
      mov   exp, ax
      or    dx, dx
      jz    Raiseexp

      mov   ax, dx
      mov   si, dx
      mov   bx, 1

   PosExpLoop:
      add   WORD PTR Ans, ax
      adc   WORD PTR Ans + 2, 0
      inc   bx
      mul   si
      mov   ax, dx
      xor   dx, dx
      div   bx
      or    ax, ax
      jnz   PosExpLoop

   Raiseexp:
      inc   WORD PTR Ans + 2
      mov   ax, WORD PTR Ans
      mov   dx, WORD PTR Ans + 2
      mov   cx, -16
      add   cx, Fudge
      add   cx, exp
      or    cx, cx
      jz    ExitExpFudged
      jns   LeftShift
      neg   cx

   RightShift:
      shr   dx, 1
      rcr   ax, 1
      loop  RightShift
      jmp   ExitExpFudged

   NegativeExp:
      not   ax
      not   dx
      add   ax, 1
      adc   dx, 0
      div   Ln2Fg16
      neg   ax
      mov   exp, ax

      or    dx, dx
      jz    Raiseexp

      mov   ax, dx
      mov   si, dx
      mov   bx, 1

   NegExpLoop:
      sub   WORD PTR Ans, ax
      sbb   WORD PTR Ans + 2, 0
      inc   bx
      mul   si
      mov   ax, dx
      xor   dx, dx
      div   bx
      or    ax, ax
      jz    Raiseexp

      add   WORD PTR Ans, ax
      adc   WORD PTR Ans + 2, 0
      inc   bx
      mul   si
      mov   ax, dx
      xor   dx, dx
      div   bx
      or    ax, ax
      jnz   NegExpLoop
      jmp   Raiseexp

   LeftShift:
      shl   ax, 1
      rcl   dx, 1
      loop  LeftShift

   ExitExpFudged:
      ret
ExpFudged      ENDP

PUBLIC   LogFudged
LogFudged      PROC     uses si di, x_low:word, x_high:word, Fudge:word
LOCAL exp:WORD
      xor   bx, bx
      mov   cx, 16
      sub   cx, Fudge
      mov   ax, x_low
      mov   dx, x_high

      or    dx, dx
      jz    ChkLowWord

   Incexp:
      shr   dx, 1
      jz    DetermineOper
      rcr   ax, 1
      inc   cx
      jmp   Incexp

   ChkLowWord:
      or    ax, ax
      jnz   Decexp
      jmp   ExitLogFudged

   Decexp:
      dec   cx                      ; Determine power of two
      shl   ax, 1
      jnc   Decexp

   DetermineOper:
      mov   exp, cx
      mov   si, ax                  ; si =: x + 1
      shr   si, 1
      stc
      rcr   si, 1
      mov   dx, ax
      xor   ax, ax
      shr   dx, 1
      rcr   ax, 1
      shr   dx, 1
      rcr   ax, 1                   ; dx:ax = x - 1
      div   si

      mov   bx, ax                  ; ax, Fudged 16, max of 0.3333333
      shl   ax, 1                   ; x = (x - 1) / (x + 1), Fudged 16
      mul   ax
      shl   ax, 1
      rcl   dx, 1
      mov   ax, dx                  ; dx:ax, Fudged 35, max = 0.1111111
      mov   si, ax                  ; si = (ax * ax), Fudged 19

      mov   ax, bx
   ; bx is the accumulator, First term is x
      mul   si                      ; dx:ax, Fudged 35, max of 0.037037
      mov   fake_es, dx             ; Save high word, Fudged (35 - 16) = 19
      mov   di, 0c000h              ; di, 3 Fudged 14
      div   di                      ; ax, Fudged (36 - 14) = 21
      or    ax, ax
      jz    Addexp

      mov   cl, 5
      shr   ax, cl
      add   bx, ax                  ; bx, max of 0.345679
   ; x = x + x**3/3

      mov   ax, fake_es             ; ax, Fudged 19
      mul   si                      ; dx:ax, Fudged 38, max of 0.004115
      mov   fake_es, dx             ; Save high word, Fudged (38 - 16) = 22
      mov   di, 0a000h              ; di, 5 Fudged 13
      div   di                      ; ax, Fudged (38 - 13) = 25
      or    ax, ax
      jz    Addexp

      mov   cl, 9
      shr   ax, cl
      add   bx, ax
   ; x = x + x**3/3 + x**5/5

      mov   ax, fake_es             ; ax, Fudged 22
      mul   si                      ; dx:ax, Fudged 41, max of 0.0004572
      mov   di, 0e000h              ; di, 7 Fudged 13
      div   di                      ; ax, Fudged (41 - 13) = 28
      mov   cl, 12
      shr   ax, cl
      add   bx, ax

   Addexp:
      shl   bx, 1                   ; bx *= 2, Fudged 16, max of 0.693147
   ; x = 2 * (x + x**3/3 + x**5/5 + x**7/7)
      mov   cx, exp
      mov   ax, Ln2Fg16            ; Answer += exp * Ln2Fg16
      or    cx, cx
      js    SubFromAns

      mul   cx
      add   ax, bx
      adc   dx, 0
      jmp   ExitLogFudged

   SubFromAns:
      neg   cx
      mul   cx
      xor   cx, cx
      xchg  cx, dx
      xchg  bx, ax
      sub   ax, bx
      sbb   dx, cx

   ExitLogFudged:
   ; x = 2 * (x + x**3/3 + x**5/5 + x**7/7) + (exp * Ln2Fg16)
      ret
LogFudged      ENDP

PUBLIC LogFloat14
LogFloat14     PROC     x1:word, x2:word
      mov   ax, WORD PTR x1
      mov   dx, WORD PTR x2
      shl   ax, 1
      rcl   dx, 1
      xor   cx, cx
      xchg  cl, dh

      stc
                rcr   dl, 1
                rcr   ax, 1

      sub   cx, 127 + 23
      neg   cx
      push  cx
      push  dx
      push  ax
      call  LogFudged
      add   sp, 6
      ret
LogFloat14     ENDP

PUBLIC RegSftFloat
RegSftFloat     PROC   x1:word, x2:word, Shift:byte
      mov   ax, x1
      mov   dx, x2

      shl   dx, 1
      rcl   cl, 1

      add   dh, Shift

      shr   cl, 1
      rcr   dx, 1

      ret
RegSftFloat      ENDP

PUBLIC RegDivFloat
RegDivFloat     PROC  uses si di, x1:word, x2:word, y1:word, y2:word
      mov   si, x1
      mov   bx, x2
      mov   di, y1
      mov   cx, y2

      xor   ax, ax
      shl   bx, 1
      jnz   ChkOtherOp
      jmp   ExitRegDiv           ; Destination is zero

   ChkOtherOp:
      rcl   ah, 1
      shl   cx, 1
      jnz   ChkDivExp
      xor   bx, bx               ; Source is zero
      xor   si, si
      jmp   ExitRegDiv

   ChkDivExp:
      rcl   al, 1
      xor   ah, al               ; Resulting sign in ah
      stc                        ; Put 'one' bit back into number
      rcr   bl, 1
      stc
      rcr   cl, 1

      sub   ch, 127              ; Determine resulting exponent
      sub   bh, ch
      mov   al, bh
      mov   fake_es, ax          ; es has the resulting exponent and sign

      mov   ax, si               ; 8 bit shift, bx:si moved to dx:ax
      mov   dh, bl
      mov   dl, ah
      mov   ah, al
      xor   al, al

      mov   bh, cl               ; 8 bit shift, cx:di moved to bx:cx
      mov   cx, di
      mov   bl, ch
      mov   ch, cl
      xor   cl, cl

      shr   dx, 1
      rcr   ax, 1

      div   bx
      mov   si, dx               ; Save (and shift) remainder
      mov   dx, cx               ; Save the quess
      mov   cx, ax
      mul   dx                   ; Mult quess times low word
      xor   di, di
      sub   di, ax               ; Determine remainder
      sbb   si, dx
      mov   ax, di
      mov   dx, si
      jc    RemainderNeg

      xor   di, di
      jmp   GetNextDigit

   RemainderNeg:
      mov   di, 1                ; Flag digit as negative
      not   ax                   ; Convert remainder to positive
      not   dx
      add   ax, 1
      adc   dx, 0

   GetNextDigit:
      shr   dx, 1
      rcr   ax, 1
      div   bx
      xor   bx, bx
      shl   dx, 1
      rcl   ax, 1
      rcl   bl, 1                ; Save high bit

      mov   dx, cx               ; Retrieve first digit
      or    di, di
      jz    RemoveDivOneBit

      neg   ax                   ; Digit was negative
      neg   bx
      dec   dx

   RemoveDivOneBit:
      add   dx, bx
      mov   cx, fake_es
      shl   ax, 1
      rcl   dx, 1
      jc    AfterDivNorm

      dec   cl
      shl   ax, 1
      rcl   dx, 1

   AfterDivNorm:
      mov   bl, dh               ; Perform remaining 8 bit shift
      mov   dh, dl
      mov   dl, ah
      mov   si, dx
      mov   bh, cl               ; Put in the exponent
      shr   ch, 1                ; Get the sign
      rcr   bx, 1                ; Normalize the result
      rcr   si, 1

   ExitRegDiv:
      mov   ax, si
      mov   dx, bx
      ret
RegDivFloat      ENDP

Term        equ
Num         equ
Factorial   equ
Sin         equ
Cos         equ
e           equ
Inve        equ

SinCos086   PROC     uses si di, LoNum:WORD, HiNum:WORD, SinAddr:WORD, \
                                CosAddr:WORD
   mov   ax, LoNum
   mov   dx, HiNum

   xor   cx, cx
;   mov   SinNeg, cx    ;CJLT - not needed now
;   mov   CosNeg, cx     ;CJLT - not needed now
   mov   a, cx          ;CJLT - Not needed by the original code, but it
                        ;       is now!
   or    dx, dx
   jns   AnglePositive

   not   ax
   not   dx
   add   ax, 1
   adc   dx, cx         ;conveniently zero
   mov   a,8            ;a now has 4 bits: Sign+quadrant+octant

AnglePositive:
   mov   si, ax
   mov   di, dx
   mul   WORD PTR InvPiFg33
   mov   bx, dx
   mov   ax, di
   mul   WORD PTR InvPiFg33
   add   bx, ax
   adc   cx, dx
   mov   ax, si
   mul   WORD PTR InvPiFg33 + 2
   add   bx, ax
   adc   cx, dx
   mov   ax, di
   mul   WORD PTR InvPiFg33 + 2
   add   ax, cx
   adc   dx, 0

   and   dx, 3  ;Remove multiples of 2 pi
   shl   dx, 1  ;move over to make space for octant number
;
;CJLT - new code to reduce angle to:  0 <= angle <= 45
;
   or    ax, ax
   jns   Lessthan45
   inc   dx     ;octant number
   not   ax     ;angle=90-angle if it was >45 degrees
Lessthan45:
   add   a,  dx ;remember octant and Sign in a
   mov   Num, ax ;ax=Term, now
;
; We do the Taylor series with all numbers scaled by pi/2
; so InvPiFg33 represents one. Truly.
;
   mov   Factorial, WORD PTR InvPiFg33 + 2
   mov   one, Factorial
   mov   Cos, Factorial          ; Cos = 1
   mov   Sin, Num                ; Sin = Num

LoopIntSinCos:
   TaylorTerm                    ; ax=Term = Num * (Num/2) * (Num/3) * . . .
   sub   Cos, Term               ; Cos = 1 - Num*(Num/2) + (Num**4)/4! - . . .
   cmp   Term, WORD PTR TrigLimit
   jbe   SHORT ExitIntSinCos

   TaylorTerm
   sub   Sin, Term               ; Sin = Num - Num*(Num/2)*(Num/3) + (Num**5)/5! - . . .
   cmp   Term, WORD PTR TrigLimit
   jbe   SHORT ExitIntSinCos

   TaylorTerm
   add   Cos, Term
   cmp   Term, WORD PTR TrigLimit
   jbe   SHORT ExitIntSinCos

   TaylorTerm                    ; Term = Num * (x/2) * (x/3) * . . .
   add   Sin, Term
   cmp   Term, WORD PTR TrigLimit
   jnbe  LoopIntSinCos

ExitIntSinCos:
   xor   ax, ax
   mov   cx, ax
   cmp   Cos, WORD PTR InvPiFg33 + 2
   jb    CosDivide               ; Cos < 1.0

   inc   cx                      ; Cos == 1.0
   jmp   StoreCos

CosDivide:
   mov   dx, Cos
   div   WORD PTR InvPiFg33 + 2

StoreCos:
   mov   Cos, ax                 ; cx:Cos

   xor   ax, ax
   mov   bx, ax
   cmp   Sin, WORD PTR InvPiFg33 + 2
   jb    SinDivide               ; Sin < 1.0

   inc   bx                      ; Sin == 1.0
   jmp   StoreSin

SinDivide:
   mov   dx, Sin
   div   WORD PTR InvPiFg33 + 2

StoreSin:
   mov   Sin, ax                 ; bx:Sin
; CJLT again. New tests are needed to correct signs and exchange sin/cos values
   mov   ax, a
   inc   al     ;forces bit 1 to xor of previous bits 0 and 1
   test  al, 2
   jz    ChkNegCos

   xchg  cx, bx
   xchg  Sin, Cos
;   mov   ax, SinNeg    commented out by CJLT. This was a bug.
;   xchg  ax, CosNeg
;   mov   CosNeg, ax    and this was meant to be  mov  SinNeg,ax

ChkNegCos:
   inc   al     ;forces bit 2 to xor of original bits 1 and 2
   test  al, 4
   jz    ChkNegSin

   not   Cos    ;negate cos if quadrant 2 or 3
   not   cx
   add   Cos, 1
   adc   cx, 0

ChkNegSin:
   inc   al
   inc   al     ;forces bit 3 to xor of original bits 2 and 3
   test  al, 8
   jz    CorrectQuad

   not   Sin
   not   bx
   add   Sin, 1
   adc   bx, 0

CorrectQuad:

CosPolarized:
   mov   dx, bx
   mov   bx, CosAddr
   mov   WORD PTR [bx], Cos
   mov   WORD PTR [bx+2], cx

SinPolarized:
   mov   bx, SinAddr
   mov   WORD PTR [bx], Sin
   mov   WORD PTR [bx+2], dx
   ret
SinCos086      ENDP

PUBLIC ArcTan086
;
;Used to calculate logs of complex numbers, since log(R,theta)=log(R)+i.theta
; in polar coordinates.
;
;In C we need the prototype:
;long ArcTan086(long, long)

;The parameters are x and y, the returned value arctan(y/x) in the range 0..2pi.
ArcTan086     PROC  uses si di, x1:word, x2:word, y1:word, y2:word
      xor   ax, ax ;ax=0
      mov   si, x1 ;Lo
      mov   bx, x2 ;Hi
      or    bx, bx ;Sign set ?
      jns   xplus
      inc   ax
      not   si
      not   bx
      add   bx, 1
      adc   si, 0       ;We have abs(x) now
 xplus:
      mov   di, y1 ;Lo
      mov   cx, y2 ;Hi
      or    cx, cx ;Sign set?
      jns   yplus
      inc   ax
      inc   ax     ;Set bit 1 of ax
      shl   ax, 1  ; and shift it all left
      not  di
      not   cx
      add   di, 1
      adc   cx, 0  ;We have abs(y) now
yplus:
      cmp   bx, cx      ;y>x?
      jl    no_xchg
      jg    xchg_xy
      cmp   si, di      ;Hi words zero. What about lo words?
      jle   no_xchg
xchg_xy:                ;Exchange them
      inc   ax          ;Flag the exchange
      xchg  si, di
      xchg  bx, cx
no_xchg:
      mov   SinNeg, ax  ;Remember signs of x and y, and whether exchanged
      or    cx, cx      ;y=1.0 ?
      jz    ynorm       ;no, so continue
normy:                  ;yes, normalise by reducing y to 16 bits max.
      rcr   cx, 1       ; (We don't really lose any precision)
      rcr   di, 1
      clc
      rcr   bx, 1
      rcr   si, 1
      or    cx, cx
      jnz   normy
ynorm:

      ret
ArcTan086       ENDP

;
;There now follows Chris Lusby Taylor's novel (such modesty!) method
;of calculating exp(x). Originally, I was going to do it by decomposing x
;into a sum of terms of the form:
;
;  th            -i
; i   term=ln(1+2  )
;                                                                        -i
;If x>term[i] we subtract term[i] from x and multiply the answer by 1 + 2
;
;We can multiply by that factor by shifting and adding. Neat eh!
;
;Unfortunately, however, for 16 bit accuracy, that method turns out to be
;slower than what follows, which is also novel. Here, I first divide x not
;by ln(2) but ln(2)/16 so that we can look up an initial answer in a table of
;2^(n/16). This leaves the remainder so small that the polynomial Taylor series
;converges in just 1+x+x^2/2 which is calculated as 1+x(1+x/2).
;
_e2xLT   PROC           ;argument in dx.ax (bitshift=16 is hard coded here)
   or    dx, dx
   jns   CalcExpLT

   not   ax             ; if negative, calc exp(abs(x))
   not   dx
   add   ax, 1
   adc   dx, 0

CalcExpLT:
         shl   ax, 1
      rcl   dx, 1
      shl   ax, 1
      rcl   dx, 1
      shl   ax, 1
      rcl   dx, 1               ;x is now in fg19 form for accuracy
                                ; so, relative to fg16, we have:
   div   Ln2Fg15                ; 8x==(a * Ln(2)/2) + Rem
                                ;  x=(a * Ln(2)/16) + Rem/8
                                ;so exp(x)=exp((a * Ln(2)/16) + Rem/8)
                                ;         =exp(a/16 * Ln(2)) * exp(Rem/8)
                                ;         =2^(a/16) * exp(Rem)
                                ;a mod 16 will give us an initial estimate
   mov  cl, 4
   mov  di, ax                  ;Remember original
   shr  ax, cl
   mov   a, ax                  ;a/16 will give us a bit shift
   mov  ax, di
   and  ax, 0000fh              ;a mod 16
   shl  ax, 1                   ;used as an index into 2 byte per element Table
   mov  di, ax
   dec  cx                      ;3, please
   add   dx, 4                  ;to control rounding up/down
   shr   dx, cl                 ;Num=Rem/8 (convert back to fg16)
                                ;
   mov   ax, dx
   shr   ax, 1                  ;Num/2  fg 16
   inc   ax                     ;rounding control
   stc
   rcr   ax, 1                  ;1+Num/2   fg15
   mul   dx                     ;dx:ax=Num(1+Num/2) fg31, so dx alone is fg15
   shl   ax, 1                  ;more rounding control
   adc   dx, 8000H              ;dx=1+Num(1+Num/2) fg15
   mov   ax, Table[di]          ;Get table entry fg15
   mul   dx                     ;Only two multiplys used!  fg14, now (15+15-16)
   shl   ax, 1
   rcl   dx, 1                  ;fg15
   mov   e,  dx
   ret                          ; return e=e**x * (2**15), 1 < e**x < 2
_e2xLT   ENDP                   ;        a= bitshift needed

Exp086    PROC     uses si di, LoNum:WORD, HiNum:WORD
   mov   ax, LoNum
   mov   dx, HiNum
;   mov   overflow, 0

    call  _e2xLT        ;Use Chris Lusby Taylor's e2x

   cmp   a, 15          ;CJLT - was 16
   jae   Overflow

;   cmp   expSign, 0
;   jnz   NegNumber
   cmp   HiNum, 0       ;CJLT - we don't really need expSign
   jl   NegNumber

   mov   ax, e
   mov   dx, ax
   inc   a
   mov   cx, 16
   sub   cx, a
   shr   dx, cl
   mov   cx, a
   shl   ax, cl
   jmp   ExitExp086

Overflow:
   xor   ax, ax
   xor   dx, dx
   mov   overflow, 1
   jmp   ExitExp086

NegNumber:
   cmp   e, 8000h
   jne   DivideE

   mov   ax, e
   dec   a
   jmp   ShiftE

DivideE:
   xor   ax, ax
   mov   dx, ax
   stc
   rcr   dx, 1
   div   e

ShiftE:
   xor   dx, dx
   mov   cx, a
   shr   ax, cl

ExitExp086:
   ret
Exp086    ENDP

SinhCosh086    PROC     uses si di, LoNum:WORD, HiNum:WORD, SinhAddr:WORD, \
                                   CoshAddr:WORD
   mov   ax, LoNum
   mov   dx, HiNum

   call  _e2xLT         ;calculate exp(|x|) fg 15
                        ;answer is e*2^a
   cmp   e, 8000h       ;e==1 ?
   jne   InvertE        ; e > 1, so we can invert it.

   mov   dx, 1
   xor   ax, ax
   cmp   a, 0
   jne   Shiftone

   mov   e, ax
   mov   cx, ax
   jmp   ChkSinhSign

Shiftone:
   mov   cx, a
   shl   dx, cl
   dec   cx
   shr   e, cl
   shr   dx, 1
   shr   e, 1
   mov   cx, dx
   sub   ax, e
   sbb   dx, 0
   xchg  ax, e
   xchg  dx, cx
   jmp   ChkSinhSign

InvertE:
   xor   ax, ax               ; calc 1/e
   mov   dx, 8000h
   div   e

   mov   Inve, ax

ShiftE:
   mov   cx, a
   shr   Inve, cl
   inc   cl
   mov   dx, e
   shl   e, cl
   neg   cl
   add   cl, 16
   shr   dx, cl
   mov   cx, dx               ; cx:e == e**Exp

   mov   ax, e                ; dx:e == e**Exp
   add   ax, Inve
   adc   dx, 0
   shr   dx, 1
   rcr   ax, 1                ; cosh(Num) = (e**Exp + 1/e**Exp) / 2

   sub   e, Inve
   sbb   cx, 0
   sar   cx, 1
   rcr   e, 1

ChkSinhSign:
   or    HiNum, 0
   jns   StoreHyperbolics

   not   e
   not   cx
   add   e, 1
   adc   cx, 0

StoreHyperbolics:
   mov   bx, CoshAddr
   mov   WORD PTR [bx], ax
   mov   WORD PTR [bx+2], dx

   mov   bx, SinhAddr
   mov   WORD PTR [bx], e
   mov   WORD PTR [bx+2], cx

   ret
SinhCosh086    ENDP

Log086   PROC     uses si di, LoNum:WORD, HiNum:WORD, Fudge:WORD
LOCAL Exp:WORD, Accum:WORD, LoAns:WORD, HiAns:WORD
;NOTE: CJLT's method does not need LoAns, HiAns, but he hasn't yet
;taken them out
      xor   bx, bx
      mov   cx, Fudge
      mov   ax, LoNum
      mov   dx, HiNum
;      mov   overflow, 0

      or    dx, dx
      js    Overflow
      jnz   ShiftUp

      or    ax, ax
      jnz   ShiftUp

   Overflow:
      mov   overflow, 1
      jmp   ExitLog086

   ShiftUp:
      inc   cx                      ; cx = Exp
      shl   ax, 1
      rcl   dx, 1
      or    dx, dx
      jns   ShiftUp             ;shift until dx in fg15 form

      neg   cx
      add   cx, 31
      mov   Exp, cx
;CJLT method starts here. We try to reduce x to make it very close to 1
;store LoAns in bx for now (fg 16; initially 0)
      mov  cl,2         ;shift count
redoterm2:
      cmp  dx, 0AAABH   ;x > 4/3 ?
      jb  doterm3
      mov  ax, dx
      shr  ax, cl
      sub  dx, ax       ;x:=x(1-1/4)
      add  bx, 49a5H    ;ln(4/3) fg 15
      jmp  redoterm2
 doterm3:
      inc  cl           ;count=3
redoterm3:
      cmp  dx, 9249H    ;x > 8/7 ?
      jb  doterm4
      mov  ax, dx
      shr  ax, cl
      sub  dx, ax       ;x:=x(1-1/8)
      add  bx, 222eH    ;ln(8/7)
      jmp  redoterm3
 doterm4:
      inc  cl           ;count=4
      cmp  dx, 8889H    ;x > 16/15 ?
      jb  skipterm4
      mov  ax, dx
      shr  ax, cl
      sub  dx, ax       ;x:=x(1-1/16)
      add  bx, 1085h    ;ln(16/15)
;No need to retry term4 as error is acceptably low if we ignore it
skipterm4:
;Now we have reduced x to the range 1 <=x <1.072
;
;Now we continue with the conventional series, but x is so small we
;can ignore all terms except the first!
;i.e.:
;ln(x)=2(x-1)/(x+1)
      sub   dx, 8000h           ; dx= x-1, fg 15
      mov   cx, dx
      stc
      rcr   cx, 1               ; cx = 1 + (x-1)/2   fg 15
                                ;   = 1+x            fg 14
      mov   ax,4000H            ;rounding control (Trust me)
      div   cx                  ;ax=ln(x)
      add   bx, ax              ;so add it to the rest of the Ans. No carry
   MultExp:
      mov   cx, Exp
      mov   ax, Ln2Fg16
      or    cx, cx
      js    SubFromAns

      mul   cx                      ; cx = Exp * Lg2Fg16, fg 16
      add   ax, bx              ;add bx part of answer
      adc   dx, 0
      jmp   ExitLog086

   SubFromAns:
      inc   bx          ;Somewhat artificial, but we need to add 1 somewhere
      neg   cx
      mul   cx
   not   ax
      not   dx
      add   ax, bx
      adc   dx, 0

   ExitLog086:
      ret
Log086   ENDP

END


ASM Mode Reel

 NEWTON.ASM : Procedure NewtonFractal() from FRACTINT.
 Lee Daniel Crocker, 4/23/89.

; Tabs: 8
;
; Modifications:
;   BT = Bert Tyler
;   TW = Timothy Wegner
;   RD = Robert Day
;   MP = Mark Peterson
;
; Note: newton.asm was totally rewritten by Lee Crocker for FRACTINT 10.0
;    for integration with the newly structured fractal engine in calcmand.c
;    and fractals.c. The current routine consists of the inner orbit
;    calculation, with the supporting code removed. Early versions of
;    newton.asm contained a complete newton fractal function.
;
; Assembled by Microsoft Macro Assembler 5.1, for use with Microsoft C 5.1.
;

; Required for compatibility if Turbo ASM (BT 5/20/89)

IFDEF ??version
MASM51
QUIRKS
ENDIF

.model  medium, c

public  NewtonFractal2
public  invertz2

.data
        extrn   coloriter:dword, maxcolor:word, degree:word, basin:word
        extrn   row:word, col:word

        extrn   dx0:dword, dy0:dword
        extrn   dx1:dword, dy1:dword

        extrn   old:qword, new:qword, d1overd:qword, roverd:qword
        extrn   threshold:qword, floatmin:qword, floatmax:qword
        extrn   f_radius:qword, f_xcenter:qword, f_ycenter:qword
        extrn   roots:word, tempsqrx:qword

statw   dw      ?

.code

public  NewtonFractal2
NewtonFractal2 proc

;
; cpower(&old, degree-1, &tmp)
;
        mov     ax, degree
        dec     ax

        fld     old + 8
        fld     old
;
; cpower() is expanded inline here.
;
        shr     ax, 1
        jnc     load1                   ; if (exp & 1)

        fld     st(1)
        fld     st(1)
        jmp     short looptop           ; tmp = old
load1:
        fldz
        fld1                            ; else tmp = [1,0]
looptop:
        cmp     ax, 0
        je      loopexit                ; while (exp)

        fld     st(2)                   ; RD 5/7/89: Calculate xt^2 - yt^2
        fadd    st, st(4)               ; by using (xt+yt)*(xt-yt), which
        fld     st(3)                   ; trades one multiplication for an
        fsub    st, st(5)               ; addition.  This trick saves a
        fmul                            ; whopping 1.2% of time.

        fld     st(4)
        fmul    st, st(4)
        fadd    st, st                  ; yt = 2 * xt * yt

        fstp    st(5)                   ; tmp.y = yt
        fstp    st(3)                   ; tmp.x = xt

        shr     ax, 1
        jnc     looptop                 ; if (exp & 1)

        fld     st(2)
        fmul    st, st(1)
        fld     st(4)
        fmul    st, st(3)
        fsub                            ; tmp.x = xt * tmp.x - yt * tmp.y

        fld     st(3)
        fmul    st, st(3)
        fld     st(5)
        fmul    st, st(3)
        fadd                            ; tmp.y = xt * tmp.y + yt * tmp.x
        fstp    st(3)
        fstp    st(1)

        jmp     short looptop
loopexit:
        fstp    st(2)
        fstp    st(2)
;
; End of complex_power() routine.  Result is in ST, ST(1)
;
;
; complex_mult(tmp, old, &new);
;
        fld     old + 8
        fld     old

        fld     st(3)       ; tmp.y
        fmul    st, st(1)   ; old.x
        fld     st(3)       ; tmp.x
        fmul    st, st(3)   ; old.y
        fadd
        fld     st(3)       ; tmp.x
        fmul    st, st(2)   ; old.x
        fld     st(5)       ; tmp.y
        fmul    st, st(4)   ; old.y
        fsub
;
; if (DIST1(new) < THRESHOLD) {
;
        fld1
        fsubr   st, st(1)
        fmul    st, st
        fld     st(2)       ; new.y
        fmul    st, st
        fadd
        fcomp   threshold
        fstsw   statw
        mov     ax, statw
        sahf
        jnc     notless
;
; if (fractype == NEWTBASIN) {
;
        mov     ax, basin
        cmp     ax, 0
        je      notbasin

        mov     bx, roots
        mov     dx, -1                  ; tempcolor = -1
        sub     cx, cx
dloop:
        fld     qword ptr [bx]  ; roots[i].x
        fsub    st, st(3)       ; old.x
        fmul    st, st
        fld     qword ptr [bx+8]; roots[i].y
        fsub    st, st(5)       ; old.y
        fmul    st, st
        fadd
        fcomp   threshold
        fstsw   statw
        mov     ax, statw
        sahf                            ; if (distance(roots[i],old) < threshold)...
        jnc     nl2

; TW commented out next few lines and add dx,ax to eliminate newtbasin
; color shades per Phil Wilson's request 12/03/89

; TW put it back in in response to another use as an option! 7/7/90
        mov     dx, cx
        cmp     basin,2                 ; basin==2 is flag for stripes
        jne     nostripes
        mov     ax, word ptr coloriter
        and     ax, 1
        shl     ax, 1
        shl     ax, 1
        shl     ax, 1

        and     dx, 7
        add     dx, ax
nostripes:
        inc     dx                      ; tempcolor = 1+(i&7)+((color&1)<<3)
        jmp     short nfb               ; break
nl2:
        add     bx, 16
        inc     cx
        cmp     cx, degree
        jl      dloop
nfb:
        mov     ax, dx
        cmp     dx, -1
        jne     notm1
        mov     ax, maxcolor            ; if (tmpcolor == -1)...
notm1:
        mov     word ptr coloriter, ax
        mov     word ptr coloriter+2, 0
notbasin:
        mov     ax, 1
        jmp     nlexit
notless:
        fld     d1overd
        fmul    st(2), st               ; new.y *= d1overd
        fmul
        fld     roverd
        fadd                            ; new.x = d1overd * new.x + roverd

        fld     st(5)       ; tmp.y
        fmul    st, st
        fld     st(5)       ; tmp.x
        fmul    st, st
        fadd
        fcom    floatmin
        fstsw   statw
        mov     ax, statw
        sahf                            ; if (mod(tmp) < FLT_MIN) {
        jnc     cont
        mov     ax, 1
        fstp    st
        jmp     nlexit
cont:
        fld1
        fdivr
        fst     st(4)       ; old.y
        fstp    st(3)       ; old.x

        fld     st(4)       ; tmp.x
        fmul    st, st(1)   ; new.x
        fld     st(6)       ; tmp.y
        fmul    st, st(3)   ; new.y
        fadd
        fmulp   st(3), st   ; old.x

        fld     st(4)       ; tmp.x
        fmul    st, st(2)   ; new.y
        fld     st(6)       ; tmp.y
        fmul    st, st(2)   ; new.x
        fsub
        fmulp   st(4), st   ; old.y     ; old = new / tmp

; MP Orbit Bug Fix 11/1/90
;       fstp    new
;       fstp    new + 8
;       fstp    old
;       fstp    old + 8
        fstp    st
        fstp    st
        fst     new
        fstp    old
        fst     new + 8
        fstp    old + 8

        mov     ax, 0
        jmp     nlx2

nlexit:

;MP Orbit Bug Fix 11/1/90
;       fstp    new
;       fstp    new + 8
        fstp    st
        fstp    st
        fstp    new
        fstp    new + 8

nlx2:
        fstp    st
        fstp    st

        ret
NewtonFractal2 endp
;
;
;
public  invertz2
invertz2 proc   uses si, zval:word      ; TW 11/03/89 changed zval to near

        fld     f_xcenter
        fld     f_ycenter

        mov     bx, col
        shl     bx, 1
        shl     bx, 1
        shl     bx, 1

        mov     cx, row
        shl     cx, 1
        shl     cx, 1
        shl     cx, 1

        mov     ax, ds
        lds     si, dx0
        add     si, bx
        fld     qword ptr [si]
        mov     ds, ax
        lds     si, dx1
        add     si, cx
        fld     qword ptr [si]
        fadd
        fsub    st, st(2)

        mov     ds, ax
        lds     si, dy0
        add     si, cx
        fld     qword ptr [si]
        mov     ds, ax
        lds     si, dy1
        add     si, bx
        fld     qword ptr [si]
        fadd
        fsub    st, st(2)

        mov     ds, ax
        fld     st(1)
        fmul    st, st
        fld     st(1)
        fmul    st, st
        fadd

        fcom    floatmin
        fstsw   statw
        mov     ax, statw
        sahf
        jnc     inl1

        fstp    st
        fld     floatmax
        jmp     icom
inl1:
        fld     f_radius
        fdivr
icom:
        fst     tempsqrx

        fmul    st(2), st
        fmul
        faddp   st(2), st
        faddp   st(2), st

;       lds     si, zval        ; TW 11/03/89 zval is now a near pointer
        mov     si, zval        ; TW
        fstp    qword ptr [si+8]
        fstp    qword ptr [si]

        ret
invertz2 endp

END


FpuIntro

FPU Introduction*

Vu dans ... ASMBOOK.chm de http://codes-sources.commentcamarche.net/source/32244-aide-assembleur-avec-exemples-sujets-abordes-processeur-masm32-radasm-api-fpu-etc

Introduction:
Faire des calculs mathématique avec le processeur n'est pas toujours simple car il ne travail que sur des nombre entier, c'est pourquoi les constructeurs ajoute un coprocesseur mathématique qui lui, est entièrement dédié a cette tâche. Le coprocesseur n'accède pas directement a la Ram, cela complique un petit peu notre affaire mais il est capable de faire très rapidement des calculs sur des nombres codés dans des registres de 80 bits. Les échanges avec le processeur permette de recevoir des valeurs de 64 bits maximum, les valeurs supérieur seront donc tronquées. Le coprocesseur utilise une pile pour stocker les valeurs, cette pile STx peut stocker 8 valeurs de ST0 à ST7.
Attention aux instructions de transfert les valeurs seront empilées ou dépilés selon l'instruction utilisée, il s'agit bien d'un pile.(push empile, pop dépile)
N'oubliez pas non plus d'initialiser avant de s'en servir avec l'instruction Finit.

Une astuce assez mignonne consiste a faire des macros dans un fichier et de l'inclure à votre projet pour s'en servir. Il y'a avec masm32 la fpulib qui contient des fonctions prêts à l'emploi.

Les registres du FPU

On l'a vue une pile de 8 registres de 80 bits pour stocker les nombres.
Mais le FPU c'est aussi 3 registres de 16 Bits.
Le registre de control
Le registre de Status
Le registre de Tag

Registre de Control

Registre de 16 Bits, Permettant de selectionner plusieurs mode de fonctionnement de la FPU.

15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
 |  |  | IC | RC |  | PC |  | IEM |  | PM | UM | OM | ZM | DM | IM

IM Invalid operation Mask
DM Denormalized operand Mask
ZM Zero divide Mask, masque de division par 0
OM Overflow Mask, masque de dépassement
UM Underflow Mask, masque d'underflow (probablement si on dépile trop, plus de pop que de push)
PM Precision Mask, masque de précision

The IEM field (bit 7) or Interrupt Enable Mask determines whether any of the interrupt masks will be enabled (bit = 0) or all those masks will be disabled (bit = 1). This bit field is set to 1 in the initialized state. (This field is also for compatibility with early co-processors and not used anymore.)

Bits 5-0 are the interrupt masks. In the initialized state, they are all set to 1 which lets the FPU handle all exceptions. When any one of them is set to 0, it instruct the FPU to generate an interrupt whenever that particular exception is detected so that the program will take whatever action may be deemed necessary before returning control to the FPU.

The IC field (bit 12) or Infinity Control allows for two types of infinity arithmetic:

0 = Both -infinity and +infinity are treated as unsigned infinity (initialized state)
1 = Respects both -infinity and +infinity

This field has been retained for compatibility with the 287 and earlier co-processors. In the more modern FPUs, this bit is disregarded and both -infinity and +infinity are respected.

The RC field (bits 11 and 10) or Rounding Control determines how the FPU will round results in one of four ways:

00 = Round to nearest, or to even if equidistant (this is the initialized
state)
01 = Round down (toward -infinity)
10 = Round up (toward +infinity)
11 = Truncate (toward 0)

The PC field (bits 9 and 8) or Precision Control determines to what precision the FPU rounds results after each arithmetic instruction in one of three ways:

00 = 24 bits (REAL4)
01 = Not used
10 = 53 bits (REAL8)
11 = 64 bits (REAL10) (this is the initialized state)

Les Bits 6 et 13 à 15 sont non utilisé ou reservé.(ndla: Mylène semble reservé aussi)

Les fonctions FSTCW (fpu store control word) et FLDCW (fpu load control word) permettent respectivement de sauver et de charger le registre de control.

Registre de Statut

Registre de 16 bits indiquant l'état dans laquel se situe la FPU.Ce registre peut-être modifié après chaque instruction son acces complète ne se fait quand lecture.
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
B | C3 | TOP |  |  | C2 | C1 | C0 | IR | SF | P | U | O | Z | D | I

A l'initialisation de la FPU tous les bits sont mis à 0.

Le bit 15 BUSY (B) indique que la FPU est occupé.

The C3 (bit 14) and C2 - C0 (bits 10-8) fields contain the condition codes following the execution of some instructions such as comparisons. These codes will be explained in detail for each instruction affecting those fields.

The TOP field (bits 13-11) is where the FPU keeps track of which of its 80-bit registers is at the TOP. The BC numbers described previously for the FPU's internal numbering system of the 80-bit registers would be displayed in that field. When the programmer specifies one of the FPU 80-bit registers ST(x) in an instruction, the FPU adds (modulo 8) the ST number supplied to the value in this TOP field to determine in which of its registers the required data is located.

The IR field (bit 7) or Interrupt Request gets set to 1 by the FPU while an exception is being handled and gets reset to 0 when the exception handling is completed. When the interrupt is masked in the Control Word for the FPU to handle the exception, this bit may never be seen to be set while stepping through the instructions with a debugger. However, if the programmer handles the interrupt, that bit should remain set until the interrupt handling routine is completed.

Bits 6-0 are flags raised by the FPU whenever it detects an exception. Those exception flags are cumulative in the sense that, once set (bit=1), they are not reset (bit=0) by the result of a subsequent instruction which, by itself, would not have raised that flag. Those flags can only be reset by either initializing the FPU (FINIT instruction) or by explicitly clearing those flags (FCLEX instruction).

The SF field (bit6) or Stack Fault exception is set whenever an attempt is made to either load a value into a register which is not free (the C1 bit would also get set to 1) or pop a value from a register which is free (and the C1 bit would get reset to 0). (Such stack fault is also treated as an invalid operation and the I field flag bit0 would thus also be set by this exception; see below.)

The P field (bit5) or Precision exception is set whenever some precision is lost by instructions which do exact arithmetic. For example, dividing 1 by 10 does not yield an exact value in binary arithmetic and would set the P exception flag. Another example which sets the P exception flag would be the conversion of a REAL10 to a REAL4 when some of the least significant bits would be lost.
If the FPU handles this exception (when the PM bit is set in the Control Word), it rounds the result according to the rounding mode specified in the RC field of the Control Word.

The U field (bit4) or Underflow exception flag gets set whenever a value is too small (without being equal to 0) to be represented properly. Each of the floating point formats has a different limit on the smallest number which can be represented. The U flag gets set if the result of an operation exceeds that limit. For example, dividing a valid very small number by a large number could exceed the limit. A valid REAL10 small number may be much smaller than acceptable for the REAL4 or REAL8 formats; in such cases, conversion from the former to the latter would also set the U flag.
If the FPU handles this exception (when the UM bit is set in the Control Word), it would denormalize the value until the exponent is in range or ultimately return a 0.

The O field (bit3) or Overflow exception flag gets set whenever a value is too large in magnitude to be represented properly. Again, each of the floating point formats has a different limit on the largest number which can be represented. The O flag gets set if the result of an operation exceeds that limit. For example, multiplying a valid very large number by another large number could exceed the limit. A valid REAL10 large number may be much larger than acceptable for the REAL4 or REAL8 formats; conversion from the former to the latter would also set the O flag.
If the FPU handles this exception (when the OM bit is set in the Control Word), it would generate a properly signed INFINITY according to the IC flag of the Control Word.

The Z field (bit2) or Zero divide exception flag gets set whenever the division of a finite non-zero value by 0 is attempted.
If the FPU handles this exception (when the ZM bit is set in the Control Word), it would generate a properly signed INFINITY according to the XOR of the operand signs and then according to the IC flag of the Control Word.

The D field (bit1) or Denormalized exception flag gets set whenever an instruction attempts to operate on a denormalized number or the result of the operation is a denormalized number.
If the FPU handles this exception (when the DM bit is set in the Control Word), it would simply continue with normal processing and then check for other possible exceptions.

Le bit 0 Invalid (I) est positionné quand la FPU considère l'opération non valide.

Cela peut se produire par exemple si:
- On empile plus+ que la pile ne peut recevoir (overflow) ou si l'on dépile (pop) plus que possible.
- Si l'on divise 0 par 0, ou si l'on soustrait l'infini à l'infini.

Si la FPU rencontre cette exception alors que le masque IM du registre de control est mis à 1, la fonction retournera un NAN si l'un est utilisé comme opérande sinon elle retournera un "INDEFINITE" NAN.*

Voir l'instruction FSTSW FPU store status word.

Registre de TAG (Marqueurs)

15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
Tag7 |  | Tag6 |  | Tag5 |  | Tag4 |  | Tag3 |  | Tag2 |  | Tag1 |  | Tag0

Un tag est un groupe de 2 bits. Par exemple le bit 0 et le bit 1 forme à eux deux le Tag0 correspondant au registre ST0.Chaque tag peut donc avoir 4 valeurs.

00 | Le registre contient une valeur valide non équivalente à zéro.
01 | Le registre contient la valeur de 0.
10 | Le registre contient une valeur spécifique, infinie par exemple.
11 | Le registre est vide.

(Partiellement traduit et adapté du tutoriel sur la fpulib)

Auteurs et sources: BruNews / Babasse celon les infos traduitent de Raymond Filiatreault
Contact: bnutiles3@free.fr

SOURCES UTILES SUR LE FPU

http://www.asmfr.com/code.aspx?ID=20825
Tres bonne exemple de programmation avec le fpu


FpuAlpha

FPU Instructions par ordre alphabétique

F2XM1 2 to the X power minus 1
FABS Valeur absolue de ST(0)
FADD Additionne 2 valeurs.
FADDP Additionne 2 valeurs et fait un pop ST(0)
FBLD Charge un BCD depuis la mémoire
FBSTP Sauve un BCD dans la mémoire
FCHS Change le signe de ST(0)
FCLEX Efface les exceptions
FCMOVcc Déplacement conditionné par les flags du CPU
FCOM Compare ST(0) à une autre valeur
FCOMI Compare ST(0) à ST(i) et modifie les flags du CPU
FCOMIP Compare ST(0) à ST(i) modifie les flags du CPU flags et fait un pop ST(0)
FCOMP Compare ST(0) à une valeur et fait un pop ST(0)
FCOMPP Compare ST(0) à ST(1) et fait un pop sur les 2 registres
FCOS Cosinus de la valeur ST(0)
FDECSTP Decremente le compteur de pile
FDIV Divise 2 valeurs
FDIVP Divise 2 Valeur et fait un pop ST(0)
FDIVR Divise à l'envers 2 valeurs
FDIVRP Divise à l'envers 2 valeurs et fait un pop ST(0)
FFREE Libère un registre de donnée
FIADD Ajoute un entier en mémoire à ST(0)
FICOM Compare ST(0) à une valeur entière
FICOMP Compare ST(0) à une valeur entière et fait un pop ST(0)
FIDIV Divise ST(0) par un entier en mémoire
FIDIVR Divise un entier en mémoire par ST(0)
FILD Charge un entier depuis la mémoire
FIMUL Multiplie ST(0) par un entier en mémoire
FINCSTP Incremente le pointeur de pile
FINIT Initialise la FPU
FIST Sauve un entier dans la mémoire
FISTP Sauve un entier dans la mémoire et fait un pop ST(0)
FISUB Soustrait un entier en mémoire à ST(0)
FISUBR Soustrait ST(0) à un entier en mémoire
FLD Charge un nombre (réel)
FLD1 Charge la valeur de 1
FLDCW Charge un word dans le registre de control
FLDENV LoaD environment*
FLDL2E Load the log base 2 of e (Napierian constant)*
FLDL2T Load the log base 2 of Ten*
FLDLG2 Load the log base 10 of 2 (common log of 2)*
FLDLN2 Load the log base e of 2 (natural log of 2)*
FLDPI Charge la valeur de PI
FLDZ Charge la valeur de Zero
FMUL Multiplie deux valeurs
FMULP Multiplie deux valeurset fait un pop ST(0)
FNCLEX Efface les exceptions (no wait)
FNINIT Initialise la FPU (no wait)
FNOP Ne fait rien, l'équivalent d'un nop mais pour la fpu
FNSAVE Sauve le registre d'état du FPU (no wait)
FNSTCW Sauve le registre de control (no wait)
FNSTENV Store environment (no wait)*
FNSTSW Store status word (no wait)
FPATAN Partial arctangent of the ratio ST(1)/ST(0)
FPREM Partial remainder
FPREM1 Partial remainder 1
FPTAN Partial tangent of the angle value in ST(0)
FRNDINT arrondis ST(0) dans un entier(integer)
FRSTOR Restore all registers
FSAVE Save state of FPU
FSCALE Scale ST(0) by ST(1)
FSIN Sine of the angle value in ST(0)
FSINCOS Sine and cosine of the angle value in ST(0)
FSQRT Square root of ST(0)
FST Store real number
FSTCW Store control word
FSTENV Store environment
FSTP Store real number and pop ST(0)
FSTSW Store status word
FSUB Subtract two floating point values
FSUBP Subtract two floating point values and pop ST(0)
FSUBR Subtract in reverse two floating point values
FSUBRP Subtract in reverse two floating point values and Pop ST(0)
FTST Test ST(0) par comparaison à +0.0
FUCOM Unordered Compare ST(0) to a floating point value
FUCOMI Unordered Compare ST(0) to ST(i) and set CPU flags
FUCOMIP Unordered Compare ST(0) to ST(i) and set CPU flags and pop ST(0)
FUCOMP Unordered Compare ST(0) to a floating point value and pop ST(0)
FUCOMPP Unordered Compare ST(0) to ST(1) and pop both registers
FWAIT Attends tant que la FPU est occupé
FXAM Examine the content of ST(0)
FXCH Exchange the top data register with another data register
FXTRACT Extract exponent and significand
FYL2X Y*Log2X
FYL2XP1 Y*Log2(X+1)


FADD

FADD m32 ST(0) += m32
FADD m64 ST(0) += m64
FADD ST(0), ST(i) ST(0) += ST(i)
FADD ST(i), ST(0) ST(i) += ST(0)


FADDP

FADDP ST(i), ST(0) ; ST(i) += ST(0) ; pop stack
FADDP ; ST(1) += ST(0) ; pop stack


Fiadd

FIADD src

FIADD m32int ST(0) += m32int
FIADD m16int ST(0) += m16int


FDIV

Instruction
        Description
FDIV m32real        ST(0) /= m32real
FDIV m64real        ST(0) /= m64real
FDIV ST(0), ST(i)   ST(0) /= ST(i)
FDIV ST(i), ST(0)   ST(i) /= ST(0)


FDIVP

FDIVP ST(i), ST(0) ST(i) /= ST(0) et pop stack
FDIVP ST(1) /= ST(0) et pop stack


FDIVR

Instruction
        Description
FDIVR m32real       ST(0) = m32real / ST(0)
FDIVR m64real       ST(0) = m64real / ST(0)
FDIVR ST(0), ST(i)  ST(0) = ST(i) / ST(0)
FDIVR ST(i), ST(0)  ST(i) = ST(0) / ST(i)


FDIVRP

FDIVRP ST(i), ST(0) ST(i) = ST(0) / ST(i)
FDIVRP ST(1) = ST(0) / ST(1)

pop stack ensuite


FIDIV

FIDIV src

FIDIV m32int ST(0) /= m32int
FIDIV m16int ST(0) /= m16int


FIDIVR

FIDIVR src

FIDIVR m32int ST(0) = m32int / ST(0)
FIDIVR m16int ST(0) = m16int / ST(0)


FABS

ST(0) = ABS(ST(0));


FCHS

Change le signe de ST(0)


FCOM

FCOM m32real     cmp ST(0) et m32real
FCOM m64real     cmp ST(0) et m64real
FCOM ST(i)       cmp ST(0) et ST(i)
FCOM             cmp
ST(0) et ST(1)

Le signe du zéro est ignoré.

Condition       C3   C2   C0
ST(0) > SRC      0    0    0
ST(0) < SRC      0    0    1
ST(0) = SRC      1    0    0

Unordered 1 1 1


FCOMP

FCOMP m32real cmp ST(0) et m32real

FCOMP m64real cmp ST(0) et m64real

FCOMP ST(i) cmp ST(0) et ST(i)

FCOMP cmp ST(0) et ST(1)

Remonte la pile après la comparaison.

Le signe du zéro est ignoré.

Condition       C3   C2   C0

ST(0) > SRC 0 0 0

ST(0) < SRC 0 0 1

ST(0) = SRC 1 0 0

Unordered 1 1 1


FCOMPP

Compare ST(0) et ST(1)

Remonte la pile 2 FOIS après la comparaison.

Le signe du zéro est ignoré.

Condition       C3   C2   C0

ST(0) > SRC 0 0 0

ST(0) < SRC 0 0 1

ST(0) = SRC 1 0 0

Unordered 1 1 1


FINIT

Initialise FPU après attente de fin d'opération en cours.

FPUControlWord = 037FH

FPUStatusWord = 0

FPUTagWord = FFFFh

FPUDataPointer = 0

FPUInstructionPointer = 0

FPULastInstructionOpcode = 0

C0 = C1 = C2 = C3 = 0


FBLD

FBLD mem

Charge le BCD à l'adresse spécifiée, le convertit en réel et le pousse sur stack.

ST(0) indéfini si BCD invalide.


FBSTP

FBSTP mem

Convertit ST(0) en BCD à l'adresse spécifiée et POP stack.


FILD

FILD src

FILD m16int

FILD m32int

FILD m64int

Convertit src en réel et pousse sur stack


FIST

FIST dest

FIST m16int m16int = (int16) ST(0)

FIST m32int m32int = (int32) ST(0)


FISTP

FISTP dest

FISTP m16int m16int = (int16) ST(0)

FISTP m32int m32int = (int32) ST(0)

FISTP m64int m64int = (int64) ST(0)

pop stack ensuite


FLD

FLD (Charge un nombre réel)
Syntaxe: fld Src
Exception flags: Stack Fault, Invalid operation, Denormalized value

"Pousse" src sur la pile

FLD m32real
FLD m64real
FLD m80real
FLD ST(i)

Le texte en anglais du tuto en cours de modif et traduction:
This instruction decrements the TOP register pointer in the Status Word and loads the floating point value from the specified source (Src) in the new TOP data register. The source can be one of the FPU's data registers or the memory address of a REAL4, REAL8, or REAL10 value (see Chap.2 for addressing modes of real numbers).

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

If the source is a denormalized number, a Denormal exception will be detected, setting the related flag in the Status Word. The value will still be loaded and normalized if possible.

If the source is one of the FPU's data registers, its value is retrieved before the TOP register pointer is decremented. If that source register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer will still be decremented and the new value in ST(0) will be the INDEFINITE NAN.

The content of the Tag Word will be modified according to the result of the operation.

Exemples d'utilisation de cette instruction:

fld  st(3) ;La valeur de ST(3) est copié dans le nouveau ST(0)
           ;La valeur est maintenant la même dans ST(0) et ST(4)
       ;Ne perdez pas de vue qu'il s'agit d'une pile!
fld  st    ;duplicates the TOP value which will now be in both ST(0) and ST(1)

Note that fld st(7) will always be an Invalid operation and the outcome will always be the INDEFINITE value in the new ST(0). The ST(7) register must be empty for a valid FLD instruction but would also be an invalid source if it is empty!
fld  real4_var   ;La valeur d'un real4_var variable
                 ;est convertie au REAL10 format et copié dans ST(0)

fld  qword ptr[ebx] ;ST(0) prend la valeur du QWORD situé à l'adresse
                 ;indiqué par EBX et que le FPU considère comme une
         ;valeur de type REAL8.

fld  tbyte ptr[edi+50] ;the FPU interprets the value at the address EDI+50
                 ;as a REAL10 and copies it to ST(0).
The content of memory is simply a series of bits. Floating point data cannot be differentiated from any other type of data. The FPU will thus interpret a series of bits as instructed. (See Rule #3).

If the source is a REAL10 value in memory, it has the same format as the FPU's data registers and is simply copied as is.

However, if the source is a REAL4 or a REAL8 value, it is converted to the 80-bit REAL10 format before loading it to ST(0). When such an operation is observed with a debugger, the displayed value in the FPU data register may often seem slightly different from the intended one. That would be due to the rounding which occurred when the data had to be shortened to fit into the REAL4 or REAL8 format, and some precision was lost.

Just as 3.33333... is never the exact value of 10÷3 regardless of the length of the fractional part, binary fractions behave in a similar manner. For example, a decimal value of 1.2 in binary would be:

1.00110011001100110011001100110011....b    0011 repeating indefinitely.
If a variable is declared as a REAL4 and initialized with that value, the assembler would most probably have used the FPU to convert it to binary with 64 bits of precision using the "0011" sequence as often as possible. To fit that value into the REAL4 format, it could only keep the first 23 fraction bits. The 24th bit being a "1", the number was rounded UP to:
1.0011 0011 0011 0011 0011 010b
Because of this rounding UP, this value is now slightly larger than the original decimal value and a debugger would show it as follows if loaded into an FPU data register:
1.2000000476837158
Similarly, some binary fractions would be rounded DOWN and the resulting value would be slightly smaller than the original one. For example, a REAL4 variable initialized with a decimal value of 1.3 would be loaded to the FPU's data register as:
1.0100 1100 1100 1100 1100 110b
which would be shown in decimal format by a debugger as:
1.2999999523162842
This rounding can also affect REAL4 variables initialized with large numbers having more than 7 integer digits. For example, if a REAL4 variable is initialized with 987654321.0 and loaded to the FPU, a debugger would show it as 987654336.0!
The FPU and floating point real numbers do not provide unlimited precision. The REAL4 format's relative precision is only equivalent to approx. 7 significant decimal digits, the REAL8 format being equivalent to approx. 15 significant decimal digits, and the REAL10 to 19 significant decimal digits.


FST

FST dest

FST m32real m32real = ST(0)

FST m64real m64real = ST(0)

FST ST(i) ST(i) = ST(0)


FSTP

FSTP m32real m32real = ST(0)

FSTP m64real m64real = ST(0)

FSTP m80real m80real = ST(0)

FSTP ST(i) ST(i) = ST(0)

pop stack ensuite


FXCH

FXCH ST(i) échange ST(0) et ST(i)

FXCH échange ST(0) et ST(1)


FADD, FADDP: Floating-Point Addition

fadd  mem32
fadd  mem64
fadd  fpureg
fadd  fpureg,st0
fadd  st0,fpureg
faddp fpureg
faddo st0,fpureg

FADD, given one operand, adds the operand to ST0 and stores the result back in ST0. If the operand has the TO modifier, the result is stored in the register given rather than in ST0.

FADDP performs the same function as FADD TO, but pops the register stack after storing the result.

The given two-operand forms are synonyms for the one-operand forms.


A.69. FUCOMxx: Floating-Point Unordered Compare

FUCOM fpureg ; DD E0+r [386,FPU]

FUCOM ST0,fpureg ; DD E0+r [386,FPU]

FUCOMP fpureg ; DD E8+r [386,FPU]

FUCOMP ST0,fpureg ; DD E8+r [386,FPU]

FUCOMPP ; DA E9 [386,FPU]

FUCOMI fpureg ; DB E8+r [P6,FPU]

FUCOMI ST0,fpureg ; DB E8+r [P6,FPU]

FUCOMIP fpureg ; DF E8+r [P6,FPU]

FUCOMIP ST0,fpureg ; DF E8+r [P6,FPU]

FUCOM compares ST0 with the given operand, and sets the FPU flags accordingly. ST0 is treated as the left-hand side of the comparison, so that the carry flag is set (for a `less-than' result) if ST0 is less than the given operand.

FUCOMP does the same as FUCOM, but pops the register stack afterwards. FUCOMPP compares ST0 with ST1 and then pops the register stack twice.

FUCOMI and FUCOMIP work like the corresponding forms of FUCOM and FUCOMP, but write their results directly to the CPU flags register rather than the FPU status word, so they can be immediately followed by conditional jump or conditional move instructions.

The FUCOM instructions differ from the FCOM instructions (section A.35) only in the way they handle quiet NaNs: FUCOM will handle them silently and set the condition code flags to an `unordered' result, whereas FCOM will generate an exception.


PADDSIW: MMX Packed Addition to Implicit Destination

paddsiw r/m64,mmxreg

PADDSIW, specific to the Cyrix extensions to the MMX instruction set, performs the same function as PADDSW, except that the result is not placed in the register specified by the first operand, but instead in the register whose number differs from the first operand only in the last bit.

So PADDSIW MM2,MM0 would put the result in MM1, but PADDSIW MM2,MM1 would put the result in MM0.


 Library FPU masm32

The original FPULIB (library of floating point math functions) was primarily designed for the beginners in assembly programming who had some need for such math
functions but did not yet have the opportunity lo learn how to use the FPU and its instructions.

Some feedback proved that a little knowledge can be dangerous. Some users were loading too many FPU registers themselves;
called functions then resulted in failure because an insufficient number of free registers were remaining to accomplish the task.

A modification was issued later to blindly free any register(s) required by the function, regardless of whether it contained valid data or not.
This would have no effect on the beginner using strictly the lib functions.
However, it would prevent "FPU stack overflow" errors due to inadvertant use of the FPU registers by "intermediate" programmers.
The drawback for that latter group was that valid data on the FPU could be trashed.

Later, it also became evident that some assembler HLL instructions would blindly trash FPU registers to perform their task.
Although such action seems to be generally accepted as "fair game", an alternate version of the FPULIB has thus been prepared to avoid such blind trashing.
Again, this modified version would have no effect on the beginner using strictly the lib functions.
The only drawback is the added overhead to save the FPU registers and restore them.

This version may be an advantage for intermediate users who may want to use some of the library functions without any fear of loosing valuable data in FPU registers,
and without any need of preserving such data themselves.

The only restriction of that modified version was that the ST7 register must be free whenever the FPU was not the source for any input parameter AND the result was
specified to be returned on the FPU. If that ST7 register was NOT free under those conditions, it would NOT have been trashed but the function would have failed.

Because the primary intention of the library is to help beginners, AND beginners are not expected to know about FPU register management,
AND the library functions don't free the registers, it became evident that preserving ALL the FPU registers was not entirely practical.
This latest version (and hopefully the last major one) would now trash the FPU's ST7 register if,
and only if, no input parameter is specified as being present in the FPU top register but the result is specified to remain on the FPU.

The latest modification was a redesign of the FpuAtoFL function after it was observed that the conversion of integer strings (no decimal digits)
may have resulted in the incorporation of a minuscule decimal difference (19th significant digit). The modified algorithm corrects that.
However, any string with only decimal digits may still suffer that minuscule inaccuracy.

This FPULIB21.zip file contains all necessary files, including all source code.
The bulk of the .zip file is the HELP file which has also been updated to reflect the modifications.

All source code ---------------------------------------------------------------------------------------------------------

FpuAbs.asm          FpuAdd.asm          FpuArccos.asm         FpuArccosh.asm         FpuArcsin.asm         FpuArcsinh.asm
FpuArctan.asm       FpuArctanh.asm      FpuAtoFL.asm          FpuChs.asm             FpuComp.asm           FpuCos.asm
FpuCosh.asm         FpuDiv.asm          FpuEexpX.asm          FpuExam.asm            FpuFLtoA.asm          FpuLnx.asm
FpuLogx.asm         FpuMul.asm          FpuRound.asm          FpuSin.asm             FpuSinh.asm           FpuSize.asm
FpuSqrt.asm         FpuState.asm        FpuSub.asm            FpuTan.asm             FpuTanh.asm           FpuTexpX.asm
FpuTrunc.asm        FpuXexpY.asm

-------------------------------------------------------------------------------------------------------------------------