Bir ikiliyi 32 bit int'e yuvarlamanın hızlı bir yöntemi açıklandı


169

Lua'nın kaynak kodunu okurken, Lua'nın a'yı 32-bit'e macroyuvarlamak için kullandığını fark ettim . Çıkardım ve şöyle görünüyor:doubleintmacro

union i_cast {double d; int i[2]};
#define double2int(i, d, t)  \
    {volatile union i_cast u; u.d = (d) + 6755399441055744.0; \
    (i) = (t)u.i[ENDIANLOC];}

İşte ENDIANLOColarak tanımlanır endian , 0küçük endian için 1büyük endian için. Lua endiyansı dikkatle ele alır. veya tgibi bir tamsayı türü anlamına gelir .intunsigned int

Biraz araştırma yaptım macrove aynı düşünceyi kullanan daha basit bir format var :

#define double2int(i, d) \
    {double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}

Veya bir C ++ stilinde:

inline int double2int(double d)
{
    d += 6755399441055744.0;
    return reinterpret_cast<int&>(d);
}

Bu hile IEEE 754 kullanan herhangi bir makinede çalışabilir (bu hemen hemen her makine anlamına gelir). Hem pozitif hem de negatif sayılar için çalışır ve yuvarlama Bankanın Kuralını takip eder . (Bu şaşırtıcı değildir, çünkü IEEE 754'ü takip eder.)

Test etmek için küçük bir program yazdım:

int main()
{
    double d = -12345678.9;
    int i;
    double2int(i, d)
    printf("%d\n", i);
    return 0;
}

Beklendiği gibi -12345679 çıktı.

Bu zorluğun nasıl işlediğini ayrıntılı olarak anlatmak istiyorum macro. Sihirli sayı 6755399441055744.0aslında 2^51 + 2^52ya 1.5 * 2^52, ve 1.5ikili olarak temsil edilebilir 1.1. Bu sihirli sayıya herhangi bir 32-bit tam sayı eklendiğinde, buradan kayboldum. Bu hile nasıl çalışır?

Not: Bu Lua kaynak kodunda, Llimits.h .

GÜNCELLEME :

  1. @Mysticial'ın işaret ettiği gibi, bu yöntem kendini 32 bit ile sınırlamaz , sayı 2 ^ 52 aralığında olduğu sürece int64 bit'e de genişletilebilir int. ( macroBazı değişikliklere ihtiyaç var.)
  2. Bazı malzemeler bu yöntemin Direct3D'de kullanılamayacağını söylüyor .
  3. X86 için Microsoft birleştirici ile çalışırken, daha da hızlı macroyazılmış assembly(bu da Lua kaynağından çıkarılır):

    #define double2int(i,n)  __asm {__asm fld n   __asm fistp i}
  4. Tek bir hassas sayı için benzer bir sihirli sayı var: 1.5 * 2 ^23


3
neye kıyasla "hızlı"?
Cory Nelson

3
@CoryNelson Basit bir oyuncuya kıyasla hızlı. Bu yöntem, doğru bir şekilde uygulandığında (SSE intrinsics ile), bir dökümden tam anlamıyla yüz kat daha hızlıdır. (oldukça pahalı bir dönüşüm koduna kötü bir işlev çağrısını başlatır)
Mysticial

2
Doğru - Bundan daha hızlı olduğunu görebiliyorum ftoi. Ama SSE'den bahsediyorsanız, neden sadece tek talimatı kullanmıyorsunuz CVTTSD2SI?
Cory Nelson

3
@tmyklebu Gidilen kullanım durumlarının double -> int64çoğu gerçekten 2^52aralık dahilindedir . Bunlar özellikle kayan noktalı FFT'ler kullanarak tamsayı kıvrımları yaparken yaygındır.
Gizemli

7
@MSalters Mutlaka doğru değil. Bir oyuncu kadrosu, taşma ve NAN vakalarının uygun şekilde işlenmesi de dahil olmak üzere dilin özelliklerine uygun olmalıdır. (veya derleyici IB veya UB durumunda belirttiği her ne ise) Bu kontroller çok pahalı olma eğilimindedir. Bu soruda bahsedilen hile, bu tür köşe vakalarını tamamen görmezden geliyor. Bu nedenle, hızı istiyorsanız ve uygulamanız bu gibi köşe durumlarını umursamıyorsa (veya hiç karşılaşmazsa), bu hack mükemmel bir şekilde uygundur.
Gizemli

Yanıtlar:


161

A doubleşöyle temsil edilir:

çift ​​gösterim

ve iki adet 32-bit tamsayı olarak görülebilir; şimdi, intkodunuzun tüm sürümlerinde (32 bit olduğunu varsayarak int) alınan, şeklin sağındaki koddur , bu yüzden sonunda yaptığınız şey en düşük 32 bit mantis almaktır.


Şimdi, sihirli sayıya; doğru şekilde belirttiğiniz gibi, 6755399441055744 2 ^ 51 + 2 ^ 52; böyle bir sayı eklemek double, burada Wikipedia tarafından açıklandığı gibi ilginç bir özelliğe sahip olan 2 ^ 52 ve 2 ^ 53 arasındaki "tatlı aralığa" girmeye zorlar :

2 52 = 4.503.599.627.370.496 ve 2 53 = 9.007.199.254.740.992 arasında temsili sayılar tamsayıdır

Bu, mantisin 52 bit genişliğinden kaynaklanmaktadır.

2 51 +2 52 eklemeyle ilgili diğer ilginç gerçek , sadece en düşük 32 biti aldığımız için mantissa'yı sadece en yüksek iki bitte etkilemesidir.


Son fakat en az değil: işaret.

IEEE 754 kayan nokta bir büyüklük ve işaret temsilini kullanırken, "normal" makinelerdeki tamsayılar 2'nin tamamlayıcı aritmetiğini kullanır; bu nasıl halledilir?

Sadece pozitif tamsayılardan bahsettik; şimdi 32-bit ile temsil edilebilen aralıkta negatif bir sayı ile uğraştığımızı varsayalım int(mutlak değerde) (-2 ^ 31 + 1); ara -a. Böyle bir sayı, sihirli sayı eklenerek açıkça pozitif hale getirilir ve elde edilen değer 2 52 +2 51'dir. + (- a) olur.

Şimdi, mantis'i 2'nin tamamlayıcı temsilinde yorumlarsak ne elde ederiz? Bu, 2'nin (2 52 +2 51 ) ve (-a) tamamlayıcı sonucunun bir sonucu olmalıdır . Yine, ilk terim sadece üst iki biti etkiler, 0 ~ 50 bitlerinde kalan, 2'nin (-a) 'nın tamamlayıcı temsilidir (yine ek iki üst bit).

Bir 2'nin tamamlayıcı sayısının daha küçük bir genişliğe indirgenmesi sadece soldaki ekstra bitlerin kesilmesiyle yapıldığından, alt 32 bitin alınması 32 bit, 2'nin tamamlayıcı aritmetiğini doğru bir şekilde (-a) verir.


"" "2 ^ 51 + 2 ^ 52 eklemeyle ilgili diğer ilginç gerçek, mantisayı yalnızca en yüksek iki bitte etkilemesidir - yine de atılır, çünkü yalnızca en düşük 32 bitini alıyoruz" "" Bu nedir? Bunu eklemek tüm mantisleri değiştirebilir!
YvesgereY

@John: elbette, onları eklemenin tüm amacı, değeri o aralıkta olmaya zorlamaktır, bu da açıkça mantisin (diğer şeyler arasında) orijinal değere göre değişmesine neden olabilir. Burada söylediğim şey, o aralıkta olduğunuzda, karşılık gelen 53 bit tamsayıdan farklı olan tek bitlerin, yine de atılan bit 51 ve 52 olmasıdır.
Matteo Italia

2
Size dönüştürmek isteyenler için bunu int64_tmantisleri 13 bit kadar sola ve sonra sağa kaydırarak yapabilirsiniz. Bu, üs ve iki biti 'sihirli' sayıdan temizler, ancak işareti 64 bit işaretli tam sayıya yaymaya devam eder. union { double d; int64_t l; } magic; magic.d = input + 6755399441055744.0; magic.l <<= 13; magic.l >>= 13;
Wojciech Migda

2 ^ 51'in yalnızca negatif değerleri işlemek için gerekli olduğunu doğru anladım mı?
Kentzo
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.