Aşağıdaki beyana sahipsem:
float a = 3.0 ;
bu bir hata mı? Bir kitapta okunan 3.0
bir olan double
değer ve sahip olduğum olarak belirtmek float a = 3.0f
. Öyle mi?
Aşağıdaki beyana sahipsem:
float a = 3.0 ;
bu bir hata mı? Bir kitapta okunan 3.0
bir olan double
değer ve sahip olduğum olarak belirtmek float a = 3.0f
. Öyle mi?
;
sonraya ihtiyacın var .
Yanıtlar:
Bildirmek bir hata değildir float a = 3.0
: eğer yaparsanız, derleyici çift değişmez 3.0'ı sizin için bir float'a dönüştürecektir.
Ancak, gereken belirli senaryolarda şamandıra değişmezleri notasyonu kullanın.
Performans nedenleriyle:
Özellikle şunları göz önünde bulundurun:
float foo(float x) { return x * 0.42; }
Burada derleyici, döndürülen her değer için (çalışma zamanında ödeyeceğiniz) bir dönüşüm yayınlayacaktır. Bundan kaçınmak için şunları beyan etmelisiniz:
float foo(float x) { return x * 0.42f; } // OK, no conversion required
Sonuçları karşılaştırırken hataları önlemek için:
örneğin aşağıdaki karşılaştırma başarısız olur:
float x = 4.2;
if (x == 4.2)
std::cout << "oops"; // Not executed!
Float değişmez gösterimi ile düzeltebiliriz:
if (x == 4.2f)
std::cout << "ok !"; // Executed!
(Not: elbette, genel olarak eşitlik için float veya double sayıları böyle karşılaştırmanız gerekmez )
Doğru aşırı yüklenmiş işlevi çağırmak için (aynı nedenden dolayı):
Misal:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
int main()
{
foo(42.0); // calls double overload
foo(42.0f); // calls float overload
return 0;
}
Cyber'in belirttiği gibi , bir tür kesintisi bağlamında, derleyicinin birfloat
:
Durumda auto
:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
Ve benzer şekilde, şablon türü kesintisi durumunda:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
template<typename T>
void bar(T t)
{
foo(t);
}
int main()
{
bar(42.0); // Deduce double
bar(42.0f); // Deduce float
return 0;
}
42
, otomatik olarak yükseltilen float
(ve herhangi bir düzgün derleyicide derleme zamanında gerçekleşecek olan) bir tamsayıdır , dolayısıyla performans cezası yoktur. Muhtemelen şöyle bir şeyi kastettin 42.0
.
4.2
için 4.2f
ayar yan etkisi olabilir FE_INEXACT
, bayrak derleyici ve sisteme bağlı olarak ve bazı (kuşkusuz az) programları bu bayrak için kayan nokta operasyonları olmayan kesin, ve hangi bakımı ve testi yapmak . Bu, basit açık derleme zamanı dönüşümünün programın davranışını değiştirdiği anlamına gelir.
float foo(float x) { return x*42.0; }
tek duyarlıklı bir çarpma için derlenebilir ve en son denediğimde Clang tarafından bu şekilde derlendi. Ancak float foo(float x) { return x*0.1; }
, tek bir tek duyarlıklı çarpmaya derlenemez. Bu yamadan önce biraz fazla iyimser olabilirdi, ancak yamadan sonra sonuç her zaman aynı olduğunda dönüşüm-double_precision_op-conversion-double_precision_op-conversion'u single_precision_op'a birleştirmelidir. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
someFloat
, ifade someFloat * 0.1
daha doğru sonuçlar someFloat * 0.1f
verirken, çoğu durumda bir kayan nokta bölmesinden daha ucuzdur. Örneğin, (float) (167772208.0f * 0.1), 16777222 yerine 16777220'ye doğru bir şekilde yuvarlayacaktır. Bazı derleyiciler, double
bir kayan nokta bölmesinin yerine bir çarpmanın yerini alabilir , ancak olmayanlar için (tüm değerler olmasa da çoğu için güvenlidir) ) çarpma yararlı bir optimizasyon olabilir, ancak yalnızca double
karşılıklı olarak yapılırsa .
Değişkeni kayan nokta olarak tanımladığınız için derleyici aşağıdaki değişmez değerlerden herhangi birini yüzer sayıya çevirecektir.
float a = 3; // converted to float
float b = 3.0; // converted to float
float c = 3.0f; // float
Kullandığınız auto
(veya başka türden kesinti yöntemleri), örneğin:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
auto
tek durum bu değildir.
Son eki olmayan kayan nokta değişmezleri double türündedir , bu taslak C ++ standart bölümünde 2.14.4
Kayan değişmez değerler ele alınmıştır :
[...] Bir sonek tarafından açıkça belirtilmediği sürece kayan değişmez değerin türü çifttir. [...]
böylece atamak bir hatadır 3.0
bir çift değişmezi a şamandıra ?:
float a = 3.0
Hayır, dönüştürülmez, 4.8
Kayan nokta dönüşümleri bölümünde ele alınmaktadır :
Kayan nokta türündeki bir prvalue, başka bir kayan nokta türünün bir pr değerine dönüştürülebilir. Kaynak değeri hedef türünde tam olarak gösterilebiliyorsa, dönüşümün sonucu bu tam temsildir. Kaynak değeri iki bitişik hedef değer arasındaysa, dönüşümün sonucu bu değerlerden herhangi birinin uygulama tanımlı bir seçimidir. Aksi takdirde, davranış tanımsızdır.
GotW # 67'de bunun etkileri hakkında daha fazla ayrıntı okuyabiliriz : çift veya hiçbir şey söyleyen:
Bu, bir çift sabitin örtük olarak (yani sessizce) bir float sabitine dönüştürülebileceği anlamına gelir, bunu yapmak hassasiyeti (yani verileri) kaybetse bile. Bunun C uyumluluğu ve kullanılabilirlik nedenleriyle kalmasına izin verildi, ancak kayan nokta çalışması yaparken aklınızda bulundurmaya değer.
Tanımlanmamış davranış olan bir şey yapmaya çalışırsanız, yani bir float'ın temsil edebileceği minimum değerden küçük veya maksimum değerden daha büyük bir float'a çift miktar koymaya çalışırsanız, kalite derleyicisi sizi uyaracaktır. Gerçekten iyi bir derleyici, tanımlanabilecek, ancak bilgi kaybedebilecek bir şey yapmaya çalışırsanız, yani bir float tarafından temsil edilebilen minimum ve maksimum değerler arasında bir float'a çift miktar koyarsanız, isteğe bağlı bir uyarı sağlayacaktır. tam olarak bir şamandıra olarak temsil edilmelidir.
Bu yüzden genel durum için farkında olmanız gereken uyarılar var.
Pratik bir perspektiften, bu durumda, teknik olarak bir dönüşüm olsa da sonuçlar büyük olasılıkla aynı olacaktır , bunu godbolt üzerinde aşağıdaki kodu deneyerek görebiliriz :
#include <iostream>
float func1()
{
return 3.0; // a double literal
}
float func2()
{
return 3.0f ; // a float literal
}
int main()
{
std::cout << func1() << ":" << func2() << std::endl ;
return 0;
}
ve biz sonuçları görüyoruz func1
ve func2
ikisini de kullanarak, aynıdır clang
ve gcc
:
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
As Pascal bu yorumunda işaret her zaman bu saymak mümkün olmayacaktır. 0.1
Ve 0.1f
sırasıyla kullanmak , dönüştürmenin artık açıkça yapılması gerektiğinden, oluşturulan derlemenin farklı olmasına neden olur. Aşağıdaki kod:
float func1(float x )
{
return x*0.1; // a double literal
}
float func2(float x)
{
return x*0.1f ; // a float literal
}
aşağıdaki derlemeyle sonuçlanır:
func1(float):
cvtss2sd %xmm0, %xmm0 # x, D.31147
mulsd .LC0(%rip), %xmm0 #, D.31147
cvtsd2ss %xmm0, %xmm0 # D.31147, D.31148
ret
func2(float):
mulss .LC2(%rip), %xmm0 #, D.31155
ret
Dönüşümün performans üzerinde bir etkisi olup olmayacağını belirleyip belirlemediğinize bakılmaksızın, doğru türü kullanmak niyetinizi daha iyi belgeler. Örneğin, açık bir dönüşüm kullanmak static_cast
, dönüşümün yanlışlıkla değil, bir hatayı veya olası bir hatayı işaret edebilecek şekilde tasarlandığını netleştirmeye yardımcı olur.
Not
Supercat'ın işaret ettiği gibi, örneğin 0.1
ile çarpma 0.1f
eşdeğer değildir. Yorumdan alıntı yapacağım çünkü mükemmeldi ve bir özet muhtemelen bunu hak etmeyecektir:
Örneğin, f 100000224'e eşitse (bu tam olarak bir kayan nokta olarak gösterilebilir), onu onda bir ile çarpmak 10000022'ye yuvarlanan bir sonuç vermelidir, ancak 0.1f ile çarpmak bunun yerine hatalı bir şekilde 10000023'e yuvarlayan bir sonuç verecektir. Eğer on'a bölme niyetindeyseniz, çift sabit 0.1 ile çarpma muhtemelen 10f'ye bölmeden daha hızlı ve 0.1f ile çarpmadan daha kesin olacaktır.
Asıl amacım, başka bir soruda verilen yanlış bir örneği göstermekti, ancak bu, oyuncak örneklerinde ince sorunların olabileceğini iyi bir şekilde gösteriyor.
f = f * 0.1;
ve f = f * 0.1f;
farklı şeyler yapmak . Örneğin, f
100000224'e eşitse (ki bu tam olarak a olarak gösterilebilir float
), onu onda bir ile çarpmak, 10000022'ye yuvarlanan bir sonuç vermelidir, ancak 0.1f ile çarpmak, bunun yerine yanlışlıkla 10000023'e yuvarlayan bir sonuç verecektir. amaç ona bölmektir, double
sabit 0,1 ile çarpma büyük olasılıkla bölmeden daha hızlı 10f
ve çarpmadan daha kesin olacaktır 0.1f
.
Derleyicinin onu reddetmesi anlamında bir hata değil, ancak istediğiniz gibi olmayabileceği anlamında bir hatadır.
Kitabınızın doğru bir şekilde ifade ettiği gibi 3.0
, bir tür değeridir double
. Dan örtük bir dönüşüm vardouble
yefloat
, bu yüzdenfloat a = 3.0;
bir değişkenin geçerli bir tanımıdır.
Bununla birlikte, en azından kavramsal olarak, bu gereksiz bir dönüşüm gerçekleştirir. Derleyiciye bağlı olarak, dönüştürme derleme zamanında gerçekleştirilebilir veya çalışma süresi için kaydedilebilir. Çalışma zamanı için kaydetmenin geçerli bir nedeni, kayan nokta dönüşümlerinin zor olması ve değer tam olarak gösterilemezse beklenmeyen yan etkilere sahip olabilmesidir ve değerin tam olarak temsil edilip edilemeyeceğini doğrulamak her zaman kolay değildir.
3.0f
bu sorunu önler: teknik olarak, derleyicinin çalışma zamanında sabiti hesaplamasına izin verilir (her zaman öyledir), burada, herhangi bir derleyicinin bunu yapması için kesinlikle hiçbir neden yoktur.
Bir hata olmasa da, biraz özensiz. Bir şamandıra istediğinizi biliyorsunuz, bu yüzden onu bir şamandıra ile başlatın.
Başka bir programcı gelebilir ve bildirimin hangi kısmının, türünün veya başlatıcının doğru olduğundan emin olmayabilir. Neden ikisi de doğru olmasın?
float Cevap = 42.0f;
Bir değişken tanımladığınızda, sağlanan başlatıcıyla başlatılır. Bu, başlatıcının değerini başlatılan değişkenin türüne dönüştürmeyi gerektirebilir. Şöyle dediğinizde olan şey budur float a = 3.0;
: Başlatıcı değeri dönüştürülür float
ve dönüşümün sonucu başlangıç değeri olur a
.
Bu genellikle iyidir, ancak yazmaktan zarar gelmez 3.0f
ne yaptığınızın farkında olduğunuzu göstermek , özellikle de yazmak istiyorsanız auto a = 3.0f
.
Aşağıdakileri denerseniz:
std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;
çıktı alacaksınız:
4:8
32-bit makinede 3.2f boyutu 4 bayt olarak alınır ve 3.2, 32-bit makinede 8 bayt alan çift değer olarak yorumlanır. Bu, aradığınız cevabı sağlamalıdır.
double
ve float
farklı, float
bir çift değişmezden
Derleyici, değişmez değerlerden en uygun türü çıkarır veya en uygun olduğunu düşündüğü şeyin en uygun olduğunu düşünür. Bu, hassasiyet yerine verimlilik kaybıdır, yani şamandıra yerine çift kullanın. Şüpheniz varsa, açık hale getirmek için kaşlı ayraçları kullanın:
auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int
Yazı tipi dönüştürme kurallarının geçerli olduğu başka bir değişkenden başlatırsanız hikaye daha ilginç hale gelir: Bir çift formu bir literal oluşturmak yasal olsa da, olası daraltma olmadan bir int'ten oluşturulamaz:
auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'
3.0
sizin için bir float'a dönüştürecektir. Sonuç, ile ayırt edilemezfloat a = 3.0f
.