b0lt ya ha explicado cómo funciona el sesgo. En una suposición, quizás desee saber por qué usan una representación sesgada aquí, aunque virtualmente todas las computadoras modernas usan el complemento de dos esencialmente en cualquier otro lugar (e incluso las máquinas que no usan el complemento de dos, usan un complemento o señal -magnitud, no sesgo).
Uno de los objetivos de los estándares de punto flotante IEEE era que podía tratar los bits de un número de coma flotante como un entero (firmado) del mismo tamaño, y si los comparaba de esa manera, los valores se ordenarían en el mismo orden que los números de coma flotante que representaron.
Si utilizó una representación de complemento a dos para el exponente, un pequeño número positivo (es decir, con un exponente negativo) se vería como una gran número entero porque se establecería el segundo MSB. Al usar una representación de sesgo en su lugar, no se topa con eso: un exponente más pequeño en el número de punto flotante siempre se ve como un entero más pequeño.
FWIW, este es también el motivo por el cual los números de coma flotante se organizan primero con el signo, luego el exponente y finalmente el significado en los bits menos significativos. De esta forma, puede tomar números flotantes positivos, tratar esos bits como enteros, y ordenarlos. Cuando lo haga, el resultado tendrá los números de coma flotante en el orden correcto. Por ejemplo:
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
// some arbitrary floating point values
std::vector<double> vals = { 1e21, 1, 2.2, 2, 123, 1.1, 0.0001, 3, 17 };
std::vector<long long> ivals;
// Take those floating point values, and treat the bits as integers:
for (auto &&v : vals)
ivals.push_back(*reinterpret_cast<long long *>(&v));
// Sort them as integers:
std::sort(ivals.begin(), ivals.end());
// Print out both the integers and the floating point value those bits represent:
for (auto &&i : ivals)
std::cout << i << "\t(" << *reinterpret_cast<double *>(&i) << ")\n";
}
Cuando corremos esto, el resultado es idéntico:
4547007122018943789 (0.0001)
4607182418800017408 (1)
4607632778762754458 (1.1)
4611686018427387904 (2)
4612136378390124954 (2.2)
4613937818241073152 (3)
4625478292286210048 (17)
4638355772470722560 (123)
4921056587992461136 (1e+21)
Como se puede ver, a pesar de que los clasificó como enteros, los números de punto flotante que esos bits representan también salir en el orden correcto
Esto tiene limitaciones con respecto a los números de coma flotante. Si bien todas las computadoras (no antiguas) concuerdan en la representación de los números positivos, hay tres representaciones que se han utilizado (bastante recientemente) para los números con signo: magnitud firmada, complemento de uno y complemento de dos.
Simplemente tratando los bits como un entero y comparando funcionará bien en una computadora que utiliza la representación de magnitud firmada para enteros. Para las computadoras que usan complemento de uno o complemento de dos, los números negativos se ordenarán en orden invertido. Como esta sigue siendo una regla simple, es bastante fácil escribir código que funcione con ella.Si cambiamos la llamada sort
arriba para algo como esto:
std::sort(ivals.begin(), ivals.end(),
[](auto a, auto b) { if (a < 0.0 && b < 0.0) return b < a; return a < b; }
);
... A continuación, ordenar correctamente los números positivos y negativos. Por ejemplo, la entrada de:
std::vector<double> vals = { 1e21, 1, 2.2, 2, 123, 1.1, 0.0001, 3, 17, -0.001, -0.00101, -1e22 };
producirá un resultado de:
-4287162073302051438 (-1e+22)
-4661071411077222194 (-0.00101)
-4661117527937406468 (-0.001)
4547007122018943789 (0.0001)
4607182418800017408 (1)
4607632778762754458 (1.1)
4611686018427387904 (2)
4612136378390124954 (2.2)
4613937818241073152 (3)
4625478292286210048 (17)
4638355772470722560 (123)
4921056587992461136 (1e+21)
Otra lectura interesante relacionada con esta pregunta es el artículo de esta Wikipedia: https://en.wikipedia.org/wiki/IEEE_754-1985 – nbro