byte + byte = int… por quê?

365

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 realizada em byte(ou short) tipos é implicitamente convertido de volta para um número inteiro. A solução é converter explicitamente o resultado em um byte:

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

O que eu estou querendo saber é por quê? É 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 executando uma longa lista de cálculos sobre "pequenos números" (ou seja, <8) e armazenando os resultados intermediários em uma grande variedade. O uso de uma matriz de bytes (em vez de uma matriz int) é mais rápido (devido a ocorrências de cache). Mas a extensa conversão de bytes espalhada pelo código o torna muito mais ilegível.

Robert Cartaino
fonte
10
Não é o conhecimento de Eric sobre o padrão que seria útil aqui - é o conhecimento dele sobre o design da linguagem; por que não? Mas sim, a resposta de Eric seria muito definitiva :)
Jon Skeet
143
As várias reflexões abaixo são uma aproximação razoável das considerações de design. De maneira mais geral: não considero bytes como "números"; Penso neles como padrões de bits que podem ser interpretados como números, caracteres, cores ou qualquer outra coisa. Se você vai fazer contas com elas e tratá-las como números, faz sentido mover o resultado para um tipo de dados que é mais comumente interpretado como um número.
Eric Lippert
28
@ Eric: Isso faz muito sentido para byte, mas provavelmente não tanto quanto para short / ushort.
Jon Skeet
23
@ Eric: byte1 | byte2não os trata como números. Isso é tratá-los precisamente como padrões de bits. Entendo o seu ponto de vista, mas acontece que toda vez que eu fazia aritmética em bytes em C #, eu os tratava como bits, não como números, e esse comportamento está sempre no caminho.
Roman Starkov

Respostas:

228

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á + operação em bytes, os bytes são convertidos primeiro em números inteiros e o resultado da adição de dois números inteiros é um número inteiro (32 bits).

azheglov
fonte
Eu tentei o código abaixo, mas ele ainda não está funcionando. byte z = (byte) x + (byte) y;
Anônimo
10
isso ocorre porque não há + operação para bytes (veja acima). Tente byte z = (byte) ((int) x + (int) y)
azheglov
35
Essa deve ser a resposta mais correta e concisa. Não há operando para adicionar entre bytes; portanto, em vez de explicar por que "adicionar dois bytes" funciona ou não ( nunca aconteceu ), isso mostra claramente por que o resultado é um int, porque a única coisa que aconteceu foi uma adição de 2 ints .
precisa saber é o seguinte
2
Fiquei tonto ao ler todas as outras respostas (sem ofensa ao Sr. Jon Skeet). Esta foi a resposta mais simples que descreve corretamente o que está acontecendo sob o capô. Obrigado!
rayryeng
Aqui está uma resposta que eu escrevi em outra parte que contém um programa para identificar quando essa promoção automática-driven compilador intestá ocorrendo: stackoverflow.com/a/43578929/4561887
Gabriel Staples
172

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

Eu acredito que é basicamente por uma questão de desempenho. Os processadores têm operações nativas para fazer aritmética com 32 bits muito rapidamente. A conversão da conversão do resultado para um byte automaticamente poderia ser feita automaticamente , mas resultaria em penalidades de desempenho no caso em que você realmente não deseja esse comportamento.

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

EDIT: Irritantemente, agora eu examinei as especificações anotadas do ECMA C # 2, as especificadas do MS C # 3 e a especificação da CLI da anotação, e nenhuma delas menciona isso até onde posso ver. Tenho certeza de que vi o motivo exposto acima, mas estou impressionado se souber onde. Desculpas, fãs de referência :(

Jon Skeet
fonte
14
Lamento dizer isso, mas acho que essa não é a melhor resposta.
VVS
42
Você votou negativamente em todas as respostas que acha que não são as melhores? ;)
Jon Skeet
55
(Só para esclarecer, eu realmente não estou gostando de você. Parece que todo mundo tem seus próprios critérios de votação rebaixada, e tudo bem. Eu apenas rebaixo uma resposta se acredito que ela é ativamente prejudicial, e não apenas não ideal. )
Jon Skeet
21
Eu uso a votação como um instrumento para obter a "melhor" resposta para o topo. Na verdade, descobri que você não disse muita coisa na sua resposta, que foi a principal razão do meu voto negativo. Outro motivo talvez seja o meu sentimento subjetivo de que seu representante lhe dá um grande bônus quando se trata de votação e você está recebendo respostas "melhores".
VVS
23
A IMO, a melhor maneira de obter a "melhor" resposta para o topo, é fazer um voto positivo. Para ser honesto, eu acho que a resposta mais informativa aqui é o comentário de Eric na questão ... mas além disso, para a perspectiva de design (em oposição ao "o que o compilador está fazendo" perspectiva) Eu não acho que é muito responder além do "desempenho". Em particular, eu realmente não compro o argumento "impede o estouro" (17 votos), pois isso sugere int + int = long.
Jon Skeet
68

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

