Ubah float menjadi string dengan presisi & jumlah digit desimal ditentukan?

91

Bagaimana Anda mengonversi pelampung menjadi string di C ++ sambil menentukan presisi & jumlah digit desimal?

Sebagai contoh: 3.14159265359 -> "3.14"

Shannon Matthews
sumber
mengapa tidak membuat temp float dengan presisi yang Anda inginkan dan kemudian mengubahnya menjadi string?
Gilad
@Gilad 'temp float' tidak memiliki 'presisi yang ditentukan' dan ada beberapa kasus ketika pendekatan seperti itu akan rusak. Pertanyaan ini pada dasarnya seperti menanyakan "apa yang setara dengan format '% .2f'"?
pengguna2864740
1
Lihat stackoverflow.com/questions/14432043/c-float-formatting jika ini hanya diperlukan untuk IO.
pengguna2864740

Jawaban:

133

Cara tipikal adalah dengan menggunakan stringstream:

#include <iomanip>
#include <sstream>

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

Lihat diperbaiki

Gunakan notasi floating-point tetap

Setel floatfield bendera format untuk aliran strfixed .

Jika floatfielddisetel ke fixed, nilai floating-point ditulis menggunakan notasi titik tetap: nilai diwakili dengan angka persis sama di bagian desimal seperti yang ditentukan oleh bidang presisi ( precision) dan tanpa bagian eksponen.

dan setprecision .


Untuk konversi tujuan teknis , seperti menyimpan data dalam file XML atau JSON, C ++ 17 mendefinisikan keluarga fungsi to_chars .

Dengan asumsi kompiler yang sesuai (yang tidak kami miliki pada saat penulisan), hal seperti ini dapat dipertimbangkan:

#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
}
AlexD
sumber
31

Metode umum untuk melakukan hal semacam ini adalah dengan "mencetak ke string". Dalam C ++ itu berarti menggunakan std::stringstreamsesuatu seperti:

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

Pilihan lainnya adalah snprintf:

double pi = 3.1415926;

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

Demo . Penanganan kesalahan harus ditambahkan, yaitu memeriksawritten < 0.

Columbo
sumber
Mengapa snprintflebih baik dalam kasus ini? Tolong jelaskan ... Ini pasti tidak akan lebih cepat, menghasilkan lebih sedikit kode [Saya telah menulis implementasi printf, itu cukup kompak hanya di bawah 2000 baris kode, tetapi dengan ekspansi makro mencapai sekitar 2500 baris]
Mats Petersson
1
@ MatPetersson Saya sangat percaya bahwa ini akan menjadi lebih cepat. Itulah alasan saya membuat jawaban ini sejak awal.
Columbo
@MatsPetersson Saya telah melakukan pengukuran (GCC 4.8.1, -O2) yang menunjukkan bahwa snprintf memang membutuhkan waktu lebih sedikit, dengan faktor 17/22. Saya akan segera mengupload kode.
Columbo
@MatsPetersson Tolok ukur saya mungkin cacat, tetapi versi stringstream membutuhkan waktu dua kali lebih lama dari versi snprintf pada 1000000 iterasi (Clang 3.7.0, libstdc ++, -O2).
Saya melakukan beberapa pengukuran juga - dan tampaknya (setidaknya di Linux) baik stringstream dan snprintf menggunakan __printf_fpfungsionalitas dasar yang sama - agak aneh bahwa proses ini memakan waktu lebih lama stringstream, karena seharusnya tidak perlu.
Mats Petersson
10

Anda bisa menggunakan C ++ 20 std::formatatau fmt::formatfungsi dari pustaka {fmt} , std::formatberdasarkan:

#include <fmt/core.h>

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

dimana 2presisi.

vitaut.dll
sumber
8

Di sini solusi hanya menggunakan std. Namun, perhatikan bahwa ini hanya membulatkan ke bawah.

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

Untuk roundeditu menghasilkan:

3.14

Kode tersebut mengubah seluruh float menjadi string, tetapi memotong semua karakter 2 karakter setelah "."

Sebastian R.
sumber
Hanya saja dengan cara ini Anda tidak benar-benar membulatkan angka.
ChrCury78
1
@ ChrCury78 seperti yang dinyatakan dalam jawaban saya ini hanya membulatkan ke bawah. Jika Anda ingin pembulatan normal cukup tambahkan 5 ke diget mengikuti nilai Anda. Misalnya number + 0.005dalam kasus saya.
Sebastian R.
3

Di sini saya memberikan contoh negatif di mana Anda ingin menghindari saat mengubah bilangan mengambang menjadi string.

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

Anda mendapatkan

99463 99.463 99.462997

Catatan: variabel num dapat berupa nilai apa pun yang mendekati 99,463, Anda akan mendapatkan hasil cetak yang sama. Intinya adalah untuk menghindari fungsi "to_string" c ++ 11 yang nyaman. Butuh beberapa saat untuk keluar dari jebakan ini. Cara terbaik adalah metode stringstream dan sprintf (bahasa C). C ++ 11 atau yang lebih baru harus memberikan parameter kedua sebagai jumlah digit setelah titik mengambang untuk ditampilkan. Saat ini defaultnya adalah 6. Saya mengemukakan hal ini agar orang lain tidak membuang waktu untuk masalah ini.

Saya menulis versi pertama saya, beri tahu saya jika Anda menemukan bug yang perlu diperbaiki. Anda dapat mengontrol perilaku persisnya dengan iomanipulator. Fungsi saya adalah untuk menunjukkan jumlah digit setelah koma desimal.

string ftos(float f, int nd) {
   ostringstream ostr;
   int tens = stoi("1" + string(nd, '0'));
   ostr << round(f*tens)/tens;
   return ostr.str();
}
Kemin Zhou
sumber