byte + byte = int… por quê?

Olhando para este código c #:

byte x = 1; byte y = 2; byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte' 

O resultado de qualquer matemática executada em tipos de byte (ou short ) é implicitamente retornado para um inteiro. A solução é converter explicitamente o resultado de volta para um byte:

 byte z = (byte)(x + y); // this works 

O que eu estou querendo saber é por que? É arquitetônico? Filosófico?

Nós temos:

  • int + int = int
  • long + long = long
  • float + float = float
  • double + double = double

Então por que não:

  • byte + byte = byte
  • short + short = short ?

Um pouco de fundo: estou realizando uma longa lista de cálculos em “números pequenos” (ou seja, <8) e armazenando os resultados intermediários em uma grande matriz. Usar uma matriz de bytes (em vez de uma matriz int) é mais rápido (devido a ocorrências de cache). Mas os extensos byte-casts espalhados pelo código fazem com que seja muito mais ilegível.

A terceira linha do seu trecho de código:

 byte z = x + y; 

na verdade significa

 byte z = (int) x + (int) y; 

Portanto, não há nenhuma operação + em bytes, os bytes são convertidos em números inteiros e o resultado da adição de dois inteiros é um número inteiro (32 bits).

Em termos de “por que isso acontece” é porque não há operadores definidos pelo C # para aritmética com byte, sbyte, short ou ushort, como outros já disseram. Esta resposta é sobre por que esses operadores não estão definidos.

Eu acredito que é basicamente por causa do desempenho. Processadores têm operações nativas para fazer aritmética com 32 bits muito rapidamente. Fazer a conversão de volta do resultado para um byte automaticamente poderia ser feito, mas resultaria em penalidades de desempenho no caso de você realmente não querer esse comportamento.

Eu acho que isso é mencionado em um dos padrões anotados do C #. Olhando…

EDIT: Irritantemente, eu olhei agora através da especificação anotada ECMA C # 2, a especificação anotada do MS C # 3 e a especificação CLI de anotação, e nenhum deles menciona isso tanto quanto eu posso ver. Tenho certeza de que já vi a razão dada acima, mas estou arrebentado se souber onde. Desculpas, fãs de referência 🙁

Eu pensei que tinha visto isso em algum lugar antes. A partir deste artigo, The Old New Thing :

Suponha que vivêssemos em um mundo de fantasia onde operações em ‘byte’ resultassem em ‘byte’.

 byte b = 32; byte c = 240; int i = b + c; // what is i? 

Neste mundo de fantasia, o valor de eu seria 16! Por quê? Como os dois operandos para o operador + são ambos bytes, a sum “b + c” é calculada como um byte, o que resulta em 16 devido ao estouro de inteiro. (E, como observei anteriormente, estouro de inteiro é o novo vetor de ataque de segurança.)

EDIT : Raymond está defendendo, essencialmente, a abordagem C e C ++ teve originalmente. Nos comentários, ele defende o fato de que o C # adota a mesma abordagem, com base na compatibilidade retroativa da linguagem.

C #

O ECMA-334 afirma que a adição só é definida como legal em int + int, uint + uint, long + long e ulong + ulong (ECMA-334 14.7.4). Como tal, estas são as operações candidatas a serem consideradas em relação a 14.4.2. Como existem castings implícitos de byte para int, uint, long e ulong, todos os membros da function de adição são membros de function aplicáveis ​​em 14.4.2.1. Temos que encontrar o melhor casting implícito pelas regras em 14.4.2.3:

Casting (C1) para int (T1) é melhor que casting (C2) para uint (T2) ou ulong (T2) porque:

  • Se T1 é int e T2 é uint, ou ulong, C1 é a melhor conversão.

Casting (C1) para int (T1) é melhor que casting (C2) para long (T2) porque há um casting implícito de int para long:

  • Se existir uma conversão implícita de T1 para T2 e não existir conversão implícita de T2 para T1, C1 é a melhor conversão.

Daí a function int + int é usada, que retorna um int.

Que é tudo um caminho muito longo para dizer que está enterrado muito fundo na especificação C #.

CLI

A CLI opera somente em 6 tipos (int32, int nativo, int64, F, O e &). (Partição ECMA-335 3 seção 1.5)

Byte (int8) não é um desses tipos e é automaticamente coagido para um int32 antes da adição. (ECMA-335 partição 3 seção 1.6)

As respostas indicando alguma ineficiência adicionando bytes e truncar o resultado de volta para um byte estão incorretas. Os processadores x86 têm instruções especificamente projetadas para operação de números inteiros em quantidades de 8 bits.

Na verdade, para processadores x86 / 64, a execução de operações de 32 ou 16 bits é menos eficiente que operações de 64 bits ou de 8 bits, devido ao byte de prefixo do operando que precisa ser decodificado. Em máquinas de 32 bits, executar operações de 16 bits implica a mesma penalidade, mas ainda existem opcodes dedicados para operações de 8 bits.

Muitas arquiteturas RISC têm instruções eficientes de palavras / bytes nativas semelhantes. Aqueles que geralmente não têm um valor de armazenamento e conversão de um valor assinado de alguns bits.

Em outras palavras, essa decisão deve ter sido baseada na percepção do tipo de byte, não devido a ineficiências subjacentes do hardware.

Lembro-me de uma vez ter lido algo de Jon Skeet (não consigo encontrá-lo agora, continuo procurando) sobre como o byte na verdade não sobrecarrega o operador +. Na verdade, ao adicionar dois bytes, como em sua amostra, cada byte está sendo convertido implicitamente em um int. O resultado disso é obviamente um int. Agora, por que isso foi projetado dessa maneira, esperarei o próprio Jon Skeet postar 🙂

EDIT: Encontrado! Ótima informação sobre este mesmo tópico aqui .

