Bir işlevin satır içi sürümü, satır içi olmayan sürümden farklı bir değer döndürür


85

Aynı işlevin, yalnızca biri satır içi olup diğerinde farklı olan iki sürümü nasıl farklı değerler döndürebilir? İşte bugün yazdığım bazı kodlar ve nasıl çalıştığından emin değilim.

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

Tüm çıktıların eşit olmasını beklerdim 1, ancak aslında şu çıktıyı veriyor (g ++ 8.3.1, bayrak yok):

1
0
1

onun yerine

1
1
1

Düzenleme: clang ++ 7.0.0 şunu verir:

0
0
0

ve g ++ -Bunu hızlıca:

1
1
1

3
Lütfen hangi derleyici, derleyici seçeneklerini ve hangi makineyi kullandığınızı belirtebilir misiniz? Windows üzerinde GCC 7.1'de benim için iyi çalışıyor.
Diodacus

31
==Kayan nokta değerleri her zaman biraz öngörülemez değil mi?
500 - Dahili Sunucu Hatası


2
-OfastBu tür optimizasyonlara izin veren seçeneği ayarladınız mı?
cmdLP

4
Derleyici , standart kitaplık dönerken cbrt(27.0)değeri için 0x0000000000000840döner 0x0100000000000840. Çiftler virgülden sonra 16. sayı olarak farklılık gösterir. Benim sistem: archlinux4.20 x64 gcc8.2.1 glibc2.28 ile İşaretli bu . Gcc veya glibc'nin doğru olup olmadığını merak ediyorum.
KamilCuk

Yanıtlar:


73

Açıklama

Bazı derleyiciler (özellikle GCC), derleme zamanında ifadeleri değerlendirirken daha yüksek hassasiyet kullanır. Bir ifade yalnızca sabit girdilere ve değişmez değerlere bağlıysa, ifade bir constexpr değişkenine atanmamış olsa bile derleme zamanında değerlendirilebilir. Bunun meydana gelip gelmediği şunlara bağlıdır:

  • İfadenin karmaşıklığı
  • Derleyicinin derleme zamanı değerlendirmesi yapmaya çalışırken kesme olarak kullandığı eşik
  • Özel durumlarda kullanılan diğer buluşsal yöntemler (clang elides döngüleri gibi)

İlk durumda olduğu gibi bir ifade açıkça sağlanırsa, karmaşıklığı daha düşüktür ve derleyicinin bunu derleme zamanında değerlendirmesi olasıdır.

Benzer şekilde, bir işlev satır içi olarak işaretlenirse, derleyicinin onu derleme zamanında değerlendirme olasılığı daha yüksektir, çünkü satır içi işlevler değerlendirmenin gerçekleşebileceği eşiği yükseltir.

Daha yüksek optimizasyon seviyeleri, daha yüksek hassasiyetli derleme zamanı değerlendirmesi nedeniyle gcc'de tüm ifadelerin doğru olarak değerlendirildiği -Ofast örneğinde olduğu gibi bu eşiği de artırır.

Bu davranışı burada derleyici gezgininde gözlemleyebiliriz. -O1 ile derlendiğinde, yalnızca satır içi olarak işaretlenen işlev derleme zamanında değerlendirilir, ancak -O3'te her iki işlev de derleme zamanında değerlendirilir.

Not: Derleyici gezgini örneklerinde printfbunun yerine iostream kullanıyorum çünkü ana işlevin karmaşıklığını azaltarak efekti daha görünür kılıyor.

Çalışma inlinezamanı değerlendirmesini etkilemediğini göstermek

Standart girdiden değer elde ederek ifadelerden hiçbirinin derleme zamanında değerlendirilmemesini sağlayabiliriz ve bunu yaptığımızda, 3 ifadenin tümü burada gösterildiği gibi yanlış döndürür: https://ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

İle Kontrast Bu örnekte aynı derleyici ayarları kullanmak ancak daha yüksek duyarlıklı derleme zamanı değerlendirme sonucunda derleme sırasında değer sağlar.


22

Gözlemlendiği gibi, ==kayan nokta değerlerini karşılaştırmak için operatörün kullanılması, farklı derleyicilerle ve farklı optimizasyon seviyelerinde farklı çıktılarla sonuçlanmıştır.

Kayan nokta değerlerini karşılaştırmanın iyi bir yolu , makalede özetlenen göreceli tolerans testidir: Kayan nokta toleransları yeniden ziyaret edildi .

İlk önce Epsilon( göreceli tolerans ) değerini hesaplıyoruz ki bu durumda:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

Ve sonra bunu hem satır içi hem de satır içi olmayan işlevlerde şu şekilde kullanın:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

Şimdi işlevler şunlardır:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

Şimdi çıktı, [1 1 1]farklı derleyicilerle ve farklı optimizasyon seviyelerinde beklendiği gibi olacaktır ( ).

Canlı demo


max()Aramanın amacı nedir ? Tanım olarak, floor(x)küçüktür veya eşittir x, bu nedenle max(x, floor(x))her zaman eşit olacaktır x.
Ken Thomases

@KenThomases: Bir argüman diğerinin maxsadece olduğu bu özel durumda, floorgerekli değildir. Ancak argümanların maxbirbirinden bağımsız değerler veya ifadeler olabileceği genel bir durumu düşündüm .
PW

operator==(double, double)Tam olarak bunu yapmamalı , farkın ölçekli bir epsilondan daha küçük olup olmadığını kontrol et? SO ile ilgili kayan nokta ile ilgili soruların yaklaşık% 90'ı o zaman mevcut olmazdı.
Peter - Monica'yı eski

Kullanıcının Epsilonkendi gereksinimlerine bağlı olarak değeri belirtmesinin daha iyi olacağını düşünüyorum .
PW
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.