Suponha que vivamos em um mundo de fantasia em que 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 i seria 16! Por quê? Como os dois operandos do operador + são ambos bytes, a soma "b + c" é computada como um byte, o que resulta em 16 devido ao estouro de número inteiro. (E, como observei anteriormente, o excesso de número inteiro é o novo vetor de ataque de segurança.)

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

Michael Petrotta
fonte
42
Com números inteiros, se os adicionarmos e ele estourar, ele não o converterá automaticamente como um tipo de dados diferente; então, por que fazê-lo com byte?
Ryan
2
Com entradas, ele transborda. Tente adicionar int.MaxValue + 1 você começa -2147483648 vez de 2147483648.
David Basarab
8
@ Longhorn213: Sim, é o que Ryan está dizendo: int math pode transbordar, mas int math não retorna muito.
22430 Michael Petrotta
28
Exatamente. Se isso é feito para ser uma medida de segurança, é um muito mal implementado;)
Jon Skeet
5
@Ryan: "preguiçoso" é uma cobrança bastante pesada contra os designers de linguagem C #, por algo tão básico quanto a matemática primitiva. Se você quiser acusá-los de algo, faça com que seja "excessiva compatibilidade com o C / C ++".
Michael Petrotta
58

C #

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

A conversão (C1) para int (T1) é melhor que a conversão (C2) para uint (T2) ou ulong (T2) porque:

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

A conversão (C1) para int (T1) é melhor que a conversão (C2) para longa (T2) porque há uma conversão implícita de int para longa:

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

Portanto, a função int + int é usada, que retorna um int.

O que é uma maneira muito longa de dizer que está enterrado profundamente na especificação C #.

CLI

A CLI opera apenas em 6 tipos (int32, int nativo, int64, F, O e &). (Seção 1.5 da partição 3 do ECMA-335)

O byte (int8) não é um desses tipos e é automaticamente coagido a um int32 antes da adição. (Partição ECMA-335, seção 1.6)

Alun Harford
fonte
O fato de a ECMA especificar apenas essas operações específicas não impediria que um idioma implementasse outras regras. O VB.NET permitirá com utilidade byte3 = byte1 And byte2sem uma conversão, mas inútil lançará uma exceção de tempo de execução se int1 = byte1 + byte2gerar um valor acima de 255. Não sei se algum idioma permitiria byte3 = byte1+byte2e lançaria uma exceção quando isso exceder 255, mas não lançará uma exceção se int1 = byte1+byte2gerar um valor no intervalo 256-510.
Supercat
26

As respostas que indicam alguma ineficiência na adição de bytes e no truncamento do resultado em um byte estão incorretas. Os processadores x86 possuem instruções especificamente projetadas para operação com números inteiros em quantidades de 8 bits.

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

Muitas arquiteturas RISC têm instruções nativas similares semelhantes à palavra / byte. Aqueles que geralmente não têm um valor de armazenar e converter em valor assinado de algum tamanho de bit.

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

Christopher
fonte
+1; Se apenas essa percepção não estava errado cada vez que eu já mudou e com OR dois bytes em C # ...
Roman Starkov
Não deve haver nenhum custo de desempenho para truncar o resultado. Na montagem x86, é apenas a diferença entre copiar um byte do registro ou quatro bytes do registro.
Jonathan Allen
11
@JonathanAllen Exatamente. A única diferença é, ironicamente, ao realizar uma conversão ampliada . O atual projeto incorre em uma penalidade de desempenho para executar a instrução alargamento (seja assinado estender ou não assinado estender.)
reirab
" percepção do que é o tipo de byte " - Isso pode explicar esse comportamento para byte(e char), mas não para o shortqual semanticamente é claramente um número.
SMLS
13

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

EDIT: Encontrei! Ótimas informações sobre esse mesmo tópico aqui .

BFree
fonte
9

Isto é devido ao estouro e transporta.

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

Exemplo:

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

