byte + byte = int… neden?


365

Bu C # koduna bakarak:

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

byte(Veya short) türler üzerinde yapılan herhangi bir matematiğin sonucu dolaylı olarak bir tam sayıya geri döndürülür. Çözüm, sonucu açıkça bir bayta geri döndürmektir:

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

Merak ediyorum neden? Mimari mi? Felsefi?

Sahibiz:

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

Yani neden olmasın:

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

Biraz arka plan: "Küçük sayılar" (yani <8) üzerinde uzun bir hesaplama listesi gerçekleştiriyorum ve ara sonuçları büyük bir dizide saklıyorum. Bayt dizisi kullanmak (int dizisi yerine) daha hızlıdır (önbellek isabetlerinden dolayı). Ancak kod boyunca yayılan geniş bayt atımları onu daha okunamaz hale getirir.



10
Eric'in burada yararlı olacak standart bilgisi değil - dilin tasarımı hakkındaki bilgisi ; neden olmasın. Ama evet, Eric'in cevabı oldukça kesin olurdu :)
Jon Skeet

143
Aşağıdaki çeşitli ögeler, tasarımla ilgili düşüncelerin makul bir yaklaşımıdır. Daha genel olarak: Baytları "sayılar" olarak düşünmüyorum; Onları sayılar, karakterler, renkler ya da herhangi bir şey olarak yorumlanabilecek bit kalıpları olarak düşünüyorum . Onlara matematik yapacağınız ve sayı olarak ele alacaksanız, sonucu daha çok sayı olarak yorumlanan bir veri türüne taşımak mantıklıdır.
Eric Lippert

28
@Eric: Bayt için çok mantıklı, ancak kısa / ushort için muhtemelen çok mantıklı değil.
Jon Skeet

23
@Eric: byte1 | byte2onlara sayı gibi davranmaz. Bu onlara tam olarak bit kalıpları gibi davranıyor. Bakış açınızı anlıyorum, ama öyle oluyor ki, her seferinde C #'daki baytlarda herhangi bir aritmetik yaptım, aslında sayı olarak değil bit olarak davranıyordum ve bu davranış her zaman yolunda.
Roman Starkov

Yanıtlar:


228

Kod snippet'inizin üçüncü satırı:

byte z = x + y;

aslında şu anlama gelir

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

Bu nedenle, baytlarda + işlemi yoktur, baytlar önce tamsayılara verilir ve iki tamsayı eklenmesinin sonucu bir (32 bit) tamsayıdır.


Aşağıdaki kodu denedim ama hala çalışmıyor. bayt z = (bayt) x + (bayt) y;
İsimsiz

10
bunun nedeni baytlar için + işlemi olmamasıdır (yukarıya bakınız). Bayt z = (bayt) ((int) x + (int) y) deneyin
azheglov

35
Bu en doğru, özlü cevap olmalı. Baytlar arasına eklenecek bir işlenen yoktur, bu yüzden "iki bayt ekleme" nin neden işe yarayıp yaramadığını açıklamak yerine ( asla olmadı ), bu neticenin neden bir int olduğunu gösterir, çünkü gerçekleşen tek şey 2 ints eklenmesi. .
RichardTheKiwi

2
Tüm diğer cevapları (Bay Jon Skeet'e karşı suç yok) okurken başım döndü. Bu, kaputun altında neler olup bittiğini doğru bir şekilde açıklayan en basit cevap olarak buldum. Teşekkürler!
rayryeng

Başka bir yere yazdığım ve derleyici güdümlü otomatik tanıtımın ne zaman gerçekleştiğini belirlemek için bir program içeren bir cevap int: stackoverflow.com/a/43578929/4561887
Gabriel Staples

172

"Neden hiç oluyor" açısından, başkalarının söylediği gibi, C # tarafından bayt, sbyte, kısa veya ushort ile aritmetik için tanımlanmış herhangi bir işleç olmamasıdır. Bu cevap, bu operatörlerin neden tanımlanmadığıyla ilgilidir.

Temel olarak performans uğruna olduğuna inanıyorum. İşlemciler, 32 bit ile çok hızlı bir şekilde aritmetik yapmak için yerel işlemlere sahiptir. Otomatik bir bayt sonucundan dönüşüm geri Doing olabilir yapılabilir, ama aslında o davranışı istemiyoruz durumda performans cezalar neden olacaktır.

Ben düşünüyorum bu açıklamalı C # standartlarından biri bahsedilmiştir. Looking ...

EDIT: Can sıkıcı bir şekilde, şimdi açıklamalı ECMA C # 2 spec, açıklamalı MS C # 3 spec ve açıklama CLI spec baktı ve hiçbiri gördüğüm kadarıyla bu söz. Ben değilim emin Yukarıda verilen nedeni gördüm ama nerede biliyorsanız ben blowed ediyorum. Özür dilerim, referans hayranları :(


14
Bunu söylediğim için üzgünüm, ama bunu en iyi cevap bulamıyorum.
VVS

42
En iyi cevap olmadığını düşündüğünüz her cevabı iptal ettiniz mi? ;)
Jon Skeet

