Estou tentando entender como o NumPy implementa o arredondamento para o mais próximo, mesmo ao converter para um formato de precisão mais baixa, neste caso, Float32 para Float16, especificamente o caso em que o número é normal em Float32, mas é arredondado para um subnormal em Float16.
Link para o código: https://github.com/numpy/numpy/blob/13a5c4e569269aa4da6784e2ba83107b53f73bc9/numpy/core/src/npymath/halffloat.c#L244-L365
Meu entendimento é o seguinte,
Em float32, o número tem os bits
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
e | e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 | m0 | m1 | m2 | m3 | m4 | m5 | m6 | m7 | m8 | m9 | m10 | m11 | m12 | m13 | m14 | m15 | m16 | m17 | m18 | m19 | m20 | m21 | m22 |
/*
* If the last bit in the half significand is 0 (already even), and
* the remaining bit pattern is 1000...0, then we do not add one
* to the bit after the half significand. However, the (113 - f_exp)
* shift can lose up to 11 bits, so the || checks them in the original.
* In all other cases, we can just add one.
*/
if (((f_sig&0x00003fffu) != 0x00001000u) || (f&0x000007ffu)) {m
f_sig += 0x00001000u;
}
O código acima é usado ao quebrar empates para o par mais próximo. Não entendo por que na segunda parte do OR lógico, fazemos AND bit a bit contra 0x0000'07ffu
(bits m12-m22) e não 0x0000'ffffu
(m11-m22) .
Depois de alinharmos os bits da mantissa para que fiquem no formato subnormal para float16 (que é o que a mudança de bits antes deste pedaço de código faz), na representação numérica float32 acima teríamos m10
- m22
decidindo qual direção arredondar.
Meu entendimento é que a segunda parte do OR verifica se o número é maior do que o ponto médio, e se for, então adiciona um ao bit meio-significante. Mas com o número original, ele não está apenas verificando um subconjunto dos números que estão acima do ponto médio? No número float16, m9 seria a última precisão que vai permanecer. Então, arredondaremos para cima se,
m9 é 1, m10 é 1 e m11-m22 são todos 0 (A primeira parte do OR)
m10 é 1, pelo menos um de m11-m22 é 1 (para colocar o número acima do ponto médio)
pode ser simplificado adicionando 1 a m10, se qualquer um de m11-m22 for 1. se m10 já for 1, a adição sangrará para m9, caso contrário, permanecerá inalterado. Mas, no caso do código NumPy, os bits verificados são m12-m22.
Não tenho certeza do que estou perdendo aqui. Esse é um cenário de caso especial?
Eu esperava que os bits m11-m22 fossem os que decidiriam se adicionariam 1 e nem m12-m22.