27

My application needs to convert double values to char* to write to a pipe that accepts only characters. The usual ways of doing this are using the sprintf() function or using ostringstream from iomanip.h header file.

Turns out, both of these have really bad performance. And my application needs to do this conversion so often that it becomes the primary bottleneck.

Is there any other function I could use? What logic can I use to write an efficient conversion function? The only thing I have managed to come up with so far is to get each individual digit out using division and mod operations, and append these digits to a char* to get the entire double value. This doesn't seem like a good approach though, and will likely have bad performance itself.

Thanks in advance for your thoughts.

EDIT: There is some confusion over how the char* will be used. The char* will be an argument to the fwrite function which writes to a pipe.

arsaKasra
  • 179
  • 1
  • 3
  • 10
Shailesh Tainwala
  • 6,299
  • 12
  • 58
  • 69
  • 4
    What do you mean by "pipe accepts only characters"? Can you send/write bytes? Does the format in which data goes to this pipe matter (does it need to be human readable), or do you convert it back on other side? – dbrank0 May 25 '12 at 06:40
  • 2
    Read this: http://stackoverflow.com/questions/3173056/why-does-dtoa-c-contain-so-much-code – nhahtdh May 25 '12 at 06:42
  • why not bitcast to an int and encode that in hex and send that (then on the other side convert back) – ratchet freak Feb 28 '16 at 13:33

9 Answers9

19

If you want to print any number that double type can support, use whatever library out there to do the job. It saves your sanity: Why does "dtoa.c" contain so much code?

If you want to print a subset of numbers in double type. For example, up to 4 digits after decimal point, and not more than 5 digits before decimal point, then you can round the number and convert to int type, before printing it out using division and mod. I can confirm the performance of this method.


EDIT: If you original purpose is to send the data for communication, then sending the binary form of double will be the fastest and most accurate method (no possible loss of precision due to conversion). The way to do this is explained in other answers.

Community
  • 1
  • 1
nhahtdh
  • 55,989
  • 15
  • 126
  • 162
  • +1: if the OP knows anything about the range of floats present, scaled integer conversion is probably the best optimization. – wallyk May 25 '12 at 06:53
  • 7
    He wants to serialize the contents of double. There is no need to represent it. – RedX May 25 '12 at 06:57
10

You can use the std::ostream write and std::istream read methods with any data type you just have to reinterpret_cast the data as a char pointer:

double value = 5.0;
std::ostream os;
//...
os.write(reinterpret_cast<const char*>(&value), sizeof(value));
//..
std::istream is;
is.read(reinterpret_cast<char*>(&value), sizeof(value));
Casey
  • 10,297
  • 11
  • 59
  • 88
7

if you wanna read data byte by byte. use this technique

double dbl = 2222;
char* ptr = (char*)(&dbl);

this will return lowest byte of dbl. and ptr++ will refer 2nd byte of dbl.

Saad
  • 8,857
  • 2
  • 41
  • 51
  • 2
    It is not type casting. The question is about printing value of double. – nhahtdh May 25 '12 at 06:45
  • 1
    he needs to send it by characters, and i think this can be done by this as you have the bytes of double variable and can be combined at when all the bytes are received at 2nd end – Saad May 25 '12 at 06:47
  • You might be right. The question is not that clear about the purpose of the conversion. – nhahtdh May 25 '12 at 06:50
  • this should definitely work and be the fastest, the OP doesn't say that the pipe only accepts human readable numbers, but characters – stijn May 25 '12 at 06:54
  • 3
    This might be affected by little endian / big endian sensitivity, but I agree it's best to avoid conversion if possible. – Component 10 May 25 '12 at 07:12
4

Are you in control of both ends of the pipe? Are you just trying to tunnel doubles through or do you need a valid text representation of the double?

If you are just tunneling and the pipe is 8 bit clean then use one of the answers above.

If you need a string use one of the other answers above.

If your problem is that the pipe is only 7 bits wide, then convert to radix 64 on write and back again on read.

Community
  • 1
  • 1
Julian
  • 1,522
  • 11
  • 26
2

Some systems provide dtostre and dtostrf conversion functions - might be worth benchmarking. You can look at sprintf() source code (e.g. GNU version) for ideas, picking up the %e, %f and/or %g formatting as desired, avoiding the format string interpretation step and even making it inlinable, and performance-tune to taste - you might be able to remove special casing for NaN, infinity and other values if you know you don't have to handle them.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
2

The slow part of sprintf on your system may not necessarily be converting the double, but parsing the format string. That might be good news for you as that is something that you could optimize away.

Additionally, try hard to document all the knowledge you have about range, accuracy, and nature of the double values that you need to process, and use it to develop a special algorithm.

Assuming your inputs are never subnormal numbers, use a known fixed precision and accuracy, a relatively high performing result may look like this:

itoa((int)((f + 0.00001) * 10000))

However, the sprintf and ostream approaches you are already aware of are the only solutions that are completely general and portable.

Jirka Hanika
  • 13,301
  • 3
  • 46
  • 75
  • @nhahtdh - That's true. I'll delete my answer altogether unless I see question edits that would clarify the exact purpose of the output pipe. In the end dumping the floats in the binary might be an excellent solution (Casey's answer), or it might be unusable. – Jirka Hanika May 25 '12 at 07:11
  • Although not directly applicable, your answer gives me some leads on how to solve the problem. Thanks! – Shailesh Tainwala May 25 '12 at 08:28
2
/*

_ecvt_s Converts a double number to a string.

Syntax:

errno_t _ecvt_s( 
   char * _Buffer,
   size_t _SizeInBytes,
   double _Value,
   int _Count,
   int *_Dec,
   int *_Sign
);

[out] _Buffer
Filled with the pointer to the string of digits, the result of the conversion.

[in] _SizeInBytes
Size of the buffer in bytes.

[in] _Value
Number to be converted.

[in] _Count
Number of digits stored.

[out] _Dec
Stored decimal-point position.

[out] _Sign
Sign of the converted number.


*/


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

...
char *buf = (char*) malloc(_CVTBUFSIZE);
int decimal;
int sign;
int err;


err = _ecvt_s(buf, _CVTBUFSIZE, 1.2, 5, &decimal, &sign);

if (err != 0) {
// implement error handling
}
else printf("Converted value: %s\n", buf);

...

Hope this helps.

Sibren
  • 1,068
  • 11
  • 11
Dragos
  • 72
  • 2
  • 7
1

Using ftoawill be slightly better than sprintf as this is what it is using internally. See related question here Also look at how ftoa is implemented in your library source and see if you can improve on it for your specific scenarios.

It seems ftoa is not standard at all, my bad. Here's a discussion showing an implementation which claims to be far faster that sprintf. You'd need to profile both on your own target environments, and implement with double rather than float.

Community
  • 1
  • 1
SmacL
  • 22,555
  • 12
  • 95
  • 149
  • Is ftoa (for float) or dtoa (for double) in the specification in any version? – nhahtdh May 25 '12 at 06:54
  • After a quick check, apparently not! I'll do some rummaging around as I've certainly seen it when single stepping through some libraries. Possibly not as standard as I thought. – SmacL May 25 '12 at 07:09
0

Concerning dtostre, there is also an extended version dtostren0(https://github.com/63n0m3/Stdlib_extension), where programmer can decide for how many decimal places the returned number is displayed in human friendly way(without e+n), the way normal humans would write. It still depends on original dtostre. Disclosure: I am an author.

Gen0me
  • 115
  • 8