55
(Sadece açıklığa kavuşturmak için, size gerçekten gitmiyorum. Görünüşe göre herkesin aşağı oy için kendi kriterleri var ve sorun değil. Sadece ideal olmayanlardan ziyade aktif olarak zararlı olduğuna inanıyorsam bir cevabı aşağı indiririm. )
Jon Skeet

21
Oylamayı en iyi cevabı almak için bir araç olarak kullanıyorum. Aslında cevabımda çok fazla şey söylemediğini gördüm ki bu benim düşüşümün ana sebebiydi. Başka bir neden belki de temsilci oylama konusunda size büyük bir bonus veriyor ve "daha iyi" cevapların üstüne iniyorsun.
VVS

23
IMO "en iyi" yanıtı almanın en iyi yolu, bunu düzeltmektir. Dürüst olmak gerekirse, ben burada en bilgilendirici cevap sanmıyorum orada ( "Ne derleyici yapıyor" perspektif aksine) tasarım perspektif için Eric'in sorudaki yorum ... ama bunun dışında olduğunu düşünüyorum olduğunu çok "performans" ın ötesinde cevap. Özellikle, int + int = long önereceği gibi "taşmayı önler" argümanını (17 oy) gerçekten almıyorum.
Jon Skeet

68

Bunu daha önce bir yerde gördüğümü sandım . Gönderen Bu makalede, Eski Yeni Thing :

Diyelim ki 'bayt' üzerindeki işlemlerin 'bayt' ile sonuçlandığı bir fantezi dünyasında yaşadık.

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

Bu fantezi dünyasında, i'nin değeri 16 olurdu! Neden? + İşlecinin iki işleneninin her ikisi de bayt olduğundan, "b + c" toplamı bir bayt olarak hesaplanır, bu da tamsayı taşması nedeniyle 16 ile sonuçlanır. (Ve daha önce de belirttiğim gibi, tamsayı taşması yeni güvenlik saldırısı vektörüdür.)

DÜZENLEME : Raymond aslında C ve C ++ yaklaşımını başlangıçta savundu. Yorumlarda, C # 'ın dilin geriye dönük uyumluluğu nedeniyle aynı yaklaşımı benimsediğini savunuyor.


42
Tamsayılarla onları eklersek ve taşarsa, otomatik olarak farklı bir veri türü olarak dökülmez, neden byte ile yapar?
Ryan

2
Ints ile taşar. İnt.MaxValue + 1 eklemeyi deneyin 2147483648 yerine -2147483648 olsun.
David Basarab

8
@ Longhorn213: Evet, Ryan'ın söylediği şey: int matematik taşabilir, ancak int matematik uzun süre geri dönmez.
Michael Petrotta

28
Kesinlikle. Bunun bir güvenlik önlemi olması gerekiyorsa, çok kötü uygulanmış bir önlemdir;)
Jon Skeet

5
@Ryan: "tembel", ilkel matematik kadar temel bir şey için C # dil tasarımcılarına karşı oldukça ağır bir ücrettir. Eğer onları herhangi bir şeyle suçlamak istiyorsanız, onu "C / C ++ ile aşırı geriye dönük uyumluluk" haline getirin.
Michael Petrotta

58

C #

ECMA-334, ilacın sadece int + int, uint + uint, uzun + uzun ve ulong + ulong için yasal olarak tanımlandığını belirtir (ECMA-334 14.7.4). Bu itibarla, bunlar 14.4.2 ile ilgili olarak dikkate alınması gereken aday faaliyetlerdir. Bayttan int, uint, uzun ve ulong'a örtülü dökümler olduğundan, tüm toplama fonksiyonu üyeleri 14.4.2.1 altındaki uygulanabilir fonksiyon üyeleridir. 14.4.2.3'te kurallara göre en iyi örtülü ifadeyi bulmalıyız:

(C1) 'in int (T1)' e dökümü (C2) uint (T2) veya ulong (T2) 'ye dökümden daha iyidir çünkü:

  • T1 int ve T2 uint veya ulong ise, C1 daha iyi dönüşümdür.

