Float, kesinlik ve ondalık basamak sayısı ile dizeye dönüştürülsün mü?


91

Ondalık basamakların hassasiyetini ve sayısını belirtirken bir kayan noktayı C ++ 'da bir dizeye nasıl dönüştürebilirsiniz?

Örneğin: 3.14159265359 -> "3.14"


neden istediğiniz hassasiyette bir geçici kayan nokta oluşturup sonra onu dizeye dönüştürmüyorsunuz?
Gilad

@Gilad 'Temp float' 'belirli bir hassasiyete' sahip değildir ve böyle bir yaklaşımın bozulacağı durumlar vardır. Bu soru temelde "% .2f 'biçiminin eşdeğeri nedir" diye sormaya benzer.
user2864740

Yanıtlar:


133

Tipik bir yol stringstreamşunları kullanmaktır :

#include <iomanip>
#include <sstream>

double pi = 3.14159265359;
std::stringstream stream;
stream << std::fixed << std::setprecision(2) << pi;
std::string s = stream.str();

Düzeltildi gör

Sabit kayan nokta gösterimi kullan

Str akışı için floatfieldbiçim bayrağını olarak ayarlar .fixed

Olarak floatfieldayarlandığında fixed, kayan nokta değerleri sabit nokta notasyonu kullanılarak yazılır: değer, ondalık kısımda tam olarak kesinlik alanı ( precision) tarafından belirtildiği gibi çok sayıda basamakla temsil edilir ve üs parçası olmadan temsil edilir.

ve setprecision .


Verileri XML veya JSON dosyasında depolamak gibi teknik amaçlı dönüşümler için , C ++ 17, to_chars işlev ailesini tanımlar .

Uyumlu bir derleyici varsayarsak (yazarken eksik olduğumuz), şöyle bir şey düşünülebilir:

#include <array>
#include <charconv>

double pi = 3.14159265359;
std::array<char, 128> buffer;
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), pi,
                               std::chars_format::fixed, 2);
if (ec == std::errc{}) {
    std::string s(buffer.data(), ptr);
    // ....
}
else {
    // error handling
}

31

Bu tür şeyleri yapmanın alışılmış yöntemi "dizeye yazdırmaktır". C ++ 'da bu, aşağıdaki std::stringstreamgibi bir şey kullanmak anlamına gelir :

std::stringstream ss;
ss << std::fixed << std::setprecision(2) << number;
std::string mystring = ss.str();

13

Diğer bir seçenek ise snprintf:

double pi = 3.1415926;

std::string s(16, '\0');
auto written = std::snprintf(&s[0], s.size(), "%.2f", pi);
s.resize(written);

Demo . Hata işleme eklenmelidir, yani kontrol edilmelidirwritten < 0.


snprintfBu durumda neden daha iyi olur ? Lütfen açıklayın ... Kesinlikle daha hızlı olmayacak, daha az kod üretecek [Bir printf uygulaması yazdım, 2000 satır kodun hemen altında oldukça kompakt, ancak makro genişletmelerle yaklaşık 2500 satıra
ulaşıyor

1
@MatsPetersson Daha hızlı olacağına inanıyorum. Bu cevabı ilk etapta vermemin nedeni bu.
Columbo

@MatsPetersson Snprintf'in gerçekten daha az zaman aldığını gösteren 17/22 faktörüne göre ölçümler yaptım (GCC 4.8.1, -O2). Kodu kısa süre içinde yükleyeceğim.
Columbo

@MatsPetersson Karşılaştırmam muhtemelen kusurludur, ancak dizi akışı sürümü, 1000000 yinelemedeki snprintf sürümünden iki kat daha uzun sürer (Clang 3.7.0, libstdc ++, -O2).

Ben de bazı ölçümler yaptım - ve görünen o ki (en azından Linux'ta) hem stringstream hem de snprintf aynı temel __printf_fpişlevselliği kullanıyor - stringstreamgerçekten gerekmemesi gerektiği için çok daha uzun sürmesi oldukça garip .
Mats Petersson

10

Sen C ++ 20 kullanabilir std::formatveya fmt::formatgelen işlevi {Fmt} kütüphanede , std::formatdayanmaktadır:

#include <fmt/core.h>

int main()
  std::string s = fmt::format("{:.2f}", 3.14159265359); // s == "3.14"
}

2kesinlik nerede .


8

Burada sadece std kullanan bir çözüm. Ancak, bunun yalnızca aşağı yuvarlandığını unutmayın.

    float number = 3.14159;
    std::string num_text = std::to_string(number);
    std::string rounded = num_text.substr(0, num_text.find(".")+3);

İçin roundedberaber, burada:

3.14

Kod, tüm şamandırayı dizeye dönüştürür, ancak "." Karakterinden sonra tüm karakterleri 2 karakter keser.


Tek sorun, bu şekilde aslında sayıyı yuvarlamamanızdır.
ChrCury78

1
@ ChrCury78 cevabımda belirtildiği gibi, bu sadece aşağı yuvarlar. Normal bir yuvarlama istiyorsanız, değerinizin ardından digete 5 eklemeniz yeterlidir. Örneğin number + 0.005benim durumumda.
Sebastian R.

3

Burada, kayan sayıyı dizelere dönüştürürken kaçınmak istediğiniz olumsuz bir örnek veriyorum.

float num=99.463;
float tmp1=round(num*1000);
float tmp2=tmp1/1000;
cout << tmp1 << " " << tmp2 << " " << to_string(tmp2) << endl;

Sen alırsın

99463 99.463 99.462997

Not: num değişkeni 99.463'e yakın herhangi bir değer olabilir, aynı çıktıyı alacaksınız. Buradaki amaç, uygun c ++ 11 "to_string" işlevinden kaçınmaktır. Bu tuzaktan kurtulmam biraz zaman aldı. En iyi yol stringstream ve sprintf yöntemleridir (C dili). C ++ 11 veya daha yenisi, gösterilecek kayan noktadan sonraki basamak sayısı olarak ikinci bir parametre sağlamalıdır. Şu anda varsayılan değer 6'dır. Başkalarının bu konu üzerinde zaman kaybetmemesi için bunu varsayıyorum.

İlk sürümümü yazdım, düzeltilmesi gereken herhangi bir hata bulursanız lütfen bana bildirin. İomanipulator ile tam davranışı kontrol edebilirsiniz. Benim işlevim, ondalık noktadan sonraki basamakların sayısını göstermek içindir.

string ftos(float f, int nd) {
   ostringstream ostr;
   int tens = stoi("1" + string(nd, '0'));
   ostr << round(f*tens)/tens;
   return ostr.str();
}
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.