Que isso funcione não é nada trivial! É uma propriedade da representação de ponto flutuante IEEE que int∘floor = ⌊⋅⌋ se a magnitude dos números em questão for pequena o suficiente, mas diferentes representações são possíveis onde int (floor (2.3)) pode ser 1.
Este post explica por que funciona nesse intervalo .
Em um dobro, você pode representar números inteiros de 32 bits sem problemas. Não pode haver problemas de arredondamento. Mais precisamente, as dobras podem representar todos os números inteiros entre 2 53 e -2 53 inclusive .
Breve explicação : Um duplo pode armazenar até 53 dígitos binários. Quando você precisar de mais, o número é preenchido com zeros à direita.
Daqui resulta que 53 ones é o maior número que pode ser armazenado sem preenchimento. Naturalmente, todos os números (inteiros) que exigem menos dígitos podem ser armazenados com precisão.
Adicionar um a 111 (omitido) 111 (53 unidades) produz 100 ... 000 (53 zeros). Como sabemos, podemos armazenar 53 dígitos, o que resulta no preenchimento zero mais à direita.
É daí que vem o 2 53 .
Mais detalhes: Precisamos considerar como o ponto flutuante IEEE-754 funciona.
1 bit 11 / 8 52 / 23 # bits double/single precision
[ sign | exponent | mantissa ]
O número é calculado da seguinte maneira (excluindo casos especiais que são irrelevantes aqui):
-1 sinal × 1.mantissa × 2 expoente - viés
onde viés = 2 expoente - 1 - 1 , ou seja, 1023 e 127 para precisão dupla / única, respectivamente.
Sabendo que multiplicar por 2 X simplesmente muda todos os bits X para a esquerda, é fácil ver que qualquer número inteiro deve ter todos os bits na mantissa que terminam à direita do ponto decimal em zero.
Qualquer número inteiro, exceto zero, tem o seguinte formato em binário:
1x ... x onde os x -es representam os bits à direita do MSB (bit mais significativo).
Como excluímos zero, sempre haverá um MSB, e é por isso que não é armazenado. Para armazenar o número inteiro, devemos trazê-lo para a forma acima mencionada: -1 sinal × 1.mantissa × 2 expoente - viés .
É o mesmo que mudar os bits sobre o ponto decimal até que haja apenas o MSB à esquerda do MSB. Todos os bits à direita do ponto decimal são armazenados na mantissa.
A partir disso, podemos ver que podemos armazenar no máximo 52 dígitos binários além do MSB.
Segue-se que o número mais alto em que todos os bits são armazenados explicitamente é
111(omitted)111. that's 53 ones (52 + implicit 1) in the case of doubles.
Para isso, precisamos definir o expoente, de modo que o ponto decimal seja deslocado em 52 casas. Se aumentarmos o expoente em um, não saberemos o dígito da direita para a esquerda após o ponto decimal.
111(omitted)111x.
Por convenção, é 0. Definindo a mantissa inteira como zero, recebemos o seguinte número:
100(omitted)00x. = 100(omitted)000.
Isso é 1 seguido de 53 zeros, 52 armazenados e 1 adicionado devido ao expoente.
Representa 2 53 , que marca o limite (negativo e positivo) entre o qual podemos representar com precisão todos os números inteiros. Se quiséssemos adicionar um a 2 53 , teríamos que definir o zero implícito (indicado por x
) para um, mas isso é impossível.
math.floor
retorna um ponto flutuante na v2.6 , mas retorna um número inteiro na v3 . Neste ponto (quase seis anos após o PO), esse problema pode aparecer raramente.