(C1) 'in int (T1)' e dökümü, (C2) 'yi uzun (T2)' ye dökmekten daha iyidir çünkü int'ten long'a örtülü bir döküm vardır:

  • T1'den T2'ye örtük bir dönüşüm varsa ve T2'den T1'e örtük bir dönüşüm yoksa, C1 daha iyi dönüşümdür.

Bu nedenle int döndüren int + int işlevi kullanılır.

C # belirtiminde çok derinlere gömüldüğünü söylemenin çok uzun bir yolu var.

CLI

CLI yalnızca 6 tipte çalışır (int32, yerel int, int64, F, O ve &). (ECMA-335 bölüm 3 bölüm 1.5)

Bayt (int8) bu türlerden biri değildir ve eklenmeden önce otomatik olarak int32'ye zorlanır. (ECMA-335 bölüm 3 bölüm 1.6)


ECMA'nın yalnızca bu belirli işlemleri belirtmesi, bir dilin diğer kuralları uygulamasını engellemez. VB.NET, byte3 = byte1 And byte2cast olmadan yararlı bir şekilde izin verir , ancak 255'ten büyük int1 = byte1 + byte2bir değer verirse yararsız bir şekilde bir çalışma zamanı istisnası byte3 = byte1+byte2atar. 255'ten fazla olduğunda herhangi bir dilin izin verip vermeyeceğini ve bir istisna atacağını bilmiyorum , ancak int1 = byte1+byte2verim durumunda bir istisna atmaz 256-510 aralığında bir değer.
supercat

26

Bazı verimsizliklerin bayt eklediğini ve sonucun bir bayta geri kesildiğini gösteren cevaplar yanlıştır. x86 işlemcilerin 8 bitlik miktarlarda tamsayı çalışması için özel olarak tasarlanmış yönergeleri vardır.

Aslında, x86 / 64 işlemciler için, kodunun çözülmesi gereken işlenen önek baytı nedeniyle 32 bit veya 16 bit işlemler gerçekleştirmek 64 bit veya 8 bit işlemlerden daha az verimlidir. 32 bitlik makinelerde, 16 bitlik işlemler yapmak aynı cezayı gerektirir, ancak yine de 8 bitlik işlemler için ayrılmış opcodelar vardır.

Birçok RISC mimarisinin benzer yerel kelime / bayt verimli talimatları vardır. Genellikle bir bit uzunluğunun bir depola ve dönüştür ile imzalanmış değerine sahip olmayanlar.

Başka bir deyişle, bu karar, donanımın temeldeki yetersizlikleri nedeniyle değil, bayt tipinin ne için olduğu algısına dayanmalıdır.


+ 1; Sadece bu algı, her vardığımda yanlış olsaydı ve C # 'da iki bayt OR ...
Roman Starkov

Sonucu kısaltmak için herhangi bir performans maliyeti olmamalıdır. X86 derlemesinde, kayıttan bir bayt veya kayıttan dört bayt kopyalanması arasındaki fark budur.
Jonathan Allen

1
@JonathanAllen Kesinlikle. Tek fark, ironik bir şekilde, genişleme dönüşümü gerçekleştirirken yeterlidir . Geçerli tasarım genişleyen vereceği emirler bir performans düşüklüğüne maruz kalmaz (ya uzatmak veya genişletmek işaretsiz imzaladı.)
reirab

" bayt tipinin ne olduğuna ilişkin algı " - Bu, byte(ve char) için bu davranışı açıklayabilir , ancak shortanlamsal olarak açık bir şekilde bir sayı değildir.
smls

13

Bir keresinde Jon Skeet'ten bir şey okuduğumu hatırlıyorum (şimdi bulamıyorum, aramaya devam edeceğim) byte'nin + operatörünü nasıl aşırı yüklemediğiyle ilgili. Aslında, örneğinizdeki gibi iki bayt eklerken, her bayt aslında dolaylı olarak int'ye dönüştürülür. Bunun sonucu açıkçası bir int. Şimdi neden bu şekilde tasarlandığına göre, Jon Skeet'in yayın yapmasını bekleyeceğim :)

EDIT: Bulundu! Burada bu konu hakkında harika bilgiler .


9

Bunun nedeni taşma ve taşımadır.

İki adet 8 bit sayı eklerseniz, bunlar 9. bitin içine taşabilir.

Misal:

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

Emin bilmiyorum, ama ben varsayalım ints, longsve doublesonlar olduğu gibi oldukça büyük olduğu için daha fazla yer verilir. Ayrıca, iç veri yolunun genişliği 4 bayt veya 32 bit (şimdi 64 bit daha yaygın hale gelmektedir) genişliğinden dolayı, bilgisayarlar için daha verimli olan 4'ün katlarıdır. Bayt ve kısa biraz daha verimsizdir, ancak yerden tasarruf edebilirler.


