This routine is called a zillion times to create large csv files full of numbers. Is there a more efficient way to to this?
static std::string dbl2str(double d)
{
std::stringstream ss;
ss << std::fixed << std::setprecision(10) << d; //convert double to string w fixed notation, hi precision
std::string s = ss.str(); //output to std::string
s.erase(s.find_last_not_of('0') + 1, std::string::npos); //remove trailing 000s (123.1200 => 123.12, 123.000 => 123.)
return (s[s.size()-1] == '.') ? s.substr(0, s.size()-1) : s; //remove dangling decimal (123. => 123)
}
Before you start, check whether significant time is spent in this function. Do this by measuring, either with a profiler or otherwise. Knowing that you call it a zillion times is all very well, but if it turns out your program still only spends 1% of its time in this function, then nothing you do here can possibly improve your program's performance by more than 1%. If that were the case the answer to your question would be "for your purposes no, this function cannot be made significantly more efficient and you are wasting your time if you try".
First thing, avoid s.substr(0, s.size()-1). This copies most of the string and it makes your function ineligible for NRVO, so I think generally you'll get a copy on return. So the first change I'd make is to replace the last line with:
if(s[s.size()-1] == '.') {
s.erase(s.end()-1);
}
return s;
But if performance is a serious concern, then here's how I'd do it. I'm not promising that this is the fastest possible, but it avoids some issues with unnecessary allocations and copying. Any approach involving stringstream is going to require a copy from the stringstream to the result, so we want a more low-level operation, snprintf.
static std::string dbl2str(double d)
{
size_t len = std::snprintf(0, 0, "%.10f", d);
std::string s(len+1, 0);
// technically non-portable, see below
std::snprintf(&s[0], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
return s;
}
The second call to snprintf assumes that std::string uses contiguous storage. This is guaranteed in C++11. It is not guaranteed in C++03, but is true for all actively-maintained implementations of std::string known to the C++ committee. If performance really is important then I think it's reasonable to make that non-portable assumption, since writing directly into a string saves copying into a string later.
s.pop_back() is the C++11 way of saying s.erase(s.end()-1), and s.back() is s[s.size()-1]
For another possible improvement, you could get rid of the first call to snprintf and instead size your s to some value like std::numeric_limits<double>::max_exponent10 + 14 (basically, the length that -DBL_MAX needs). The trouble is that this allocates and zeros far more memory than is typically needed (322 bytes for an IEEE double). My intuition is that this will be slower than the first call to snprintf, not to mention wasteful of memory in the case where the string return value is kept hanging around for a while by the caller. But you can always test it.
Alternatively, std::max((int)std::log10(d), 0) + 14 computes a reasonably tight upper bound on the size needed, and might be quicker than snprintf can compute it exactly.
Finally, it may be that you can improve performance by changing the function interface. For example, instead of returning a new string you could perhaps append to a string passed in by the caller:
void append_dbl2str(std::string &s, double d) {
size_t len = std::snprintf(0, 0, "%.10f", d);
size_t oldsize = s.size();
s.resize(oldsize + len + 1);
// technically non-portable
std::snprintf(&s[oldsize], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
}
Then the caller can reserve() plenty of space, call your function several times (presumably with other string appends in between), and write the resulting block of data to the file all at once, without any memory allocation other than the reserve. "Plenty" doesn't have to be the whole file, it could be one line or "paragraph" at a time, but anything that avoids a zillion memory allocations is a potential performance boost.
Efficient in terms of speed or brevity?
char buf[64];
sprintf(buf, "%-.*G", 16, 1.0);
cout << buf << endl;
Displays "1". Formats up to significant 16 digits, with no trailing zeros, before reverting to scientific notation.
use snprintf and an array of char instead of stringstream and string
pass a pointer to char buffer to dbl2str into which it prints (in order to avoid the copy constructor of string called when returning). Assemble the string to be printed in a character buffer (or convert the char buffer when called to a string or add it to an existing string)
declare the function inline in a header file
#include <cstdio>
inline void dbl2str(char *buffer, int bufsize, double d)
{
/** the caller must make sure that there is enough memory allocated for buffer */
int len = snprintf(buffer, bufsize, "%lf", d);
/* len is the number of characters put into the buffer excluding the trailing \0
so buffer[len] is the \0 and buffer[len-1] is the last 'visible' character */
while (len >= 1 && buffer[len-1] == '0')
--len;
/* terminate the string where the last '0' character was or overwrite the existing
0 if there was no '0' */
buffer[len] = 0;
/* check for a trailing decimal point */
if (len >= 1 && buffer[len-1] == '.')
buffer[len-1] = 0;
}
Related
I played with the string function,i wrote the following one, obviously I set the first character in the ret string to be written in a place that is out of bounds, but instead of an exception, I get a string that has one extra place .
std::string StringManipulations::rev(std::string s)
{
std::string ret(s.size(), ' ');
for (int i = 0; i < s.size(); i++)
{
std::string ch;
ch.push_back(s[i]);
int place = s.size() -i;
ret.replace(place,1,ch);
}
return ret;
}
I write by mistake in a position that corresponds to a place that is one larger than the original string size that I assign at the beginning of the function.
Why don't we get an error ?
s = StringManipulations::rev("abcde");
std::cout << s.size();
std::cout << s;
output is : 6 _edcba
any help ?
solved: adding ch as a String adds a null terminator automatically, and by doing so we can get a new string with size+1.
C++ has a zero-overhead rule.
This means that no overhead, (like checking if an index is in-bounds) should be done unintentionally.
You don't get an exception because c++ simply doesn't verify if the index is valid.
For the extra character, this might have something to do with (regular) c strings.
In c, strings are arrays of type char (char*) without a defined size.
The end of a string is denoted with a null terminator.
C++ strings are backwards compatible, meaning that they have a null terminator too.
It's possible that you replaced the terminator with an other character but the next byte was also a zero meaning that you added one more char.
In addition to the information above about null terminators, another answer to your question is that the docs says it will only throw if the position is greater than the string size, rather than beyond the end of the string.
string replace api
Beside most common (format) function C++20 also comes with format_to_n that takes output iterator and count.
What I am looking for is the way to make sure that in case I ran out of space that my string is still zero terminated.
For example I want the following program to output 4 instead of 42.
#include<string>
#include<iostream>
#define FMT_HEADER_ONLY
#include <fmt/format.h>
void f(char* in){
fmt::format_to_n(in, 2,"{}{}", 42,'\0');
std::cout << in;
}
int main(){
char arr[]= "ABI";
f(arr);
}
Is this possible without me manually doing the comparison of number of written chars and max len I provided to function?
If you are wondering why I use '\0' as an argument:
I have no idea how to put terminating char in format string.
note: I know that for one argument I can specify max len with :. but I would like a solution that works for multiple arguments.
format_to_n returns a result. You can use that struct:
void f(char* in){
auto [out, size] = fmt::format_to_n(in, 2, "{}", 42);
*out = '\0';
std::cout << in;
}
Note that this might write "42\0" into in, so adjust your capacity as appropriate (2 for a buffer of size 3 is correct).
format_to_n returns a struct containing, among other things, the iterator past the last character written. So it's quite easy to simply check the difference between that iterator and the original iterator against the maximum number of characters, and insert a \0 where appropriate:
void f(char* in)
{
const max_chars = 2;
auto fmt_ret = fmt::format_to_n(in, max_chars,"{}", 42);
char *last = fmt_ret.out;
if(last - in == max_chars)
--last;
*last = '\0';
std::cout << in;
}
Note that this assumes that the array only holds exactly the number of characters (including the NUL terminator) as the number you attempted to pass to format_to_n. The above code will therefore overwrite the last character written with a NUL terminator, essentially doing further truncation.
If instead you pass to format_to_n the number of characters in the array - 1, then you can simply always write the NUL terminator to fmt_ret.out itself.
I was work with system, that read some symbols from the specific keyboard, pass it to the ATmega8, and then pass it to the display one-by-one(this step work correctly), but if I want to show all symbols array, I discovered that the dynamic array on first position save null, on the second save empty symbol, and the subsequent characters saved correctly. I don't see any mistakes in code, so I need a help.
This code function must return the 4-elements char array of symbols reading serial from the keyboard.
char* askPass(void){
int i;
char key;
#define PS 4
char* pass = (char*)calloc(PS, sizeof(char));
clear:
lcd_clear();
lcd_gotoxy(0, 0);
lcd_puts("Enter the password:");
lcd_gotoxy(0, 1);
lcd_puts(">>");
free(pass);
pass = (char*)calloc(0, sizeof(char));
for (i=0;i<PS;i++) pass[i] = '';
for (i=0; i<PS; i++) {
key = '-';
key = readKey();
lcd_gotoxy(3+i, 1);
if (key == 'C') {
goto clear;
} else if (key == '-'){
lcd_putchar('|');
delay_ms(10);
lcd_gotoxy(3+i, 1);
lcd_putchar(' ');
delay_ms(10);
lcd_gotoxy(3+i, 1);
i--;
} else {
pass = (char*)realloc(pass, i*sizeof(char));
*(pass+i) = key;
lcd_putchar(*(pass+i));
delay_ms(20);
}
}
/// there is an error:
/// serial input: 1234
/// lcd output: !* 34!
/// correct output: !1234!
lcd_gotoxy(0,2);
lcd_puts("!");
for (i=0; i<PS; i++) {
if (!(*(pass+i))) lcd_putchar('*');
else lcd_putchar(*(pass+i));
}
lcd_puts("!");
// end error block
return pass;
} // can't return correct array value
/*
All tests show this:
Serial input word: abcd
Output: !* cd!
Correct output: !abcd!
*/
You start my allocating 4 bytes with:
char* pass = (char*)calloc(PS, sizeof(char));
Then you call free(pass) before you even use the allocated data, only to then allocates zero bytes with:
pass = (char*)calloc(0, sizeof(char));
If makes no sense to allocate only to free without using the allocation, or to allocate zero bytes. The act of allocating zero bytes is undefined, but nothing good or useful will happen by attempting to write to such an allocation as you do.
Continuously reallocation to add one byte at a time is ill-advised also. It would be better to allocate a buffer of a reasonable length to start with and if you need to extend, extend by a number of characters in on chunk.
It is unclear in any case why you are dynamically allocating and reallocating to allow input of an arbitrary number of characters that you are not using. It would be simpler and more deterministic, to simply accept the four characters in a fixed length array and discard any extraneous input.
There is no such thing as an "empty character", calloc already initialised the allocation to zero (a nul character).
I am pretty certain this code has many other issues, but it is had to determine what you are trying to do in order to advise; you would well to use a debugger.
This routine is called a zillion times to create large csv files full of numbers. Is there a more efficient way to to this?
static std::string dbl2str(double d)
{
std::stringstream ss;
ss << std::fixed << std::setprecision(10) << d; //convert double to string w fixed notation, hi precision
std::string s = ss.str(); //output to std::string
s.erase(s.find_last_not_of('0') + 1, std::string::npos); //remove trailing 000s (123.1200 => 123.12, 123.000 => 123.)
return (s[s.size()-1] == '.') ? s.substr(0, s.size()-1) : s; //remove dangling decimal (123. => 123)
}
Before you start, check whether significant time is spent in this function. Do this by measuring, either with a profiler or otherwise. Knowing that you call it a zillion times is all very well, but if it turns out your program still only spends 1% of its time in this function, then nothing you do here can possibly improve your program's performance by more than 1%. If that were the case the answer to your question would be "for your purposes no, this function cannot be made significantly more efficient and you are wasting your time if you try".
First thing, avoid s.substr(0, s.size()-1). This copies most of the string and it makes your function ineligible for NRVO, so I think generally you'll get a copy on return. So the first change I'd make is to replace the last line with:
if(s[s.size()-1] == '.') {
s.erase(s.end()-1);
}
return s;
But if performance is a serious concern, then here's how I'd do it. I'm not promising that this is the fastest possible, but it avoids some issues with unnecessary allocations and copying. Any approach involving stringstream is going to require a copy from the stringstream to the result, so we want a more low-level operation, snprintf.
static std::string dbl2str(double d)
{
size_t len = std::snprintf(0, 0, "%.10f", d);
std::string s(len+1, 0);
// technically non-portable, see below
std::snprintf(&s[0], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
return s;
}
The second call to snprintf assumes that std::string uses contiguous storage. This is guaranteed in C++11. It is not guaranteed in C++03, but is true for all actively-maintained implementations of std::string known to the C++ committee. If performance really is important then I think it's reasonable to make that non-portable assumption, since writing directly into a string saves copying into a string later.
s.pop_back() is the C++11 way of saying s.erase(s.end()-1), and s.back() is s[s.size()-1]
For another possible improvement, you could get rid of the first call to snprintf and instead size your s to some value like std::numeric_limits<double>::max_exponent10 + 14 (basically, the length that -DBL_MAX needs). The trouble is that this allocates and zeros far more memory than is typically needed (322 bytes for an IEEE double). My intuition is that this will be slower than the first call to snprintf, not to mention wasteful of memory in the case where the string return value is kept hanging around for a while by the caller. But you can always test it.
Alternatively, std::max((int)std::log10(d), 0) + 14 computes a reasonably tight upper bound on the size needed, and might be quicker than snprintf can compute it exactly.
Finally, it may be that you can improve performance by changing the function interface. For example, instead of returning a new string you could perhaps append to a string passed in by the caller:
void append_dbl2str(std::string &s, double d) {
size_t len = std::snprintf(0, 0, "%.10f", d);
size_t oldsize = s.size();
s.resize(oldsize + len + 1);
// technically non-portable
std::snprintf(&s[oldsize], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
}
Then the caller can reserve() plenty of space, call your function several times (presumably with other string appends in between), and write the resulting block of data to the file all at once, without any memory allocation other than the reserve. "Plenty" doesn't have to be the whole file, it could be one line or "paragraph" at a time, but anything that avoids a zillion memory allocations is a potential performance boost.
Efficient in terms of speed or brevity?
char buf[64];
sprintf(buf, "%-.*G", 16, 1.0);
cout << buf << endl;
Displays "1". Formats up to significant 16 digits, with no trailing zeros, before reverting to scientific notation.
use snprintf and an array of char instead of stringstream and string
pass a pointer to char buffer to dbl2str into which it prints (in order to avoid the copy constructor of string called when returning). Assemble the string to be printed in a character buffer (or convert the char buffer when called to a string or add it to an existing string)
declare the function inline in a header file
#include <cstdio>
inline void dbl2str(char *buffer, int bufsize, double d)
{
/** the caller must make sure that there is enough memory allocated for buffer */
int len = snprintf(buffer, bufsize, "%lf", d);
/* len is the number of characters put into the buffer excluding the trailing \0
so buffer[len] is the \0 and buffer[len-1] is the last 'visible' character */
while (len >= 1 && buffer[len-1] == '0')
--len;
/* terminate the string where the last '0' character was or overwrite the existing
0 if there was no '0' */
buffer[len] = 0;
/* check for a trailing decimal point */
if (len >= 1 && buffer[len-1] == '.')
buffer[len-1] = 0;
}
I'm having a string is not null terminated error, though I'm not entirely sure why. The usage of std::string in the second part of the code is one of my attempt to fix this problem, although it still doesn't work.
My initial codes was just using the buffer and copy everything into client_id[]. The error than occurred. If the error is correct, that means I've got either client_ id OR theBuffer does not have a null terminator. I'm pretty sure client_id is fine, since I can see it in debug mode. Strange thing is buffer also has a null terminator. No idea what is wrong.
char * next_token1 = NULL;
char * theWholeMessage = &(inStream[3]);
theTarget = strtok_s(theWholeMessage, " ",&next_token1);
sendTalkPackets(next_token1, sizeof(next_token1) + 1, id_clientUse, (unsigned int)std::stoi(theTarget));
Inside sendTalkPackets is. I'm getting a string is not null terminated at the last line.
void ServerGame::sendTalkPackets(char * buffer, unsigned int buffersize, unsigned int theSender, unsigned int theReceiver)
{
std::string theMessage(buffer);
theMessage += "0";
const unsigned int packet_size = sizeof(Packet);
char packet_data[packet_size];
Packet packet;
packet.packet_type = TALK;
char client_id[MAX_MESSAGE_SIZE];
char theBuffer[MAX_MESSAGE_SIZE];
strcpy_s(theBuffer, theMessage.c_str());
//Quick hot fix for error "string not null terminated"
const char * test = theMessage.c_str();
sprintf_s(client_id, "User %s whispered: ", Usernames.find(theSender)->second.c_str());
printf("This is it %s ", buffer);
strcat_s(client_id, buffersize , theBuffer);
Methinks that problem lies in this line:
sendTalkPackets(next_token1, sizeof(next_token1) + 1, id_clientUse, (unsigned int)std::stoi(theTarget));
sizeof(next_token1)+1 will always gives 5 (on 32 bit platform) because it return size of pointer not size of char array.
One thing which could be causing this (or other problems): As
buffersize, you pass sizeof(next_token1) + 1. next_token1 is
a pointer, which will have a constant size of (typically) 4 or 8. You
almost certainly want strlen(next_token1) + 1. (Or maybe without the
+ 1; conventions for passing sizes like this generally only include
the '\0' if it is an output buffer. There are a couple of other
places where you're using sizeof, which may have similar problems.
But it would probably be better to redo the whole logic to use
std::string everywhere, rather than all of these C routines. No
worries about buffer sizes and '\0' terminators. (For protocol
buffers, I've also found std::vector<char> or std::vector<unsigned char>
quite useful. This was before the memory in std::string was
guaranteed to be contiguous, but even today, it seems to correspond more
closely to the abstraction I'm dealing with.)
You can't just do
std::string theMessage(buffer);
theMessage += "0";
This fails on two fronts:
The std::string constructor doesn't know where buffer ends, if buffer is not 0-terminated. So theMessage will potentially be garbage and include random stuff until some zero byte was found in the memory beyond the buffer.
Appending string "0" to theMessage doesn't help. What you want is to put a zero byte somewhere, not value 0x30 (which is the ascii code for displaying a zero).
The right way to approach this, is to poke a literal zero byte buffersize slots beyond the start of the buffer. You can't do that in buffer itself, because buffer may not be large enough to accomodate that extra zero byte. A possibility is:
char *newbuffer = malloc(buffersize + 1);
strncpy(newbuffer, buffer, buffersize);
newbuffer[buffersize] = 0; // literal zero value
Or you can construct a std::string, whichever you prefer.