I have a routine that is called 10% of the time but accounts for 75% of the
runtime. I do 2 sprintf(s) in the routine that appear to be the major
bottleneck.
Basically, the routine receives a double, length of returned string,
implicit decimal point (int value) and a return array address. The routine
is to convert an explicit decimal (double) number to a string representation
of an implicit number.
i.e.
"123.456" => "0012345600" (return length of 10 & implicit decimal point of
5)
"1.23" => "12300" (return length of 5 & implicit decimal point of 4)
Here's the code:
+++++++++++++++ code ++++++++++++++
void MapExplDecToImplDec( double dS1,
char *szS2, int iS2Len, int iS2DecPlace)
{
double dDecPart,dIntPart;
int iNewDecLen;
int iPositiveFlag=1; /* set it on */
char szDecTemp[52];
if (dS1 != 0)
{
if (dS1 < 0)
{
iPositiveFlag = 0;
dS1 = (- dS1); /* remove -ve sign */
}
dIntPart = floor(dS1); /* only integer part */
dDecPart = dS1 - dIntPart; /* only decimal part */
/* Convert decimal part to a number-string, i.e. "0.123" */
sprintf( szDecTemp, "%*.*f",
iS2DecPlace,
iS2DecPlace,
dDecPart);
/* Do we have a positive or negative number? */
if (iPositiveFlag)
{
iNewDecLen = iS2Len - iS2DecPlace;
sprintf( szS2, "%0*.*g%s",
iNewDecLen,
iNewDecLen,
dIntPart,
(szDecTemp+2) ); /* skip over "0." */
}
else
{
/* Also subtract 1 for the "-" sign */
iNewDecLen = iS2Len - 1 - iS2DecPlace;
sprintf( szS2, "-%0*.*g%s",
iNewDecLen,
iNewDecLen,
dIntPart,
(szDecTemp+2) ); /* skip over "0." */
}
}
else
{
/* set the target to character '0'. */
memset(szS2, '0', iS2Len);
*(szS2+iS2Len) = '\0'; /* mark the null */
}
}
+++++++++++++++ end of code ++++++++++++++
So, does anybody have any idea how to speed this routine up? or combine the
sprintf? or use something other than sprintf?
Thanks
Roger
> So, does anybody have any idea how to speed this routine up? or combine the
> sprintf? or use something other than sprintf?
Roger...
You could try something like:
#include <stdio.h>
#include <stdlib.h>
void convert(double v,char *b,int bsz,int dec)
{ char *p = b + bsz;
int i;
long x;
for (i=0; i<dec; i++) v *= 10.0;
x = v;
*p-- = '\0';
do
{ *p-- = '0' + (x % 10);
} while (x /= 10);
while (p >= b) *p-- = '0';
}
int main(void)
{ double test = 123.456;
char buffer[1024] = "";
convert(test,buffer,10,5);
printf("'%s'\n",buffer);
return 0;
}
--
Morris Dovey
West Des Moines, Iowa USA
C links at http://www.iedu.com/c
void convert(double v,char *b,int bsz,int dec)
{ char *p = b + bsz;
long x = 1L;
while (dec--) x *= 10;
x = x * v + 0.5;
*p-- = '\0';
do
{ *p-- = '0' + (x % 10);
} while (x /= 10);
while (p >= b) *p-- = '0';
}
--
I forgot to mention that it needs to handle negative numbers and very, very
large numbers (21 digits). The double to int auto-conversion is not able to
go beyond 10 significant digits. Also, in some cases there will be a
requirement for major truncation. i.e. "1234567.01234" to "56701".
So I started with your idea and expanded it. The new code is at the bottom.
Will someone explain to why I get the following weird values for this code?
double d1, d2, d3;
d1 = 8.54 * 100;
printf("d1=%f\n", d1);
d2 = floor(d1);
printf("d2=%f\n", d2);
d3 = floor(d1 + 0.0000000000001);
printf("d3=%f\n", d3);
The output is:
d1=854.000000
d2=853.000000
d3=854.000000
Why should I have to add a very, very small decimal number to d1 to get
floor() to work properly?
later
Roger...
//+++++++++++++ code +++++++++++++++
void MapExplDecToImplDec( double dS1,
char *szS2, int iS2Len, int iS2DecPlace)
{
char *szEnd = szS2 + iS2Len;
long lIntPart;
int i, positiveFlag = 1;
double dS1;
if (dS1 != 0.0) /* is it zero? */
{
*szEnd-- = '\0';
if ( dS1 < 0 )
{
dS1 = ( - dS1);
positiveFlag = 0;
}
/* Shift the decimal point right and then drop the decimal part */
/* i.e. Shift 3 digits: "123.456" -> "123456" */
dS1 = floor((dS1 * pow(10,iS2DecPlace)) + 0.000001);
while (dS1 > 0)
{
lIntPart = fmod(dS1, 1000000000);
if (lIntPart != 0)
{
do
{
*szEnd-- = '0' + (lIntPart % 10);
} while ((lIntPart /= 10) && (szEnd >= szS2));
}
else
{
/* Super huge number (1230000000000000), put filler */
for (i=0;i<9;i++) *szEnd-- = '0';
}
dS1 = floor(dS1 / 1000000000);
}
while (szEnd >= szS2) *szEnd-- = '0';
if (positiveFlag != 1) *(szS2) = '-';
}
else
{
/* set the target to default value of zeroes. */
memset(szS2, '0', iS2Len);
*(szS2+iS2Len) = '\0'; /* mark the null */
}
}
//+++++++++++++ end of code +++++++++++++++
floor(853.99999999...) --> 853.0 [usual floating point problem]
If you're having performance problems, minimize calls to other
functions where possible. I continued to play with the code after
posting last night (knowing that you needed to handle negatives;
but not wanting to take on pro bono contracting work). Since
you've done most of the work at this point, I can happily post
what I'd written. The revised code handles negative values; and
might well handle the magnitudes you need if you can change the
long int values to long long ints.
The digits() function should probably be incorporated into the
convert function to save the call overhead. I think I managed to
minimize the floating-point operations and math library calls.
I filled the field with '*' rather than truncating the output
(I'm still trying to get my head out of FORTRAN IV). It should be
easy to remove that code and include another condition in the
do{}while loop to effect truncation.
Here's what I came up with (not rigorously tested):
#include <stdio.h>
int digits(long x)
{ int n = 0;
do
{ ++n;
} while (x /= 10);
return n;
}
void convert(double v,char *s,int sz,int dp)
{ char *p = s + sz;
long x = 1L;
int sign = v < 0.0;
if (sign) v = -v;
while (dp--) x *= 10;
x = x * v + 0.5;
*p-- = '\0';
if ((digits(x) + sign) > sz)
{ while (p >= s) *p-- = '*';
return;
}
else do
{ *p-- = '0' + (x % 10);
} while (x /= 10);
while (p >= s) *p-- = '0';
if (sign) *s = '-';
}
int main(void)
{ double test = -123.456;
char buffer[64];
I played with the code a bit more and think this might come
closer to what you're after (does negative values and truncates
when value is too large for field):
#include <stdio.h>
void convert(double v,char *s,int sz,int dp)
{ char *p = s + sz;
long long x = 1LL;
int sign = v < 0.0;
if (sign) v = -v;
while (dp--) x *= 10;
x = x * v + 0.5;
*p-- = '\0';
do
{ *p-- = '0' + (x % 10);
} while ((p >= s) && (x /= 10));
while (p >= s) *p-- = '0';
if (sign) *s = '-';
}
int main(void)
{ double test = -123456789012.345;
char buffer[64];
convert(test,buffer,25,5);
#include <stdio.h>
#include <math.h>
void convert(double v,char *s,int sz,int dp)
{ double x = 1.0;
char *p = s + sz;
int sign = v < 0.0;
if (sign) v = -v;
while (dp--) x *= 10.0;
x = floor(x * v + 0.5);
*p-- = '\0';
do
{ *p-- = '0' + (int) fmod(x,10.0);
} while ((p >= s) && (x = floor(x / 10.0)));
while (p >= s) *p-- = '0';
if (sign) *s = '-';
}
int main(void)
{ double test = -12345678901234.56;
char buffer[64];
convert(test,buffer,21,5);
later
Roger...
"Morris Dovey" <mrd...@iedu.com> wrote in message
news:3E7103E3...@iedu.com...
Just to finish off this thread, the fastest version appears to be this one
(without fmod() & floor()) on OS/390 (mainframe).
The difference in CPU time used by my application from my originally posted
subroutine to this version is roughly 49% faster (almost twice as fast).
What is funny is that only this one subroutine changed: 20 lines of code but
the entire application is roughly 3000 lines. Tuning definitely paid off in
this case.
Thanks for your ideas Morris.
later
Roger...
"Morris Dovey" <mrd...@iedu.com> wrote in message
news:3E703FF0...@iedu.com...
> The difference in CPU time used by my application from my
> originally posted subroutine to this version is roughly 49%
> faster (almost twice as fast). What is funny is that only this
> one subroutine changed: 20 lines of code but the entire
> application is roughly 3000 lines. Tuning definitely paid off
> in this case.
>
> Thanks for your ideas Morris.
You're welcome (this is the kind of stuff I enjoy). On the other
hand, /you/ were the one who asked the right questions and
followed through. Make sure your client understands this.
Best regards,