23
Ancak daha büyük veri türleri aynı davranışı izlemez.
Inisheer

12
Taşma sorunları bir yana. Eğer mantığınızı alıp dile uygulayacak olsaydınız, tüm veri türleri toplama aritmetiğinden sonra daha büyük bir veri türü döndürürdü, ki bu kesinlikle böyle değildir. int + int = int, uzun + uzun = uzun. Bence soru tutarsızlık ile ilgili.
Joseph

Bu benim ilk düşüncemdi ama neden int + int = uzun değil? Bu yüzden "olası taşma" tartışmasını satın almıyorum ... henüz <grin>.
Robert Cartaino

11
Oh, ve "olası taşma" argümanı hakkında, neden byte + byte = short olmasın?
Robert Cartaino

A) Neden C # kuralları göz önüne alındığında çalışır şekilde çalışır? Cevabımı aşağıda görebilirsiniz. B) Neden olduğu gibi tasarlandı? Muhtemelen, çoğu insanın int ve baytları kullanma eğilimine ilişkin öznel kararlara dayanan sadece kullanılabilirlik düşünceleri.
mqp

5

1.6.7.5 C # dil spesifikasyonundan 7.2.6.2 İkili sayısal promosyonlar, diğer birçok kategoriye sığmazsa her iki işleneni de int'e dönüştürür. Benim tahminim onlar + baytını bir parametre olarak almak için aşırı yüklenmedi ama sadece int veri türünü kullanmak için biraz normal hareket etmek istiyorum.

C # dil Spec


4

Benim şüphe C # aslında aradığını olmasıdır operator+üzerinde tanımlı int(döndüren bir intsen olmadıkça checkedbloğu) ve dolaylı da her iki döküm bytes/ ' shortsiçin ints. Bu nedenle davranış tutarsız görünüyor.


3
Yığının her iki baytını da iter, sonra "add" komutunu çağırır. IL'de, iki değeri "yiyor" ifadesini ekleyin ve bunları bir int ile değiştirin.
Jonathan Allen

3

Bu muhtemelen dil tasarımcıları için pratik bir karardı. Sonuçta, bir int, 32-bit işaretli bir tam sayı olan Int32'dir. İnt'ten daha küçük bir tipte bir tamsayı işlemi yaptığınızda, yine de çoğu 32 bit CPU tarafından 32 bit imzalı bir int'e dönüştürülecektir. Bu, küçük tamsayıların taşma olasılığı ile birlikte, muhtemelen anlaşmayı mühürledi. Sizi sürekli olarak aşırı / düşük akışı kontrol etme işinden kurtarır ve baytlardaki bir ifadenin nihai sonucu aralıkta olduğunda, bazı ara aşamalarda aralık dışında olmasına rağmen, doğru bir şekilde sonuç.

Başka bir düşünce: Bu türlerdeki aşırı / düşük akışın simüle edilmesi gerekir, çünkü en olası hedef CPU'larda doğal olarak gerçekleşmez. Neden rahatsız oluyorsun?


2

Bu, çoğunlukla bu konuyla ilgili, burada benzer bir soruya ilk olarak gönderilen cevabım .

Int32'den daha küçük integral sayılarına sahip tüm işlemler, varsayılan olarak hesaplamadan önce 32 bite yuvarlanır. Sonucun Int32 olmasının nedeni, hesaplamadan sonra olduğu gibi bırakmaktır. MSIL aritmetik opcodlarını kontrol ederseniz, birlikte çalıştıkları tek integral sayısal tür Int32 ve Int64'tür. "Tasarım gereği".

Sonucu tekrar Int16 biçiminde istiyorsanız, cast in code işlemini gerçekleştirirseniz veya derleyici (hipotetik olarak) dönüşümü "başlık altında" yayarsa ilgisizdir.

Örneğin, Int16 aritmetiği yapmak için:

short a = 2, b = 3;

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

İki sayı 32 bite kadar genişleyecek, eklenecek, daha sonra 16 bite kadar kesilecek, bu da MS'in olmasını istedi.

Kısa (veya bayt) kullanmanın avantajı, büyük miktarda veriye (grafiksel veri, akış vb.)


1

Ekleme bayt için tanımlanmadı. Bu yüzden ekleme için int'e atılırlar. Çoğu matematik işlemi ve bayt için bu doğrudur. (eski dillerde olduğu gibi, bugün geçerli olduğunu varsayıyorum).


0

