Densité des nombres flottants

Pour rappel, il est très fréquent d’encoder les nombres à virgule sous forme de nombre flottant sous la forme:

s m2n s\ m \cdot 2^n

Il faut cependant bien comprendre que la représentation des nombres flottants est loin d’être continue comme les nombres réels mathématiques.

Représentation d’un nombre normalisée

  • le signe, +1 ou -1, sous la forme d’un bit ;
  • l’exposant décalé, sous la forme de e bits représentant un nombre entier ;
  • la mantisse, sous la forme de m bits représentant un nombre positif strictement inférieur à 1.

La valeur du nombre représenté vaut : valeur = signe × (1 + mantisse) × 2^(exposant − décalage)^

C’est ce qu’on appelle la représentation d’un nombre normalisé.

Tous les nombres réels ne sont pas représentés et les nombres ne sont pas régulièrement espacés comme le montre la représentation graphique ci-dessous pour des nombres flottants représentés sur 7 bits avec 3 bits de mantisse et 3 bits d’exposant.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker
from scipy.stats import gaussian_kde
from lyceum.repr import bin2float
# On limite les points pour y voir quelquechose
NBITS = 7
EXPOSANT=3
MANTISSE=3
x_list= [i for i in range(2**NBITS)]
N = len(x_list)
x = np.array(x_list)
x_bin = np.array([format(i, f'0{NBITS}b') for i in x])

y = np.array([bin2float(i, e=EXPOSANT, f=MANTISSE) for i in x_bin])

# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)
# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]

fig, ax = plt.subplots()
im = ax.scatter(x, y, marker='+', c=z, s=100, alpha=0.5)

xs = [i for i in x_list if 10*i % 2**(NBITS-2) == 0] + [2**(NBITS)-1]
x_labels = [format(i, f'0{NBITS}b') for i in xs]
plt.xticks(xs, x_labels, rotation='vertical')
plt.xlabel("Valeur binaire")
plt.ylabel("Valeur décimale du nombre flottant")
plt.title('Densité des valeurs flottantes sur 7 bits(e=3, f=3)',pad=30)


# Add colorbar
cbar = fig.colorbar(im, label="Densité des nombres")
#ax.set_yscale('log')
plt.show()

cell 2 output 1

On voit que la densité est plus importante pour les nombres autour de 100=110^0 = 1 et plus faible pour les très petits et très grands nombres.

Représentation d’un nombre dénormalisée

La norme IEEE-754 prévoit d’attribuer des valeurs spéciales aux nombres ayant un exposant nul ou maximum.

Notamment pour les nombres avec un exposant nul et une mantisse non nulle, on utilise une représentation d’un nombre dénormalisé.

La valeur du nombre représenté vaut : valeur = signe × (mantisse) × 2^− décalage^

# On limite les points pour y voir quelquechose
NBITS = 7
EXPOSANT=3
MANTISSE=3
x_list= [i for i in range(2**NBITS)]
N = len(x_list)
x = np.array(x_list)
x_bin = np.array([format(i, f'0{NBITS}b') for i in x])

y = np.array([bin2float(i, e=EXPOSANT, f=MANTISSE, ieee=True) for i in x_bin])
np.nan_to_num(y, copy=False, nan=2**(NBITS-2.5), posinf=2**(NBITS-2), neginf=-2**(NBITS-2))
# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)
# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]

fig, ax = plt.subplots()
im = ax.scatter(x, y, marker='+', c=z, s=100, alpha=0.5)

xs = [i for i in x_list if 10*i % 2**(NBITS-2) == 0] + [2**(NBITS)-1]
x_labels = [format(i, f'0{NBITS}b') for i in xs]
plt.xticks(xs, x_labels, rotation='vertical')
plt.xlabel("Valeur binaire")
plt.ylabel("Valeur décimale du nombre flottant")
plt.title('Densité des valeurs flottantes sur 7 bits(e=3, f=3)',pad=15)
plt.suptitle('La norme IEEE754 est appliquée avec les valeurs normalisés les nan et les infinis')
# Add colorbar
cbar = fig.colorbar(im, label="Densité des nombres")
#ax.set_yscale('log')
plt.show()

cell 3 output 1

On peut voir que l’on a perdu en étendue, mais cela d’avoir des points trop éloignés pour les grandes valeurs.

D’autre part, grâce à la normalisation la densité semble plus uniforme.