Eu não sei ao certo, mas eu suponho que ints, longse doublessão dadas mais espaço, porque eles são muito grande como ele é. Além disso, são múltiplos de 4, que são mais eficientes para os computadores manipularem, devido à largura do barramento de dados interno ter 4 bytes ou 32 bits (64 bits está se tornando mais prevalente agora). Byte e curto são um pouco mais ineficientes, mas podem economizar espaço.

samoz
fonte
23
Mas os tipos de dados maiores não seguem o mesmo comportamento.
Inisheer
12
Questões de excesso estão à parte. Se você adotasse sua lógica e a aplicasse ao idioma, todos os tipos de dados retornariam um tipo de dados maior após a aritmética de adição, o que definitivamente não é o caso. int + int = int, longo + longo = longo. Eu acho que a pergunta é em relação à inconsistência.
314 Joseph
Esse foi o meu primeiro pensamento, mas por que não int + int = long? Então, eu não estou comprando o argumento "possível estouro" ... ainda <grin>.
Robert Cartaino 02/06/2009
11
Ah, e sobre o argumento "possível estouro", por que não byte + byte = curto?
Robert Cartaino 02/06/2009
A) Por que funciona da maneira que funciona, dadas as regras do C #? Veja minha resposta abaixo. B) Por que foi projetado da maneira que é? Provavelmente apenas considerações de usabilidade, baseadas em julgamentos subjetivos sobre a maneira como a maioria das pessoas costuma usar ints e bytes.
Mqp 02/06/2009
5

Na especificação da linguagem C # 1.6.7.5 7.2.6.2 Promoções numéricas binárias, ele 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 usar o byte como parâmetro, mas querem que ele atue de maneira um pouco normal, para que apenas usem o tipo de dados int.

Linguagem C # Spec

Ryan
fonte
4

Minha suspeita é que o C # esteja realmente chamando o operator+definido em int(que retorna um a intmenos que você esteja em um checkedbloco) e implicitamente convertendo ambos os seus bytes/ shortspara ints. É por isso que o comportamento parece inconsistente.

mqp
fonte
3
Ele pressiona os dois bytes na pilha e chama o comando "add". Em IL, adicione "come" os dois valores e os substitui por um int.
Jonathan Allen
3

Provavelmente, essa foi uma decisão prática por parte dos designers de linguagem. Afinal, um int é um Int32, um número inteiro assinado de 32 bits. Sempre que você fizer uma operação inteira em um tipo menor que int, ela será convertida em um int assinado de 32 bits pela maioria das CPUs de 32 bits. Isso, combinado com a probabilidade de transbordar números inteiros pequenos, provavelmente selou o acordo. Isso evita a tarefa de verificar continuamente se há excesso / subfluxo, e quando o resultado final de uma expressão em bytes estaria dentro do alcance, apesar do fato de que, em algum estágio intermediário, estaria fora do alcance, você obtém uma resposta correta. resultado.

Outro pensamento: o excesso / subfluxo desses tipos teria que ser simulado, pois não ocorreria naturalmente nas CPUs-alvo mais prováveis. Porque se importar?

PeterAllenWebb
fonte
2

Esta é em grande parte a minha resposta que refere a este tema, apresentado pela primeira vez a uma pergunta 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 do MSIL, o único tipo numérico integral com o qual eles operam é Int32 e Int64. É "por design".

Se você deseja o resultado de volta no formato Int16, é irrelevante se você executar a conversão no código ou se o compilador (hipoteticamente) emitir a conversão "under the hood".

Por exemplo, para fazer a aritmética Int16:

short a = 2, b = 3;

short c = (short) (a + b);

Os dois números seriam expandidos para 32 bits, adicionados e truncados novamente para 16 bits, que é como a MS pretendia que fosse.

A vantagem de usar curto (ou byte) é principalmente armazenamento nos casos em que você possui grandes quantidades de dados (dados gráficos, streaming, etc.)

Kenan EK
fonte
1

A adição não está definida para bytes. Então eles são convertidos para int para a adição. Isso vale para a maioria das operações matemáticas e bytes. (observe que é assim que costumava ser em idiomas mais antigos, suponho que seja válido hoje).

Jim C
fonte
0

Eu acho que é uma decisão de projeto sobre qual operação era mais comum ... Se byte + byte = byte talvez muito mais pessoas se incomodem por ter que converter para int quando um int for necessário como resultado.

