Sempre presumi que, ao fazer (a % 256)
o otimizador, usaria naturalmente uma operação bit a bit eficiente, como se eu escrevesse (a & 0xFF)
.
Ao testar no compilador explorer gcc-6.2 (-O3):
// Type your code here, or load an example.
int mod(int num) {
return num % 256;
}
mod(int):
mov edx, edi
sar edx, 31
shr edx, 24
lea eax, [rdi+rdx]
movzx eax, al
sub eax, edx
ret
E ao tentar o outro código:
// Type your code here, or load an example.
int mod(int num) {
return num & 0xFF;
}
mod(int):
movzx eax, dil
ret
Parece que estou perdendo completamente alguma coisa. Alguma ideia?
c++
optimization
Elad Weiss
fonte
fonte
%
também não é&
.num
forunsigned
?Respostas:
Não é o mesmo. Tente
num = -79
e você obterá resultados diferentes das duas operações.(-79) % 256 = -79
, enquanto(-79) & 0xff
é algum número positivo.Usando
unsigned int
, as operações são as mesmas e o código provavelmente será o mesmo.PS- Alguém comentou
Não é assim que é definido em C, C ++, Objective-C (ou seja, todos os idiomas em que o código da pergunta seria compilado).
fonte
Resposta curta
-1 % 256
produz-1
e não o255
que é-1 & 0xFF
. Portanto, a otimização estaria incorreta.Resposta longa
C ++ tem a convenção de que
(a/b)*b + a%b == a
, o que parece bastante natural.a/b
sempre retorna o resultado aritmético sem a parte fracionária (truncando para 0). Como conseqüência,a%b
tem o mesmo sinal quea
ou é 0.A divisão
-1/256
cede0
e, portanto,-1%256
deve estar-1
em conformidade com a condição acima ((-1%256)*256 + -1%256 == -1
). Isto é obviamente diferente do-1&0xFF
que é0xFF
. Portanto, o compilador não pode otimizar da maneira que desejar.A seção relevante no padrão C ++ [expr.mul §4] a partir do N4606 declara:
Ativando a otimização
No entanto, usando
unsigned
tipos, a otimização estaria completamente correta , atendendo à convenção acima:Veja também isso .
Outras línguas
Isso é tratado de maneira muito diferente em diferentes linguagens de programação, como você pode consultar na Wikipedia .
fonte
Desde C ++ 11,
num % 256
deve ser não positivo senum
for negativo.Portanto, o padrão de bits dependeria da implementação de tipos assinados no seu sistema: para um primeiro argumento negativo, o resultado não é a extração dos 8 bits menos significativos.
Seria diferente se
num
no seu caso fosseunsigned
: hoje em dia eu quase esperaria que um compilador fizesse a otimização que você cita.fonte
num
for negativo,num % 256
será zero ou negativo (também conhecido como não positivo).(-250+256)%256==6
, mas(-250%256)+(256%256)
deve ser, de acordo com o padrão, "não positivo" e, portanto, não6
. Quebrar associatividade como essa tem efeitos colaterais na vida real: por exemplo, ao calcular a renderização "zoom out" em coordenadas inteiras, é preciso mudar a imagem para que todas as coordenadas não sejam negativas.(128+128)%256==0
mas(128%256)+(128%256)==256
. Talvez haja uma boa objeção ao comportamento especificado, mas não está claro para mim que foi o que você disse.256==0
. A chave é terN
valores exatamente possíveis no móduloN
aritmético, o que só é possível se todos os resultados estiverem no intervalo0,...,(N-1)
, não-(N-1),...,(N-1)
.Não tenho uma visão telepática do raciocínio do compilador, mas, no caso de,
%
há a necessidade de lidar com valores negativos (e as rodadas de divisão em direção a zero), enquanto&
o resultado é sempre os 8 bits mais baixos.A
sar
instrução soa para mim como "shift aritmetic right", preenchendo os bits desocupados com o valor do bit de sinal.fonte
Matematicamente falando, o módulo é definido como o seguinte:
a% b = a - b * piso (a / b)
Isso aqui deve esclarecer para você. Podemos eliminar o piso para números inteiros porque a divisão inteira é equivalente ao piso (a / b). No entanto, se o compilador usar um truque geral, como você sugere, ele precisará funcionar para todos os a e todos os b. Infelizmente, isso simplesmente não é o caso. Matematicamente falando, seu truque é 100% correto para números inteiros não assinados (vejo uma resposta declarar números inteiros quebrados, mas posso confirmar ou negar isso como -a% b deve ser positivo). No entanto, você pode fazer esse truque para todos os b? Provavelmente não. É por isso que o compilador não faz isso. Afinal, se o módulo fosse facilmente escrito como uma operação bit a bit, adicionaríamos um circuito de módulo como para adição e outras operações.
fonte
-2.3
é-3
, enquanto que se você truncar-2.3
para um número inteiro, obtém-2
. Veja en.wikipedia.org/wiki/Truncation . "para números negativos, o truncamento não arredonda na mesma direção que a função de piso". E o comportamento de%
para números negativos é precisamente o motivo pelo qual o OP está vendo o comportamento descrito.