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.
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();
std::string::c_str
?
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.
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();
}
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!
I have successfully incorporated the format functionality into my project, and it is very intuitive to use - just as a good method should be
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!