fortran
fonte
2
Pela primeira vez estou incomodado de outra maneira :) Parece que sempre preciso do resultado de bytes, por isso sempre tenho que transmitir.
Roman Starkov
Exceto que você não precisa converter para int. O elenco está implícito. Somente a outra maneira é explícita.
Niki
11
@nikie Acho que você não entendeu minha resposta. Se adicionar dois bytes produzisse um byte, a fim de evitar estouros, alguém teria que converter os operandos (não o resultado) para int antes da adição.
fortran
0

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 superior:

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);

serhio
fonte
0

Testei o desempenho entre byte e int.
Com valores int:

class Program
{
    private int a,b,c,d,e,f;

    public Program()
    {
        a = 1;
        b = 2;
        c = (a + b);
        d = (a - b);
        e = (b / a);
        f = (c * b);
    }

    static void Main(string[] args)
    {
        int max = 10000000;
        DateTime start = DateTime.Now;
        Program[] tab = new Program[max];

        for (int i = 0; i < max; i++)
        {
            tab[i] = new Program();
        }
        DateTime stop = DateTime.Now;

        Debug.WriteLine(stop.Subtract(start).TotalSeconds);
    }
}

Com valores de bytes:

class Program
{
    private byte a,b,c,d,e,f;

    public Program()
    {
        a = 1;
        b = 2;
        c = (byte)(a + b);
        d = (byte)(a - b);
        e = (byte)(b / a);
        f = (byte)(c * b);
    }

    static void Main(string[] args)
    {
        int max = 10000000;
        DateTime start = DateTime.Now;
        Program[] tab = new Program[max];

        for (int i = 0; i < max; i++)
        {
            tab[i] = new Program();
        }
        DateTime stop = DateTime.Now;

        Debug.WriteLine(stop.Subtract(start).TotalSeconds);
    }
}

Aqui está o resultado:
byte: 3.57s 157mo, 3.71s 171mo, 3.74s 168mo com CPU ~ = 30%
int: 4.05s 298mo, 3.92s 278mo, 4.28 294mo com CPU ~ = 27%
Conclusão: o
byte usa mais a CPU, mas ela custa memória e é mais rápido (talvez porque haja menos bytes a serem alocados)

puipuix
fonte
-1

Além de todos os outros ótimos comentários, pensei em acrescentar um pequeno detalhe. Muitos comentários se perguntam por que int, long e praticamente qualquer outro tipo numérico também não segue essa regra ... retorna um tipo "maior" em resposta à aritmética.

Muitas respostas tiveram a ver com desempenho (bem, 32 bits é mais rápido que 8 bits). 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, o pedaço de dados em que a CPU opera será 32 bits independentemente ... portanto, adicionar ints não será necessário. ser "mais rápido" do que adicionar dois bytes ... é tudo igual à CPU. AGORA, a adição de duas entradas será mais rápida do que a adição de dois comprimentos em um processador de 32 bits, porque a adição de dois comprimentos requer mais microops, pois você trabalha com números maiores que a palavra do processador.

Eu acho que a razão fundamental para fazer com que a aritmética de bytes resulte em ints é bastante clara e direta: 8 bits não chega muito longe! : D Com 8 bits, você tem um intervalo não assinado de 0 a 255. Isso não é um monte de espaço para trabalhar com ... a probabilidade de que você vai correr em uma bytes limitações é muito alto quando usá-los em aritmética. No entanto, a chance de você ficar sem bits ao trabalhar com ints, longs ou dobros, etc. é significativamente menor ... baixa o suficiente para que raramente encontramos 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 para double, etc. não é lógica porque esses números têm uma escala significativa.

jrista
fonte
Isso ainda não explica por que byte - byteos retornos int, ou porque eles não lançam para short...
KthProg
Por que você deseja que a adição retorne um tipo diferente da subtração? Se byte + byteretorna int, porque 255 + qualquer coisa é maior do que um byte pode conter, não faz sentido que nenhum byte menos qualquer outro byte retorne algo além de um int do ponto de vista da consistência do tipo de retorno.
jrista
Eu não, isso apenas mostra que a razão acima provavelmente não está certa. Se tivesse a ver com "ajuste" no resultado, a bytesubtração retornaria a bytee a adição de bytes retornaria a short( byte+ bytesempre caberá em a short). Se fosse sobre consistência, como você diz, shortainda seria suficiente para ambas as operações e não int. Claramente, há uma mistura de razões, nem todas necessariamente bem pensadas. Ou o motivo do desempenho fornecido abaixo pode ser mais preciso.
KthProg 25/10/19