byte + byte = int ... perché?


365

Guardando questo codice C #:

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

Il risultato di qualsiasi matematica eseguita su byte(o short) tipi viene implicitamente ricondotto a un numero intero. La soluzione è quella di riportare esplicitamente il risultato su un byte:

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

Quello che mi chiedo è perché? È architettonico? Filosofico?

Abbiamo:

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

Quindi perche no:

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

Un po 'di background: sto eseguendo un lungo elenco di calcoli su "piccoli numeri" (cioè <8) e memorizzando i risultati intermedi in un array di grandi dimensioni. L'uso di un array di byte (anziché di un array int) è più rapido (a causa degli accessi alla cache). Ma gli estesi byte cast diffusi attraverso il codice lo rendono molto più illeggibile.



10
Non è la conoscenza di Eric dello standard che sarebbe utile qui - è la sua conoscenza del design del linguaggio; cosa no perché. Ma sì, la risposta di Eric sarebbe piuttosto definitiva :)
Jon Skeet,

143
Le varie riflessioni che seguono rappresentano una ragionevole approssimazione delle considerazioni progettuali. Più in generale: non penso ai byte come a "numeri"; Li penso come schemi di bit che potrebbero essere interpretati come numeri, caratteri, colori o altro. Se stai per fare matematica su di loro e trattarli come numeri, ha senso spostare il risultato in un tipo di dati che viene più comunemente interpretato come un numero.
Eric Lippert,

28
@Eric: Questo ha molto senso per byte, ma probabilmente non altrettanto per short / ushort.
Jon Skeet,

23
@Eric: byte1 | byte2non li tratta affatto come numeri. Questo li tratta esattamente come schemi di bit. Capisco il tuo punto di vista, ma capita proprio che ogni volta che ho fatto qualsiasi aritmetica sui byte in C #, li stavo trattando come bit, non numeri, e questo comportamento è sempre tra i piedi.
Roman Starkov,

Risposte:


228

La terza riga dello snippet di codice:

byte z = x + y;

in realtà significa

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

Pertanto, non vi è alcuna operazione + sui byte, i byte vengono prima espressi in numeri interi e il risultato dell'aggiunta di due numeri interi è un numero intero (a 32 bit).


Ho provato il codice qui sotto ma non funziona ancora. byte z = (byte) x + (byte) y;
Anonimo

10
questo perché non esiste alcuna operazione + per i byte (vedi sopra). Prova byte z = (byte) ((int) x + (int) y)
azheglov

35
Questa deve essere la risposta più corretta e concisa. Non c'è un operando da aggiungere tra i byte, quindi invece di spiegare perché "aggiungere due byte" funziona o meno (non è mai successo ), questo mostra chiaramente perché il risultato è un int, perché l'unica cosa che è successa è un'aggiunta di 2 ints .
RichardTheKiwi,

2
Ho avuto le vertigini a leggere tutte le altre risposte (senza offesa per il signor Jon Skeet). Questa ho trovato la risposta più semplice che descrive correttamente cosa sta succedendo sotto il cofano. Grazie!
Rayryeng,

Ecco una risposta che ho scritto altrove che contiene un programma per identificare quando si intsta verificando questa promozione automatica guidata dal compilatore : stackoverflow.com/a/43578929/4561887
Gabriel Staples

172

In termini di "perché succede affatto" è perché non ci sono operatori definiti da C # per l'aritmetica con byte, sbyte, short o ushort, proprio come altri hanno già detto. Questa risposta spiega perché questi operatori non sono definiti.

Credo che sia fondamentalmente per motivi di performance. I processori hanno operazioni native per eseguire l'aritmetica con 32 bit molto rapidamente. Fare la parte posteriore di conversione dal risultato a un byte automaticamente poteva essere fatto, ma si tradurrebbe in sanzioni prestazioni nel caso in cui non si vuole realmente che il comportamento.

Penso che questo sia menzionato in uno degli standard C # annotati. Guardare...

EDIT: Stranamente, ora ho guardato attraverso le specifiche annotate ECMA C # 2, le specifiche annotate MS C # 3 e le specifiche CLI delle annotazioni, e nessuno di loro ne parla per quanto posso vedere. Sono sicuro di aver visto il motivo indicato sopra, ma sono colpito se so dove. Scuse, fan di riferimento :(


