Convert float to string with variable precision

I’m assuming there must be a simple way to convert a float to a string without a bunch of trailing zeros, but I can’t figure it out. I looked at fV() that uses printf() formatting, but could not figure out the calling syntax.

Without having tried it, does printf with format %g do what you like?

If not, you could just make the string with trailing zeroes, and then hack characters off the end that are zeroes…?

Yeah - I got the code working by stripping trailing zeros, followed by stripping trailing decimal point. But I hate it.

If I were coding C I would have no problem with sprintf. But I cannot figure out the proper syntax with c++ and std::string. I think the VCV api function fV() is supposed to be used for this purpose, but I don’t know how.

I have a solution for this that I’ll post tomorrow when I get back to my desktop. Uses std::string with vsnprintf in simplistic way that restricts it to shortish strings, which is fine in the context of making rack ui.

1 Like

you can still use all the c functions if you want to - they are all there. C++ is strictly a superset of C. it’s got all the C library functions, including sprintf.

This is how I did this in my 1st module (that is to say this might not be the best solution, but it works):

    std::stringstream ss;
    ss << std::fixed << std::setprecision(3) << module->currentFrequency;
...
    ss.str();
2 Likes

std::string::c_str ?

1 Like

I use this, which will truncate at 512-characters, but that’s more than enough for my modules. You can easily write a version without the hard limit that calculates the length before reserving the string space, but that implementation calls vsnprintf twice for a function that is already slow. The Rack fV() function is implements the latter design.

std::string format_string(const char *fmt, ...)
{
    const int len = 512;
    std::string s(len, '\0');
    va_list args;
    va_start(args, fmt);
    auto r = std::vsnprintf(&s[0], len, fmt, args);
    va_end(args);
    if (r < 0) return "??";
    s.resize(std::min(r, len));
    return s;
}

For UI, I generally set the precision to 2 or 3. (“%.2f”). To remove trailing zeros, use g format.

Full reference for the format specifiers is: std::vprintf, std::vfprintf, std::vsprintf, std::vsnprintf - cppreference.com.

2 Likes

Are you in a situation where integers are likely to occur? I do this to print numbers when I want integers to be special:

std::string ShortPrint(float value) {
  if (Expression::is_zero(value - floor(value))) {
    return std::to_string((int) value);
  } else {
    return std::to_string(value);
  }
}

bool Expression::is_zero(float value) {
  return std::fabs(value) <= std::numeric_limits<float>::epsilon();
}
1 Like

i think in std c++11 or 17 dan.tilley answer is the most idiomatic, and pachde’s is very friendly to C devs - and a good way to implement snprintf like api on std::strings (although should that std::min be r+1 to account for the null terminator? I think the return value of vsnprintf is chars without the null).

in c++20 the language introduces std::format and its a way better API, just it requires c++20. But luckily the library has been ported to prior versions here. We adopted that a couple of years ago, use it for new code, and have been slowly back-porting old code to it. It’s faster and safer than sprintf and way less clumsy the stream api.

Hope that helps!

3 Likes

Love it - thanks!

Thanks @dan.tilley , @pachde for the other suggestions.

1 Like

I have successfully incorporated the format functionality into my project, and it is very intuitive to use - just as a good method should be :smiley:

I simply created a src/fmt directory in my project and copied all the format header files, and the license. I also copied the format.cc to the src directory and renamed it format.cpp. For any module that requires the format routines I simply include "fmt/core.h". I haven’t needed any of the other header files yet.

Here is a snippet that shows how easy it is to use the format routine. In this case I am printing out a float that automatically sets the number of decimal places appropriately, including no trailing zeros.

std::string getDisplayValueString() override {
  if (module->params[POI_UNIT_PARAM].getValue() == CENT_UNIT)
    return fmt::format("{:g} cents", getValue()*1200.f);
  // VOCT_UNIT
  return fmt::format("{:g} V", getValue());
}

Thanks again @baconpaul for directing me to this!

2 Likes