Chuyển đổi float thành chuỗi với độ chính xác và số chữ số thập phân được chỉ định?


88

Làm cách nào để bạn chuyển đổi một số thực thành một chuỗi trong C ++ trong khi chỉ định độ chính xác và số lượng các chữ số thập phân?

Ví dụ: 3.14159265359 -> "3.14"


Tại sao không tạo một float tạm thời với độ chính xác được chỉ định mà bạn muốn và sau đó chuyển nó thành chuỗi?
Gilad

@Gilad 'Temp float' không có 'độ chính xác được chỉ định' và có những trường hợp cách tiếp cận như vậy sẽ bị hỏng. Câu hỏi này về cơ bản giống như hỏi "định dạng '% .2f' tương đương là gì"?
user2864740

1
Xem stackoverflow.com/questions/14432043/c-float-formatting nếu điều này chỉ cần cho IO.
user2864740

Câu trả lời:


131

Một cách điển hình sẽ là sử dụng stringstream:

#include <iomanip>
#include <sstream>

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

Xem đã sửa

Sử dụng ký hiệu dấu phẩy động cố định

Đặt floatfieldcờ định dạng cho luồng str thànhfixed .

Khi floatfieldđược đặt thành fixed, giá trị dấu phẩy động được viết bằng ký hiệu dấu chấm cố định: giá trị được biểu thị bằng chính xác bao nhiêu chữ số trong phần thập phân như được chỉ định bởi trường chính xác ( precision) và không có phần số mũ.

và thiết lập chính xác .


Đối với các chuyển đổi có mục đích kỹ thuật , như lưu trữ dữ liệu trong tệp XML hoặc JSON, C ++ 17 xác định họ hàm to_chars .

Giả sử một trình biên dịch tuân thủ (mà chúng tôi thiếu tại thời điểm viết bài), một cái gì đó như thế này có thể được coi là:

#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
}

28

Phương pháp thông thường để thực hiện loại điều này là "in thành chuỗi". Trong C ++, điều đó có nghĩa là sử dụng std::stringstreammột cái gì đó như:

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

12

Một lựa chọn khác là snprintf:

double pi = 3.1415926;

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

Bản trình diễn . Xử lý lỗi nên được thêm vào, tức là kiểm trawritten < 0.


Tại sao sẽ snprintftốt hơn trong trường hợp này? Vui lòng giải thích ... Nó chắc chắn sẽ không nhanh hơn, tạo ra ít mã hơn [Tôi đã viết một triển khai printf, nó khá nhỏ gọn chỉ dưới 2000 dòng mã, nhưng với việc mở rộng macro lên đến khoảng 2500 dòng]
Mats Petersson

1
@MatsPetersson Tôi thực sự tin rằng nó sẽ nhanh hơn. Đó là lý do tôi đưa ra câu trả lời này ngay từ đầu.
Columbo

@MatsPetersson Tôi đã thực hiện các phép đo (GCC 4.8.1, -O2) cho thấy rằng snprintf thực sự mất ít thời gian hơn, theo hệ số 17/22. Tôi sẽ tải lên mã trong thời gian ngắn.
Columbo

@MatsPetersson Điểm chuẩn của tôi có thể còn thiếu sót, nhưng phiên bản stringstream mất thời gian gấp đôi phiên bản snprintf ở 1000000 lần lặp (Clang 3.7.0, libstdc ++, -O2).

Tôi cũng đã thực hiện một số phép đo - và có vẻ như (ít nhất là trên Linux) rằng cả stringstream và snprintf đều sử dụng cùng một __printf_fpchức năng cơ bản - thật kỳ lạ là nó mất nhiều thời gian hơn stringstream, vì nó thực sự không nên.
Mats Petersson

9

Bạn có thể sử dụng fmt::formathàm từ thư viện {fmt} :

#include <fmt/core.h>

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

nơi 2là chính xác.

Cơ sở định dạng này đã được đề xuất để chuẩn hóa trong C ++: P0645 . Cả P0645 và {fmt} đều sử dụng cú pháp chuỗi định dạng giống Python tương tự như printf's nhưng sử dụng {}làm dấu phân tách thay vì %.


7

Đây là một giải pháp chỉ sử dụng std. Tuy nhiên, lưu ý rằng điều này chỉ làm tròn xuống.

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

roundednó mang lại:

3.14

Mã chuyển đổi toàn bộ số float thành chuỗi, nhưng cắt tất cả các ký tự 2 ký tự sau dấu "."


Chỉ có điều là theo cách này bạn không thực sự làm tròn số.
ChrCury78

1
@ ChrCury78 như đã nêu trong câu trả lời của tôi, điều này chỉ làm tròn. Nếu bạn muốn làm tròn thông thường, chỉ cần thêm 5 vào diget theo giá trị của bạn. Ví dụ number + 0.005trong trường hợp của tôi.
Sebastian R.

2

Ở đây tôi cung cấp một ví dụ phủ định mà bạn muốn tránh khi chuyển đổi số động thành chuỗi.

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

Bạn lấy

99463 99.463 99.462997

Lưu ý: biến num có thể là bất kỳ giá trị nào gần với 99,463, bạn sẽ nhận được bản in tương tự. Vấn đề là tránh dùng hàm "to_string" trong c ++ 11. Tôi đã mất một lúc để thoát ra khỏi cái bẫy này. Cách tốt nhất là phương thức stringstream và sprintf (ngôn ngữ C). C ++ 11 hoặc mới hơn phải cung cấp tham số thứ hai là số chữ số sau dấu phẩy động để hiển thị. Ngay bây giờ, mặc định là 6. Tôi đang đăng điều này để những người khác không mất thời gian cho chủ đề này.

Tôi đã viết phiên bản đầu tiên của mình, vui lòng cho tôi biết nếu bạn tìm thấy bất kỳ lỗi nào cần được sửa. Bạn có thể kiểm soát hành vi chính xác với iomanipulator. Chức năng của tôi là để hiển thị số chữ số sau dấu thập phân.

string ftos(float f, int nd) {
   ostringstream ostr;
   int tens = stoi("1" + string(nd, '0'));
   ostr << round(f*tens)/tens;
   return ostr.str();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.