web-dev-qa-db-fra.com

octet + octet = int ... pourquoi?

En regardant ce code C #:

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

Le résultat de tout calcul effectué sur les types byte (ou short) est implicitement converti en un entier. La solution consiste à convertir explicitement le résultat en octet:

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

Ce que je me demande c'est pourquoi? Est-ce architectural? Philosophique?

On a:

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

Alors pourquoi pas:

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

Un peu d’arrière-plan: j’effectue une longue liste de calculs sur des "petits nombres" (c'est-à-dire <8) et stocke les résultats intermédiaires dans un grand tableau. Utilisation d'un tableau byte (au lieu d'un tableau int) est plus rapide (à cause des occurrences dans le cache). Mais la multiplication d'octets distribués dans le code le rend encore plus illisible.

338
Robert Cartaino

La troisième ligne de votre extrait de code:

byte z = x + y;

signifie en réalité

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

Donc, il n'y a pas d'opération + sur les octets, les octets sont d'abord convertis en entiers et le résultat de l'addition de deux entiers est un entier (32 bits).

206
azheglov

En termes de "pourquoi cela se produit du tout", c'est qu'il n'y a pas d'opérateurs définis par C # pour l'arithmétique avec octet, sbyte, short ou ushort, comme d'autres l'ont déjà dit. Cette réponse concerne pourquoi ces opérateurs ne sont pas définis.

Je crois que c'est essentiellement pour des raisons de performance. Les processeurs ont des opérations natives pour faire de l'arithmétique avec 32 bits très rapidement. Reprendre automatiquement la conversion du résultat en octet pourrait, mais entraînerait des pénalités de performance dans le cas où vous ne souhaitiez pas réellement ce comportement.

Je pense ceci est mentionné dans l’un des standards annotés en C #. Vous cherchez ...

EDIT: Chose embarrassante, j’ai parcouru les spécifications annotées ECMA C # 2, MS C # 3 annotées et CLI d’annotation, et _ {aucune dont elles font mention à ce sujet. Je suis sûr j'ai vu la raison donnée ci-dessus, mais je suis bluffé si je sais où. Excuses, fans de référence :(

161
Jon Skeet

Je pensais Je l'avais déjà vu quelque part. De cet article, The Old New Thing :

Supposons que nous vivions dans un monde fantastique où les opérations sur 'octet' ont abouti à 'octet'.

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

Dans ce monde imaginaire, la valeur de i serait 16! Pourquoi? Parce que les deux les opérandes à l'opérateur + sont tous les deux octets, la somme "b + c" est donc calculée comme un octet, ce qui donne 16 en raison de débordement d'entier. (Et, comme je l’ai noté Plus tôt, le débordement d’entier est le nouveau vecteur d’attaque de sécurité )

EDIT: Raymond défend, essentiellement, l'approche adoptée par C et C++ à l'origine. Dans les commentaires, il défend le fait que C # adopte la même approche, pour des raisons de compatibilité ascendante des langages.

67
Michael Petrotta

C #

Selon la norme ECMA-334, l'addition n'est définie comme légale que sur int + int, uint + uint, long + long et ulong + ulong (ECMA-334 14.7.4). En tant que telles, ce sont les opérations candidates à prendre en compte en 14.4.2. Comme il y a des conversions implicites d'octet en int, uint, long et ulong, tous les membres de la fonction d'addition sont des membres de fonction applicables en 14.4.2.1. Nous devons trouver la meilleure distribution implicite par les règles de 14.4.2.3:

La coulée (C1) en int (T1) est meilleure que la coulée (C2) en uint (T2) ou en ulong (T2) parce que:

  • Si T1 est int et T2 est uint ou ulong, C1 est la meilleure conversion.

Le transtypage (C1) en int (T1) est préférable au transtypage (C2) en long (T2) car il existe un transtypage implicite de int en long:

  • S'il existe une conversion implicite de T1 en T2 et qu'aucune conversion implicite de T2 en T1 n'existe, C1 est la meilleure conversion. 

Par conséquent, la fonction int + int est utilisée, ce qui retourne un int.

Ce qui est tous un très long chemin pour dire qu'il est enterré très profondément dans la spécification C #.

CLI

La CLI ne fonctionne que sur 6 types (int32, natif int, int64, F, O et &). (ECMA-335 partition 3 section 1.5)

Octet (int8) n'est pas l'un de ces types et est automatiquement contraint à un int32 avant l'ajout. (ECMA-335 partition 3 section 1.6)

57
Alun Harford

Les réponses indiquant une certaine inefficacité ajoutant des octets et tronquant le résultat à un octet sont incorrectes. Les processeurs x86 ont des instructions spécialement conçues pour le fonctionnement en nombres entiers sur des quantités de 8 bits. 

En fait, pour les processeurs x86/64, les opérations 32 bits ou 16 bits sont moins efficaces que les opérations 64 bits ou 8 bits en raison de l'octet de préfixe d'opérande qui doit être décodé. Sur les machines 32 bits, l'exécution d'opérations 16 bits entraîne la même pénalité, mais il existe toujours des codes d'opération dédiés pour les opérations 8 bits.

De nombreuses architectures RISC ont des instructions Word/octets natives similaires. Ceux qui n'ont généralement pas de valeur de longueur de magasin et de conversion en valeur signée. 

En d'autres termes, cette décision doit être basée sur la perception du type d'octet, et non sur l'inefficacité sous-jacente du matériel.

25
Christopher

Je me souviens avoir lu quelque chose de Jon Skeet (je ne le trouve pas maintenant, je vais continuer à chercher) sur la façon dont l'octet ne surcharge pas l'opérateur +. En fait, lors de l'ajout de deux octets, comme dans votre exemple, chaque octet est converti implicitement en int. Le résultat de cela est évidemment un int. Maintenant, quant à POURQUOI ceci a été conçu de cette façon, je vais attendre que Jon Skeet lui-même le poste :)

EDIT: Je l'ai trouvé! Excellente information sur ce sujet même ici .

13
BFree

C'est à cause de débordement et porte.

Si vous ajoutez deux nombres à 8 bits, ils risquent de déborder dans le neuvième bit.

Exemple:

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

Je ne sais pas avec certitude, mais je suppose que ints, longs etdoubles disposent de plus d'espace, car ils sont assez volumineux. En outre, ils sont multiples de 4, ce qui est plus efficace pour les ordinateurs, du fait que le bus de données interne a une largeur de 4 octets ou 32 bits (64 bits sont de plus en plus répandus). Les octets et les raccourcis sont un peu plus inefficaces, mais ils peuvent économiser de l'espace.

6
samoz

A partir de la spécification du langage C # 1.6.7.5 7.2.6.2 Promotions numériques binaires, il convertit les deux opérandes en int s'il ne peut pas l'adapter à plusieurs autres catégories. J'imagine qu'ils n'ont pas surchargé l'opérateur + pour prendre octet en tant que paramètre, mais qu'ils souhaitent qu'il agisse un peu normalement afin d'utiliser simplement le type de données int.

Spéc. Langage c #

5
Ryan

Je soupçonne que C # appelle en réalité le operator+ défini sur int (qui retourne un int à moins que vous ne soyez dans un bloc checked) et transforme implicitement vos deux bytes/shorts en ints. C'est pourquoi le comportement semble incohérent.

4
mquander

C'était probablement une décision pratique de la part des concepteurs de langage. Après tout, un int est un Int32, un entier signé 32 bits. Chaque fois que vous effectuez une opération entière sur un type inférieur à int, elle sera convertie en un entier signé 32 bits par la plupart des processeurs 32 bits. Cela, combiné au risque de déborder de petits nombres entiers, a probablement scellé la transaction. Cela vous évite la corvée consistant à vérifier en permanence le dépassement/le sous-flux, et lorsque le résultat final d'une expression sur octets est à portée, même si, à un stade intermédiaire, il est hors de portée, vous obtenez un résultat correct. résultat.

Autre idée: il faudrait simuler le dépassement/le sous-débit de ces types, car cela ne se produirait pas naturellement sur les CPU les plus probables. Pourquoi s'embêter?

3
PeterAllenWebb

C’est pour la plupart ma réponse à ce sujet, soumise en premier à une question similaire ici .

Toutes les opérations avec des nombres entiers inférieurs à Int32 sont arrondies à 32 bits avant le calcul par défaut. La raison pour laquelle le résultat est Int32 est simplement de le laisser tel quel après le calcul. Si vous vérifiez les opcodes arithmétiques MSIL, les seuls types numériques intégraux avec lesquels ils fonctionnent sont Int32 et Int64. C'est "à dessein".

Si vous souhaitez que le résultat soit de nouveau au format Int16, il est indifférent que vous convertissiez le code en code ou que le compilateur émette (hypotétiquement) la conversion "sous le capot".

Par exemple, pour effectuer l’arithmétique Int16: 

short a = 2, b = 3;

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

Les deux nombres seraient étendus à 32 bits, seraient ajoutés, puis tronqués à 16 bits, comme le voulait la MS.

L'avantage d'utiliser short (ou octet) est principalement le stockage dans les cas où vous avez des quantités massives de données (données graphiques, streaming, etc.)

2
Kenan E. K.

Je pense que c'est une décision de conception sur l'opération qui était la plus courante ... Si octet + octet = octet, peut-être que plus de gens seront dérangés par le fait de devoir lancer le résultat sur int quand un int est requis.

1
fortran

L'addition n'est pas définie pour les octets. Donc, ils sont lancés à int pour l'addition. Ceci est vrai pour la plupart des opérations et octets mathématiques. (notez que c'était comme cela dans les langues plus anciennes, je suppose que cela est vrai aujourd'hui).

1
Jim C

J'ai testé les performances entre octet et int.
Avec les valeurs 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);
    }
}