Sanırım hangi işlemin daha yaygın olduğu konusunda bir tasarım kararı ... Bayt + bayt = bayt belki çok daha fazla insan, bir int gerektiğinde int'e atmak zorunda kaldığında rahatsız olacak.


2
Ben bir kez başka şekilde rahatsız :) Her zaman bayt sonucuna ihtiyaç gibi görünüyor, bu yüzden her zaman döküm gerekir.
Roman Starkov

Dışında int almak zorunda değilsiniz. Oyuncular kapalı. Sadece diğer yol açıktır.
Niki

1
@nikie Sanırım cevabımı anlamadın. Eğer iki bayt eklemek bir bayt üretecekse, taşmaları önlemek için birisinin eklemeden önce işlenenleri (sonucu değil) dökmesi gerekir.
fortran

0

.NET Framework kodundan:

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

.NET 3.5 ve üstü ile basitleştirin :

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

şimdi şunları yapabilirsiniz:

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


0

Bayt ve int arasındaki performansı test ediyorum.
İnt değerleriyle:

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

Bayt değerleri ile:

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

İşte sonuç:
bayt: 3.57s 157mo, 3.71s 171mo, 3.74s 168mo CPU ile ~ = 30%
int: 4.05s 298mo, 3.92s 278mo, 4.28 294mo CPU ile ~ = 27%
Sonuç:
bayt daha fazla CPU kullanıyor ancak hafızaya mal olur ve daha hızlıdır (belki tahsis edilecek daha az bayt olduğu için)


-1

Diğer tüm harika yorumlara ek olarak, küçük bir tidbit ekleyeceğimi düşündüm. Birçok yorum, int, long ve hemen hemen başka herhangi bir sayısal türün de bu kurala uymadığını merak etti ... aritmatik yanıt olarak "daha büyük" bir tür döndür.

Birçok yanıtın performansla ilgisi vardı (32 bit 8 bitten daha hızlıdır). Gerçekte, 8 bit bir sayı 32 bit CPU'ya hala 32 bit bir sayıdır ... iki bayt ekleseniz bile, cpu'nun üzerinde çalıştığı veri yığını ne olursa olsun 32 bit olacaktır ... iki bayt eklemekten daha "hızlı" ol ... hepsi cpu ile aynı. ŞİMDİ, iki int eklemek 32 bit işlemciye iki longs eklemekten daha hızlı olacaktır, çünkü iki longs eklemek, işlemciler kelimesinden daha geniş sayılarla çalıştığınız için daha fazla mikroop gerektirir.

Bayt aritmetiğinin ints ile sonuçlanmasının temel nedeninin oldukça açık ve basit olduğunu düşünüyorum: 8 bit sadece çok ileri gitmiyor! : D 8 bit ile, işaretsiz bir 0-255 aralığınız vardır. Bu değil bir aritmetik bunları kullanırken sınırlamalar ÇOK yüksektir bayt içine ile ... çalıştırmak olacak olasılığına çalışmalarına odanın bir sürü. Bununla birlikte, ints, uzun veya çiftler vb.İle çalışırken bitlerin tükenme olasılığı önemli ölçüde daha düşüktür ... daha nadiren daha fazlasına ihtiyaç duyduğumuz kadar düşüktür.

Bayttan int'e otomatik dönüşüm mantıklıdır çünkü bir bayt ölçeği çok küçüktür. İnt'den uzunluğa, floattan double'a vb. Otomatik dönüşüm mantıklı değildir , çünkü bu sayılar önemli ölçüde ölçeklidir.


Bu hala neden byte - bytegeri döndüğünü intveya neden yayınlanmadığını short
açıklamıyor

Toplama işleminin neden çıkarma işleminden farklı bir tür döndürmesini istiyorsunuz? Eğer byte + bytegetiriler int255 + şey bir bayt tutabilir değerinden büyük olduğu için, herhangi bir bayt eksi bir döndürme türü tutarlılık açısından int dışında herhangi bir başka bayt dönüş şey olmasını mantıklı değil.
jrista

Yapmam, sadece yukarıdaki nedenin muhtemelen doğru olmadığını gösterir. Eğer sonuca "uydurma" ile ilgisi byteolsaydı , çıkarma çıkarma a döndürür byteve bayt ekleme a short( byte+ byteher zaman a'ya sığar short) döndürür . Dediğiniz gibi tutarlılık ile shortilgiliyse, yine de her iki işlem için de yeterli olacaktır int. Açıkçası nedenlerin bir karışımı var, hepsi mutlaka iyi düşünülmüş değil. Veya, aşağıda verilen performans nedeni daha doğru olabilir.
KthProg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.