Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

sprintf on mainframe is too slow

75 views
Skip to first unread message

RogerLacroix

unread,
Mar 12, 2003, 12:22:31 AM3/12/03
to
Basically, that sums it up. I am working at a client site where they have
not upgraded their mainframe C compiler for quite some time.

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


Morris Dovey

unread,
Mar 12, 2003, 12:55:21 AM3/12/03
to
RogerLacroix wrote:
> Basically, that sums it up. I am working at a client site where they have
> not upgraded their mainframe C compiler for quite some time.
>
> 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:
>
<snipped>

> 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

Morris Dovey

unread,
Mar 12, 2003, 2:46:11 AM3/12/03
to
Or even:

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';
}

--

RogerLacroix

unread,
Mar 13, 2003, 1:21:10 AM3/13/03
to
Thanks.

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 +++++++++++++++

Morris Dovey

unread,
Mar 13, 2003, 2:21:32 AM3/13/03
to
RogerLacroix wrote:
> Thanks.
>
> 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?

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];

Morris Dovey

unread,
Mar 13, 2003, 3:23:12 AM3/13/03
to
Roger...

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);

Morris Dovey

unread,
Mar 13, 2003, 5:19:15 PM3/13/03
to
A version using floor() and fmod():

#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);

RogerLacroix

unread,
Mar 14, 2003, 11:32:07 PM3/14/03
to
Thanks. I didn't have a chance today to try it out. I will try on Monday.

later
Roger...

"Morris Dovey" <mrd...@iedu.com> wrote in message
news:3E7103E3...@iedu.com...

RogerLacroix

unread,
Mar 18, 2003, 8:24:33 PM3/18/03
to
Hi,

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...

Morris Dovey

unread,
Mar 18, 2003, 8:46:25 PM3/18/03
to
RogerLacroix wrote:

> 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,

0 new messages