14
Mi dispiace dirlo, ma trovo che questa non sia la risposta migliore.
VVS,

42
Hai sottovalutato ogni risposta che ritieni non sia la migliore? ;)
Jon Skeet,

55
(Solo per chiarire, non ci sto provando davvero. Sembra che ognuno abbia i propri criteri per il downvoting, e va bene. Declasserò una risposta solo se credo che sia attivamente dannoso piuttosto che non ideale. )
Jon Skeet,

21
Uso il voto come strumento per ottenere la risposta "migliore" all'inizio. In realtà ho scoperto che non hai detto molto nella tua risposta, che è stata la ragione principale del mio voto negativo. Un'altra ragione forse è la mia sensazione soggettiva che il tuo rappresentante ti dia un grande bonus quando si tratta di votare e stai arrivando in cima a risposte "migliori".
VVS,

23
IMO il modo migliore per ottenere la risposta "migliore" all'inizio è quello di votarlo. Ad essere sincero, penso che la risposta più istruttiva qui sia il commento di Eric nella domanda ... ma a parte questo, per la prospettiva del design (al contrario della prospettiva "cosa sta facendo il compilatore") non penso che ci sia molto risposta oltre "performance". In particolare, non compro davvero l'argomento "impedisce l'overflow" (17 voti) in quanto ciò suggerirebbe int + int = long.
Jon Skeet,

68

Ho pensato che avevo visto da qualche parte prima. Da questo articolo, The Old New Thing :

Supponiamo di vivere in un mondo fantastico in cui le operazioni su "byte" hanno portato a "byte".

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

In questo mondo fantastico, il valore di I sarebbe 16! Perché? Poiché i due operandi per l'operatore + sono entrambi byte, la somma "b + c" viene calcolata come un byte, che risulta in 16 a causa dell'overflow dei numeri interi. (E, come ho notato prima, l'overflow dei numeri interi è il nuovo vettore di attacco di sicurezza.)

EDIT : Raymond sta difendendo, in sostanza, l'approccio C e C ++ inizialmente adottato. Nei commenti, difende il fatto che C # adotti lo stesso approccio, sulla base della compatibilità con le versioni precedenti del linguaggio.


42
Con numeri interi se li aggiungiamo e trabocca non lo lancia automaticamente come un tipo di dati diverso, quindi perché farlo con byte?
Ryan,

2
Con ints trabocca. Prova ad aggiungere int.MaxValue + 1 ottieni -2147483648 invece di 2147483648.
David Basarab,

8
@ Longhorn213: Sì, è quello che dice Ryan: la matematica int può traboccare, ma la matematica int non restituisce molto.
Michael Petrotta,

28
Esattamente. Se si intende che questa è una misura di sicurezza, è molto mal implementata;)
Jon Skeet,

5
@Ryan: "pigro" è una carica piuttosto pesante da fare contro i progettisti del linguaggio C #, per qualcosa di semplice come la matematica primitiva. Se vuoi accusarli di qualcosa, rendilo "eccessiva compatibilità all'indietro con C / C ++".
Michael Petrotta,

58

C #

ECMA-334 afferma che l'addizione è definita come legale solo su int + int, uint + uint, long + long e ulong + ulong (ECMA-334 14.7.4). Pertanto, queste sono le operazioni candidate da prendere in considerazione rispetto al 14.4.2. Poiché vi sono cast impliciti da byte a int, uint, long e ulong, tutti i membri della funzione di addizione sono membri della funzione applicabili in 14.4.2.1. Dobbiamo trovare il miglior cast implicito dalle regole in 14.4.2.3:

Lanciare (C1) su int (T1) è meglio che lanciare (C2) su uint (T2) o ulong (T2) perché:

  • Se T1 è int e T2 è uint o ulong, C1 è la conversione migliore.

Casting (C1) in int (T1) è meglio che casting (C2) in long (T2) perché esiste un cast implicito da int a long:

  • Se esiste una conversione implicita da T1 a T2 e non esiste alcuna conversione implicita da T2 a T1, C1 è la conversione migliore.