Avec des valeurs d'octet:

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

Voici le résultat: 
octet: 3.57s 157mo, 3.71s 171mo, 3.74s 168mo avec CPU ~ = 30%
int: 4.05s 298mo, 3.92s 278mo, 4.28 294mo avec CPU ~ = 27%
Conclusion :
octets utilisent plus le processeur mais cela coûte de la mémoire et est plus rapide (peut-être parce qu’il ya moins d’octets à allouer)

1
puipuix

À partir du code .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;
}

Simplifiez avec .NET 3.5 et versions ultérieures:

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

maintenant tu peux faire:

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

En plus de tous les autres excellents commentaires, j'ai pensé ajouter un petit détail. Beaucoup de commentaires se sont demandé pourquoi les entiers, longs et pratiquement tous les autres types numériques ne suivent pas cette règle ... renvoie un type "plus gros" en réponse à arithmatique.

Beaucoup de réponses ont à voir avec la performance (bon, 32bits est plus rapide que 8bits). En réalité, un nombre de 8 bits est toujours un nombre de 32 bits à un processeur de 32 bits .... même si vous ajoutez deux octets, le bloc de données sur lequel le cpu opère va être 32 bits indépendamment de ... donc l'ajout d'ints ne va pas être plus "rapide" que l’ajout de deux octets ... c’est la même chose pour le processeur. MAINTENANT, ajouter deux entiers SERAIT plus rapide que d'ajouter deux longs sur un processeur 32 bits, car ajouter deux longs nécessite davantage de microops puisque vous travaillez avec des nombres plus larges que les processeurs Word. 

Je pense que la raison fondamentale pour laquelle l'arithmétique d'octets se traduit par des ints est assez claire et directe: 8bits ne va tout simplement pas très loin! : D Avec 8 bits, vous avez une plage non signée de 0 à 255. C'est pas beaucoup de marge de manoeuvre avec ... la probabilité que vous rencontriez des limitations en octets est TRÈS élevée lorsque vous les utilisez en calcul. Cependant, le risque de manquer de bits lorsque vous travaillez avec des ints, des long, ou des doubles, etc. est considérablement plus faible… suffisamment bas pour que nous en ayons très rarement besoin. 

La conversion automatique d'octet en entier estlogiquecar l'échelle d'un octet est si petite. La conversion automatique de int en long, float en double, etc. estn'est pas logiquecar ces nombres ont une échelle significative.

0
jrista