¿Convertir flotante en cadena con precisión y número de dígitos decimales especificado?

88

¿Cómo se convierte un flotante en una cadena en C ++ mientras se especifica la precisión y el número de dígitos decimales?

Por ejemplo: 3.14159265359 -> "3.14"

Shannon Matthews
fuente
¿Por qué no crear un flotante temporal con la precisión especificada que desea y luego convertirlo en una cadena?
Gilad
@Gilad El 'flotante temporal' no tiene una 'precisión especificada' y hay casos en los que tal enfoque se romperá. Esta pregunta es básicamente como preguntar "¿cuál es el equivalente del formato '% .2f'"?
user2864740
1
Consulte stackoverflow.com/questions/14432043/c-float-formatting si esto es solo necesario para IO.
user2864740

Respuestas:

131

Una forma típica sería usar stringstream:

#include <iomanip>
#include <sstream>

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

Ver fijo

Usar notación de punto flotante fijo

Establece el floatfieldindicador de formato para la secuencia str en fixed.

Cuando floatfieldse establece en fixed, los valores de punto flotante se escriben usando notación de punto fijo: el valor se representa exactamente con tantos dígitos en la parte decimal como se especifica en el campo de precisión ( precision) y sin parte de exponente.

y setprecision .


Para conversiones de propósito técnico , como almacenar datos en archivos XML o JSON, C ++ 17 define la familia de funciones to_chars .

Suponiendo un compilador compatible (que carecemos en el momento de escribir este artículo), se puede considerar algo como esto:

#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
fuente
28

El método habitual para hacer este tipo de cosas es "imprimir en una cadena". En C ++ eso significa usar std::stringstreamalgo como:

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

Otra opcion es snprintf:

double pi = 3.1415926;

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

Demo . Se debe agregar el manejo de errores, es decir, verificarwritten < 0.

Columbo
fuente
¿Por qué sería snprintfmejor en este caso? Por favor explique ... Ciertamente no será más rápido, producirá menos código [He escrito una implementación de printf, es bastante compacta con poco menos de 2000 líneas de código, pero con macro expansiones llega a alrededor de 2500 líneas]
Mats Petersson
1
@MatsPetersson Creo firmemente que será más rápido. Esa es la razón por la que hice esta respuesta en primer lugar.
Columbo
@MatsPetersson He realizado mediciones (GCC 4.8.1, -O2) que indican que snprintf está tomando menos tiempo, por factor 17/22. Subiré el código en breve.
Columbo
@MatsPetersson Mi punto de referencia probablemente sea defectuoso, pero la versión de secuencia de cadenas tarda el doble que la versión de snprintf en 1000000 iteraciones (Clang 3.7.0, libstdc ++, -O2).
También hice algunas mediciones, y parece (al menos en Linux) que tanto stringstream como snprintf usan la misma __printf_fpfuncionalidad subyacente , es bastante extraño que demore mucho más tiempo stringstream, ya que realmente no debería hacerlo.
Mats Petersson
9

Puede usar la fmt::formatfunción de la biblioteca {fmt} :

#include <fmt/core.h>

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

donde 2es una precisión.

Esta función de formato se ha propuesto para la estandarización en C ++: P0645 . Tanto P0645 como {fmt} usan una sintaxis de cadena de formato similar a Python que es similar a printf's pero usa {}como delimitadores en lugar de %.

vitaut
fuente
7

Aquí una solución que usa solo std. Sin embargo, tenga en cuenta que esto solo se redondea a la baja.

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

Porque roundedrinde:

3.14

El código convierte todo el flotante en una cadena, pero corta todos los caracteres 2 caracteres después del "."

Sebastián R.
fuente
Lo único es que de esta manera no redondeará el número.
ChrCury78
1
@ ChrCury78 como se indica en mi respuesta, esto solo se redondea. Si desea un redondeo normal, simplemente agregue 5 al resumen que sigue a su valor. Por ejemplo number + 0.005en mi caso.
Sebastian R.
2

Aquí proporciono un ejemplo negativo en el que desea evitar al convertir un número flotante en cadenas.

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

Usted obtiene

99463 99.463 99.462997

Nota: la variable num puede ser cualquier valor cercano a 99.463, obtendrá la misma impresión. El punto es evitar la conveniente función de c ++ 11 "to_string". Me tomó un tiempo salir de esta trampa. La mejor forma son los métodos stringstream y sprintf (lenguaje C). C ++ 11 o posterior debe proporcionar un segundo parámetro como el número de dígitos después del punto flotante para mostrar. En este momento, el valor predeterminado es 6. Estoy planteando esto para que otros no pierdan el tiempo en este tema.

Escribí mi primera versión, avíseme si encuentra algún error que deba solucionarse. Puede controlar el comportamiento exacto con el iomanipulador. Mi función es para mostrar el número de dígitos después del punto decimal.

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
fuente