Quindi viene utilizzata la funzione int + int, che restituisce un int.

Il che è un modo molto lungo per dire che è sepolto molto in profondità nelle specifiche C #.

CLI

L'interfaccia della riga di comando funziona solo su 6 tipi (int32, int nativo, int64, F, O e &). (ECMA-335 partizione 3 sezione 1.5)

Il byte (int8) non è uno di quei tipi e viene automaticamente forzato a un int32 prima dell'aggiunta. (ECMA-335 partizione 3 sezione 1.6)


Il fatto che l'ECMA specifichi solo quelle particolari operazioni non impedirebbe ad una lingua di attuare altre regole. VB.NET lo consentirà utile byte3 = byte1 And byte2senza un cast, ma inutilmente genererà un'eccezione di runtime se int1 = byte1 + byte2restituisce un valore superiore a 255. Non so se alcune lingue consentirebbero byte3 = byte1+byte2e genererebbero un'eccezione quando supera 255, ma non generare un'eccezione se int1 = byte1+byte2restituisce un valore nell'intervallo 256-510.
supercat,

26

Le risposte che indicano una certa inefficienza nell'aggiungere byte e nel troncare il risultato in un byte sono errate. I processori x86 hanno istruzioni appositamente progettate per il funzionamento di numeri interi su quantità di 8 bit.

In effetti, per i processori x86 / 64, l'esecuzione di operazioni a 32 o 16 bit è meno efficiente delle operazioni a 64 o 8 bit a causa del byte del prefisso dell'operando che deve essere decodificato. Su macchine a 32 bit, l'esecuzione di operazioni a 16 bit comporta la stessa penalità, ma ci sono ancora codici operativi dedicati per operazioni a 8 bit.

Molte architetture RISC hanno simili istruzioni efficienti word / byte native. Quelli che generalmente non hanno un valore store-and-convert-to-firmato-value-of-some-bit-length.

In altre parole, questa decisione deve essere stata basata sulla percezione di ciò a cui il tipo di byte serve, non a causa delle inefficienze sottostanti dell'hardware.


+1; se solo questa percezione non fosse sbagliata ogni volta che mi sono mai spostato e ho fatto due byte in C # ...
Roman Starkov,

Non ci dovrebbero essere costi di prestazione per troncare il risultato. Nell'assembly x86 è solo la differenza tra la copia di un byte dal registro o quattro byte dal registro.
Jonathan Allen

1
@JonathanAllen Esattamente. L'unica differenza è, ironicamente, quando si esegue una conversione allargata . Il progetto attuale comporta una penalità prestazionale per l'esecuzione dell'istruzione di ampliamento (estensione firmata o estensione non firmata)
reirab

" percezione di ciò che il tipo di byte è per " - Questo potrebbe spiegare questo comportamento per byte(e char), ma non per il shortquale semanticamente è chiaramente un numero.
smls

13

Ricordo che una volta ho letto qualcosa da Jon Skeet (non riesco a trovarlo ora, continuerò a cercare) su come byte in realtà non sovraccarica l'operatore +. In effetti, quando si aggiungono due byte come nell'esempio, ogni byte viene effettivamente convertito implicitamente in un int. Il risultato è ovviamente un int. Ora per quanto riguarda il PERCHÉ è stato progettato in questo modo, aspetterò che Jon Skeet stesso pubblichi :)

EDIT: Trovato! Grandi informazioni su questo argomento qui .


9

Ciò è dovuto al troppo pieno e al trasporto.

Se si aggiungono due numeri a 8 bit, potrebbero traboccare nel nono bit.

Esempio:

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

Non lo so per certo, ma suppongo che ints, longse doublessono date più spazio, perché sono abbastanza grandi così com'è. Inoltre, sono multipli di 4, che sono più efficienti da gestire per i computer, poiché la larghezza del bus dati interno è di 4 byte o 32 bit (64 bit sta diventando più diffuso ora). Byte e short sono un po 'più inefficienti, ma possono risparmiare spazio.


23
Ma i tipi di dati più grandi non seguono lo stesso comportamento.
Inisheer,

