Converter float em string com precisão e número de dígitos decimais especificados?

88

Como você converte um float em uma string em C ++ enquanto especifica a precisão e o número de dígitos decimais?

Por exemplo: 3.14159265359 -> "3.14"

Shannon Matthews
fonte
por que não criar um flutuador temporário com a precisão especificada desejada e depois convertê-lo em string?
Gilad
@Gilad O 'temp float' não tem uma 'precisão especificada' e há casos em que tal abordagem falhará. Esta pergunta é basicamente como perguntar "o que é equivalente ao formato '% .2f'"?
user2864740
1
Consulte stackoverflow.com/questions/14432043/c-float-formatting se for apenas necessário para IO.
user2864740

Respostas:

131

Uma maneira típica seria 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();

Veja corrigido

Use notação de ponto flutuante fixo

Define o floatfieldsinalizador de formato para o str stream fixed.

Quando floatfieldé definido como fixed, os valores de ponto flutuante são escritos usando notação de ponto fixo: o valor é representado com exatamente tantos dígitos na parte decimal como especificado pelo campo de precisão ( precision) e sem parte expoente.

e definir precisão .


Para conversões de propósito técnico , como armazenamento de dados em arquivo XML ou JSON, C ++ 17 define a família to_chars de funções.

Supondo um compilador compatível (que não possuíamos no momento da escrita), algo como isto pode ser considerado:

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

O método usual para fazer esse tipo de coisa é "imprimir em string". Em C ++, isso significa usar std::stringstreamalgo como:

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

Outra opção é snprintf:

double pi = 3.1415926;

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

Demo . O tratamento de erros deve ser adicionado, ou seja, verificação dewritten < 0.

Columbo
fonte
Por que snprintfseria melhor neste caso? Por favor, explique ... Certamente não será mais rápido, produzirá menos código [eu escrevi uma implementação de printf, é bastante compacto em pouco menos de 2.000 linhas de código, mas com expansões de macro chega a cerca de 2500 linhas]
Mats Petersson
1
@MatsPetersson Eu acredito fortemente que será mais rápido. Essa é a razão pela qual eu fiz essa resposta em primeiro lugar.
Columbo
@MatsPetersson Eu executei medições (GCC 4.8.1, -O2) que indicam que snprintf está realmente levando menos tempo, pelo fator 17/22. Vou fazer o upload do código em breve.
Columbo,
@MatsPetersson Meu benchmark provavelmente tem falhas, mas a versão stringstream leva o dobro do tempo que a versão snprintf com 1.000.000 de iterações (Clang 3.7.0, libstdc ++, -O2).
Também fiz algumas medições - e parece (pelo menos no Linux) que stringstream e snprintf usam a mesma __printf_fpfuncionalidade subjacente - é bastante estranho que demore muito mais tempo stringstream, porque realmente não deveria.
Mats Petersson,
9

Você pode usar a fmt::formatfunção da biblioteca {fmt} :

#include <fmt/core.h>

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

onde 2está uma precisão.

Este recurso de formatação foi proposto para padronização em C ++: P0645 . Ambos P0645 e {fmt} usam uma sintaxe de string de formato semelhante a Python que é semelhante a de printf, mas usa {}como delimitadores em vez de %.

vitaut
fonte
7

Aqui, uma solução usando apenas std. No entanto, observe que isso apenas arredonda para baixo.

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

Para roundedisso rende:

3.14

O código converte todo o float em string, mas corta todos os caracteres 2 caracteres após o "."

Sebastian R.
fonte
A única coisa é que dessa forma você não está realmente arredondando o número.
ChrCury78
1
@ ChrCury78 conforme declarado em minha resposta, isso apenas arredonda para baixo. Se você quiser um arredondamento normal, basta adicionar 5 ao dígito após o seu valor. Por exemplo, number + 0.005no meu caso.
Sebastian R.
2

Aqui estou fornecendo um exemplo negativo onde você deseja evitar ao converter números flutuantes em strings.

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

Você consegue

99463 99.463 99.462997

Nota: a variável num pode ser qualquer valor próximo a 99,463, você obterá a mesma impressão. O objetivo é evitar a conveniente função c ++ 11 "to_string". Levei um tempo para sair dessa armadilha. A melhor maneira são os métodos stringstream e sprintf (linguagem C). C ++ 11 ou mais recente deve fornecer um segundo parâmetro como o número de dígitos após o ponto flutuante a ser mostrado. No momento, o padrão é 6. Estou postulando isso para que outros não percam tempo com esse assunto.

Eu escrevi minha primeira versão, por favor, deixe-me saber se você encontrar algum bug que precise ser corrigido. Você pode controlar o comportamento exato com o iomanipulator. Minha função é mostrar o número de dígitos após o ponto 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
fonte