Isso é por causa de transbordamento e transporta.

Se você adicionar dois números de 8 bits, eles podem transbordar para o nono bit.

Exemplo:

  1111 1111 + 0000 0001 ----------- 1 0000 0000 

Eu não sei ao certo, mas eu assumo que ints , longs e doubles recebem mais espaço porque são muito grandes. Além disso, eles são múltiplos de 4, que são mais eficientes para os computadores manipularem, devido à largura do barramento de dados interno sendo 4 bytes ou 32 bits (64 bits estão se tornando mais predominantes agora). Byte e short são um pouco mais ineficientes, mas podem economizar espaço.

A partir da especificação do idioma C # 1.6.7.5 7.2.6.2 Promoções numéricas binárias converte os dois operandos em int se não puder ajustá-lo em várias outras categorias. Meu palpite é que eles não sobrecarregaram o operador + para tomar byte como um parâmetro, mas queriam que ele agisse um pouco normalmente, então eles apenas usavam o tipo de dados int.

Especificação da linguagem C #

Minha suspeita é que C # está realmente chamando o operator+ definido em int (que retorna um int menos que você esteja em um bloco checked ), e implicitamente convertendo ambos os seus bytes / shorts em ints . É por isso que o comportamento parece inconsistente.

Esta foi provavelmente uma decisão prática por parte dos designers de linguagem. Afinal, um int é um Int32, um inteiro assinado de 32 bits. Sempre que você fizer uma operação inteira em um tipo menor que int, ela será convertida para um int assinado de 32 bits pela maioria de qualquer CPU de 32 bits. Isso, combinado com a probabilidade de transbordar inteiros pequenos, provavelmente selou o acordo. Ele salva você da tarefa de verificar continuamente por sobre / substream e quando o resultado final de uma expressão em bytes estaria dentro do intervalo, apesar do fato de que em algum estágio intermediário ele estaria fora de alcance, você obtém um correto resultado.

Outro pensamento: o over / underflux nesses tipos teria que ser simulado, uma vez que não ocorreria naturalmente nas CPUs alvo mais prováveis. Porque se importar?

Esta é a maior parte da minha resposta que pertence a este tópico, submetido primeiro a uma questão semelhante aqui .

Todas as operações com números inteiros menores que Int32 são arredondadas para 32 bits antes do cálculo por padrão. A razão pela qual o resultado é Int32 é simplesmente deixá-lo como está após o cálculo. Se você verificar os opcodes aritméticos MSIL, o único tipo numérico integral com o qual eles operam são Int32 e Int64. É “por design”.

Se você deseja o resultado de volta no formato Int16, é irrelevante se você executar o casting no código, ou o compilador (hipoteticamente) emite a conversão “sob o capô”.

Por exemplo, para fazer aritmética Int16:

 short a = 2, b = 3; short c = (short) (a + b); 

Os dois números se expandiriam para 32 bits, seriam adicionados e depois truncados de volta para 16 bits, que é como MS pretendia que fosse.

A vantagem de usar short (ou byte) é principalmente o armazenamento nos casos em que você possui grandes quantidades de dados (dados charts, streaming, etc.)

Eu acho que é uma decisão de projeto sobre qual operação era mais comum … Se byte + byte = byte talvez muito mais pessoas ficarão incomodadas por ter que converter para int quando um int for requerido como resultado.

Adição não está definida para bytes. Então, eles são convertidos em int para a adição. Isso é verdade para a maioria das operações e bytes matemáticos. (note que é assim que costumava ser em línguas mais antigas, estou assumindo que é verdade hoje).

Do código do .NET Framework:

 // bytes private static object AddByte(byte Left, byte Right) { short num = (short) (Left + Right); if (num > 0xff) { return num; } return (byte) num; } // shorts (int16) private static object AddInt16(short Left, short Right) { int num = Left + Right; if ((num < = 0x7fff) && (num >= -32768)) { return (short) num; } return num; } 

Simplifique com o .NET 3.5 e acima:

 public static class Extensions { public static byte Add(this byte a, byte b) { return (byte)(a + b); } } 

agora você pode fazer:

 byte a = 1, b = 2, c; c = a.Add(b); 

Além de todos os outros ótimos comentários, pensei em acrescentar um pequeno detalhe. Muitos comentários se perguntaram por que int, long, e praticamente qualquer outro tipo numérico também não segue esta regra … retornar um tipo “maior” em resposta a arithmatic.

Muitas respostas tiveram a ver com o desempenho (bem, 32bits é mais rápido que 8bits). Na realidade, um número de 8 bits ainda é um número de 32 bits para uma CPU de 32 bits …. mesmo se você adicionar dois bytes, a quantidade de dados que a CPU opera será de 32 bits independentemente … então adicionar ints não vai ser qualquer “mais rápido” do que adicionar dois bytes … é tudo a mesma coisa para o cpu. Agora, adicionar dois ints será mais rápido do que adicionar dois longs em um processador de 32 bits, porque a adição de dois longs requer mais microops, já que você está trabalhando com números mais largos do que a palavra dos processadores.

Eu acho que a razão fundamental para fazer com que a aritmética de byte resulte em ints é bem clara e direta: o 8bits simplesmente não vai muito longe! : D Com 8 bits, você tem um intervalo sem sinal de 0-255. Isso não é muito espaço para trabalhar com … a probabilidade de que você vai executar em um limitações de bytes é muito alto quando usá-los em aritmética. No entanto, a chance de ficar sem bits ao trabalhar com ints, ou longs, doubles, etc. é significativamente menor … baixa o suficiente para que raramente encontremos a necessidade de mais.

A conversão automática de byte para int é lógica porque a escala de um byte é muito pequena. A conversão automática de int para long, float to double, etc. não é lógica porque esses números têm escala significativa.