12
I problemi di overflow sono da parte. Se dovessi prendere la tua logica e applicarla alla lingua, tutti i tipi di dati restituirebbero un tipo di dati più grande dopo l'aritmetica di aggiunta, il che sicuramente NON è il caso. int + int = int, long + long = long. Penso che la domanda riguardi l'incoerenza.
Joseph,

È stato il mio primo pensiero, ma allora perché int + int = long? Quindi non sto comprando l'argomento "possibile overflow" ... ancora <grin>.
Robert Cartaino,

11
Oh, e per quanto riguarda l'argeument "possibile overflow", perché non byte + byte = short?
Robert Cartaino,

A) Perché funziona nel modo in cui funziona, date le regole di C #? Vedi la mia risposta qui sotto. B) Perché è stato progettato così com'è? Probabilmente solo considerazioni sull'usabilità, basate su giudizi soggettivi sul modo in cui la maggior parte delle persone tende a usare ints e byte.
mqp,

5

Dalle specifiche del linguaggio C # 1.6.7.5 7.2.6.2 Promozioni numeriche binarie converte entrambi gli operandi in int se non è possibile inserirli in diverse altre categorie. La mia ipotesi è che non hanno sovraccaricato l'operatore + per prendere il byte come parametro, ma vogliono che agisca in qualche modo normalmente, quindi usano solo il tipo di dati int.

Linguaggio C # Spec


4

Il mio sospetto è che C # stia effettivamente chiamando il operator+definito on int(che restituisce un a intmeno che tu non sia in un checkedblocco), e implicitamente lanciando entrambi i tuoi bytes/ shortsa ints. Ecco perché il comportamento appare incoerente.


3
Invia entrambi i byte nello stack, quindi chiama il comando "aggiungi". In IL, aggiungi "mangia" i due valori e li sostituisce con un int.
Jonathan Allen

3

Questa è stata probabilmente una decisione pratica da parte dei progettisti del linguaggio. Dopotutto, un int è un Int32, un intero con segno a 32 bit. Ogni volta che si esegue un'operazione intera su un tipo più piccolo di int, verrà comunque convertito in un int con segno a 32 bit dalla maggior parte delle CPU a 32 bit. Ciò, combinato con la probabilità di traboccare piccoli numeri interi, probabilmente ha sigillato l'affare. Ti salva dal lavoro di controllo continuo di over / under-flow e quando il risultato finale di un'espressione su byte sarebbe nel range, nonostante il fatto che in qualche stadio intermedio sarebbe fuori range, ottieni un corretto risultato.

Un altro pensiero: il over / under-flow su questi tipi dovrebbe essere simulato, poiché non si verificherebbe naturalmente sulle CPU target più probabili. Perché preoccuparsi?


2

Questa è per la maggior parte la mia risposta relativa a questo argomento, presentata prima a una domanda simile qui .

Tutte le operazioni con numeri interi inferiori a Int32 vengono arrotondate fino a 32 bit prima del calcolo per impostazione predefinita. Il motivo per cui il risultato è Int32 è semplicemente lasciarlo così com'è dopo il calcolo. Se si controllano i codici operativi aritmetici MSIL, gli unici tipi numerici integrali con cui operano sono Int32 e Int64. È "di progettazione".

Se desideri il risultato nel formato Int16, è irrilevante se esegui il cast nel codice, o il compilatore (ipoteticamente) emette la conversione "sotto il cofano".

Ad esempio, per eseguire l'aritmetica Int16:

short a = 2, b = 3;

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

I due numeri si espanderebbero a 32 bit, verrebbero aggiunti, quindi troncati di nuovo a 16 bit, come MS intendeva che fosse.

Il vantaggio dell'uso di short (o byte) è principalmente l'archiviazione nei casi in cui si disponga di enormi quantità di dati (dati grafici, streaming, ecc.)


1

L'aggiunta non è definita per i byte. Quindi vengono espressi in int per l'aggiunta. Questo vale per la maggior parte delle operazioni matematiche e dei byte. (nota che era così nelle vecchie lingue, presumo che valga oggi).


0

Penso che sia una decisione di progettazione su quale operazione fosse più comune ... Se byte + byte = byte forse molte più persone saranno infastidite dal fatto di dover lanciare int quando è richiesto un int come risultato.


2
Per una volta mi sono infastidito nell'altro modo :) Mi sembra sempre di aver bisogno del risultato in byte, quindi devo sempre lanciare.
Roman Starkov,

Tranne il fatto che non devi lanciare su int. Il cast è implicito. Solo l'altro modo è esplicito.
Niki,

1
@nikie Penso che tu non abbia capito la mia risposta. Se l'aggiunta di due byte produrrebbe un byte, al fine di evitare overflow qualcuno dovrebbe lanciare gli operandi (non il risultato) in int prima dell'aggiunta.
fortran,

0

Dal codice .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;
}

Semplifica con .NET 3.5 e versioni successive:

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

ora puoi fare:

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


0

Ho testato le prestazioni tra byte e int.
Con valori 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);
    }
}

Con valori byte:

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

Ecco il risultato:
byte: 3.57s 157mo, 3.71s 171mo, 3.74s 168mo con CPU ~ = 30%
int: 4.05s 298mo, 3.92s 278mo, 4.28 294mo con CPU ~ = 27%
Conclusione:
byte usa più CPU ma costa meno memoria ed è più veloce (forse perché ci sono meno byte da allocare)


-1

Oltre a tutti gli altri grandi commenti, ho pensato di aggiungere un piccolo bocconcino. Molti commenti si sono chiesti perché int, long e praticamente qualsiasi altro tipo numerico non segua questa regola ... restituisce un tipo "più grande" in risposta all'aritmico.

Molte risposte hanno avuto a che fare con le prestazioni (beh, 32 bit è più veloce di 8 bit). In realtà, un numero a 8 bit è ancora un numero a 32 bit in una CPU a 32 bit .... anche se si aggiungono due byte, il blocco di dati su cui la CPU opera sarà 32 bit a prescindere ... quindi l'aggiunta di ints non sta per essere "più veloce" dell'aggiunta di due byte ... è tutto uguale per la cpu. ORA, l'aggiunta di due pollici sarà più veloce dell'aggiunta di due long su un processore a 32 bit, perché l'aggiunta di due long richiede più microops poiché si lavora con numeri più ampi della parola processori.

Penso che il motivo fondamentale per cui l'aritmetica dei byte abbia come risultato ints sia piuttosto chiaro e diretto: 8 bit non va molto lontano! : D Con 8 bit, hai un intervallo senza segno di 0-255. Questo è non un sacco di spazio per lavorare con ... la probabilità che si sta per imbattersi in un byte limitazioni è molto elevato quando li utilizzano in aritmetica. Tuttavia, la possibilità di rimanere a corto di bit quando si lavora con ints, o long, o double, ecc. È significativamente più bassa ... abbastanza bassa che raramente incontriamo la necessità di più.

La conversione automatica da byte a int è logica perché la scala di un byte è così piccola. La conversione automatica da int a long, float in double, ecc. Non è logica perché quei numeri hanno una scala significativa.


Questo non spiega ancora perché byte - byteritorni into perché non facciano il casting per short...
KthProg,

Perché vorresti che l'aggiunta restituisse un tipo diverso dalla sottrazione? Se byte + byterestituisce int, poiché 255 + qualsiasi cosa è maggiore di un byte può contenere, non ha senso avere alcun byte meno qualsiasi altro byte restituire qualcosa di diverso da un int dal punto di vista della coerenza del tipo di ritorno.
jrista,

Non lo farei, dimostra solo che il motivo sopra riportato probabilmente non è giusto. Se avesse a che fare con "adattamento" al risultato, la bytesottrazione restituirà a bytee l'aggiunta di byte restituirà a short( byte+ bytesi adatterà sempre in a short). Se si trattasse di coerenza come dici tu, shortsarebbe comunque sufficiente per entrambe le operazioni anziché int. Chiaramente c'è una combinazione di ragioni, non tutte necessariamente necessariamente ponderate. In alternativa, il motivo della prestazione indicato di seguito potrebbe essere più preciso.
KthProg,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.