__atoi64 not working correctly

J

Jonathan Burd

Greetings everyone,

Reading about the int64_t types added by C'99, I decided to implement
a 64-bit version of atoi for my own library. However, for
the test number provided, it isn't doing what it is supposed to do.
(I used GCC/MingW32 as the compiler with C'99 enabled. Oh, and there
is no overflow-checking in this code!) The code follows:

#include <ctype.h>

#ifdef __C99__
#include <stdint.h>

int64_t
__atoi64 (const char *numstr)
{
int64_t result = 0; /* stores the resulting number */
register int ch; /* current character */
int sign = '+'; /* sign of the number */

/* Try removing this and pass (const char*)NULL. ;) */
if (NULL == numstr)
return 0;

/* skip leading whitespace */
while (isspace(*numstr))
++numstr;

/* get the sign. incl. '+' because it is valid! */
sign = *numstr;
if ('+' == sign || '-' == sign)
++numstr;

/* shift digits to left by one place each time
* and stick the new digit in.
*/
while ( (ch = *numstr) && isdigit(ch) )
{
result *= 10;
result += ch - '0';
++numstr;
}

/* return with sign. */
return (('-' == sign) ? -result : result);
}
#endif /* not __C99__ */

#ifdef TEST
#include <stdio.h>
int64_t __atoi64 (const char *numstr);

int
main (int argc, char **argv)
{
const char *numstr = "18726761288";

/* Implementation-specific:
* LONG_LONG_MAX as in limits.h = 9223372036854775807LL
* typedef long long int64_t;
*/

#ifdef __C99__
printf ("%s: %lld\n", numstr, __atoi64 (numstr));
#endif /* not __C99__ */

return 0;
}
#endif /* not TEST */

I wonder why this isn't working. Any help will be appreciated.
Nap time. :)

Regards,
Jonathan.
 
J

jacob navia

Jonathan said:
Greetings everyone,

Reading about the int64_t types added by C'99, I decided to implement
a 64-bit version of atoi for my own library. However, for
the test number provided, it isn't doing what it is supposed to do.
(I used GCC/MingW32 as the compiler with C'99 enabled. Oh, and there
is no overflow-checking in this code!) The code follows:

#include <ctype.h>

#ifdef __C99__
#include <stdint.h>

int64_t
__atoi64 (const char *numstr)
{
int64_t result = 0; /* stores the resulting number */
register int ch; /* current character */
int sign = '+'; /* sign of the number */

/* Try removing this and pass (const char*)NULL. ;) */
if (NULL == numstr)
return 0;

/* skip leading whitespace */
while (isspace(*numstr))
++numstr;

/* get the sign. incl. '+' because it is valid! */
sign = *numstr;
if ('+' == sign || '-' == sign)
++numstr;

/* shift digits to left by one place each time
* and stick the new digit in.
*/
while ( (ch = *numstr) && isdigit(ch) )
{
result *= 10;
result += ch - '0';
++numstr;
}

/* return with sign. */
return (('-' == sign) ? -result : result);
}
#endif /* not __C99__ */

#ifdef TEST
#include <stdio.h>
int64_t __atoi64 (const char *numstr);

int
main (int argc, char **argv)
{
const char *numstr = "18726761288";

/* Implementation-specific:
* LONG_LONG_MAX as in limits.h = 9223372036854775807LL
* typedef long long int64_t;
*/

#ifdef __C99__
printf ("%s: %lld\n", numstr, __atoi64 (numstr));
#endif /* not __C99__ */

return 0;
}
#endif /* not TEST */

I wonder why this isn't working. Any help will be appreciated.
Nap time. :)

Regards,
Jonathan.

Your code works perfectly with lcc-win32.
I get:
18726761288: 18726761288

lccwin32: http://www.cs.virginia.edu/~lcc-win32
 
C

Chris Croughton

Intel's compiler and GCC give me this:

18726761288: 1546892104

Strange, gcc 3.0.4 (GNU/Linux / Duron 1200) and gcc 3.3.3 (Cygwin /
WinXP / P4) both give 18726761288: 18726761288. Could it be the library
not implementing the %lld correctly? What happens if you put the printf
as:

printf ("%s: %lld\n", numstr, __atoi64 (numstr) / 10);

You should get 1872676128 for the converted answer if your function is
working correctly.

(Incidentally, you need to #include <stddef.h> to get a definition of
NULL before your function uses it.)

Chris C
 
J

jacob navia

Jonathan said:
Intel's compiler and GCC give me this:

18726761288: 1546892104

Regards,
Jonathan.

This is very clear. If you make (int)18726761288LL you
obtain 1546892104.

Your code seems correct to me. The compilers or their
implementation of printf are buggy.
Note that the Intel compiler needs some extra switch to
get into "C99" mode. See the documentation.

If you are using gcc under windows it doesn't seem to work
with some C99 features.

If all else fails, download lcc-win32... It is working, at least
what this code is concerned.

jacob
 
J

Jonathan Burd

Strange, gcc 3.0.4 (GNU/Linux / Duron 1200) and gcc 3.3.3 (Cygwin /
WinXP / P4) both give 18726761288: 18726761288. Could it be the library
not implementing the %lld correctly? What happens if you put the printf
as:
I have ``gcc (GCC) 3.4.1 (mingw special)" running on Windows XP / P4.
printf ("%s: %lld\n", numstr, __atoi64 (numstr) / 10);
This prints ``18726761288: 1872676128". Funny.
The earlier code still prints: ``18726761288: 1546892104".

lcc-win32 produces the correct output: ``18726761288: 18726761288"
You should get 1872676128 for the converted answer if your function is
working correctly.

(Incidentally, you need to #include <stddef.h> to get a definition of
NULL before your function uses it.)

The code I presented is a snippet from a larger module.
The original module has stddef.h included.
However, I missed it when posting.

I'll try updating MinGW to the latest 3.4.2 and see what happens.

Regards,
Jonathan.
 
J

Jonathan Burd

jacob navia wrote:
This is very clear. If you make (int)18726761288LL you
obtain 1546892104.
Interesting.

Your code seems correct to me. The compilers or their
implementation of printf are buggy.
Note that the Intel compiler needs some extra switch to
get into "C99" mode. See the documentation.
<snip>

Yes, the switch is -Qc99 and I do have it enabled.
By the way, lcc-win32 gives the expected result.

Regards,
Jonathan.
 
C

Chris Croughton

I have ``gcc (GCC) 3.4.1 (mingw special)" running on Windows XP / P4.

This prints ``18726761288: 1872676128". Funny.
The earlier code still prints: ``18726761288: 1546892104".

That implies that somewhere, possibly in the library implementation of
printf, it is being truncated to a long (1872676128 will fit in a 32 bit
long, 1546892104 is 1872676128 truncated to 32 bits).

You are absolutely, positively certain that you are using &lld and not
&ld? And your function is actually being declared as returning long
long? No insult intended, but I've done sillier things myself and they
are surprisingly hard to spot...
lcc-win32 produces the correct output: ``18726761288: 18726761288"

As you do, so your function is fine it's the display of te value which
is odd.
The code I presented is a snippet from a larger module.
The original module has stddef.h included.
However, I missed it when posting.

No problem. Some systems seem to define NULL in all sorts of other
headers...
I'll try updating MinGW to the latest 3.4.2 and see what happens.

I hadn't realised 3.4.2 was out...

Chris C
 
J

Jonathan Burd

You are absolutely, positively certain that you are using &lld and not
&ld? And your function is actually being declared as returning long
long? No insult intended, but I've done sillier things myself and they
are surprisingly hard to spot...

Yes, I checked. And then, I rechecked just to make sure my eyes are
alright. I got a friend to see it too. He says he saw %lld and
"long long." j/k Heh.

I hadn't realised 3.4.2 was out...

Yes, it is out. A release candidate of the setup is available as of now.

Regards,
Jonathan.
 
J

Jonathan Burd

Jonathan said:
printf ("%s: %lld\n", numstr, __atoi64 (numstr));

And printf ("%s: %I64d\n", numstr, __atoi64 (numstr));
prints exactly what I wanted to see. Weird.

Regards,
Jonathan.
 
C

CBFalconer

Chris said:
.... snip ...

(Incidentally, you need to #include <stddef.h> to get a definition
of NULL before your function uses it.)

Also in stdio.h, stdlib.h, and time.h. Any one will do.
 
C

CBFalconer

Jonathan said:
Reading about the int64_t types added by C'99, I decided to implement
a 64-bit version of atoi for my own library. However, for
the test number provided, it isn't doing what it is supposed to do.
(I used GCC/MingW32 as the compiler with C'99 enabled. Oh, and there
is no overflow-checking in this code!) The code follows:

#include <ctype.h>

#ifdef __C99__
#include <stdint.h>

int64_t
__atoi64 (const char *numstr)
{
int64_t result = 0; /* stores the resulting number */
register int ch; /* current character */
int sign = '+'; /* sign of the number */

/* Try removing this and pass (const char*)NULL. ;) */
if (NULL == numstr)
return 0;

/* skip leading whitespace */
while (isspace(*numstr))
++numstr;

/* get the sign. incl. '+' because it is valid! */
sign = *numstr;
if ('+' == sign || '-' == sign)
++numstr;

/* shift digits to left by one place each time
* and stick the new digit in.
*/
while ( (ch = *numstr) && isdigit(ch) )
{
result *= 10;
result += ch - '0';
++numstr;
}

/* return with sign. */
return (('-' == sign) ? -result : result);
}
#endif /* not __C99__ */

#ifdef TEST
#include <stdio.h>
int64_t __atoi64 (const char *numstr);

int
main (int argc, char **argv)
{
const char *numstr = "18726761288";

/* Implementation-specific:
* LONG_LONG_MAX as in limits.h = 9223372036854775807LL
* typedef long long int64_t;
*/

#ifdef __C99__
printf ("%s: %lld\n", numstr, __atoi64 (numstr));
#endif /* not __C99__ */

return 0;
}
#endif /* not TEST */

I wonder why this isn't working. Any help will be appreciated.
Nap time. :)

I get:

[1] c:\c\junk>gcc -std=c99 -D TEST junk.c
junk.c:50: parse error before "__atoi64"
junk.c:50: warning: type defaults to `int' in declaration of
`__atoi64'
junk.c:50: warning: data definition has no type or storage class

Remember, providing int64_t is optional.
 
C

Chris Croughton

Yes, I checked. And then, I rechecked just to make sure my eyes are
alright. I got a friend to see it too. He says he saw %lld and
"long long." j/k Heh.

If I had a pound for every time I have rechecked my own code and not
spotted the obvious error <g>...

Library function error seems all that's left. Oh, you could try with
printf("%llX\n", 0x12345678901LL); and that sort of thing to see whether
that works. Also try something like printf("%lld %d\n", 12345LL, 999);
to see whether printf() is actually taking the long long argument and
converting it to a long or if it's taking the argument as a long in
which case the second field will be zero:

#include <stdio.h>
int main(void)
{
printf("%llX\n", 0x12345678901LL);
printf("%lld %d\n", 12345LL, 999);
printf("%ld %d\n", 12345LL, 999);
return 0;
}

12345678901
12345 999
12345 0
Yes, it is out. A release candidate of the setup is available as of now.

3.4.1 is available for Cygwin, but for some reason my upgrade didn't
seem to work...

Chris C
 
J

Jack Klein

Greetings everyone,

Reading about the int64_t types added by C'99, I decided to implement
a 64-bit version of atoi for my own library. However, for
the test number provided, it isn't doing what it is supposed to do.
(I used GCC/MingW32 as the compiler with C'99 enabled. Oh, and there
is no overflow-checking in this code!) The code follows:

#include <ctype.h>

#ifdef __C99__
#include <stdint.h>

int64_t
__atoi64 (const char *numstr)
{
int64_t result = 0; /* stores the resulting number */
register int ch; /* current character */
int sign = '+'; /* sign of the number */

/* Try removing this and pass (const char*)NULL. ;) */
if (NULL == numstr)
return 0;

/* skip leading whitespace */
while (isspace(*numstr))
++numstr;

/* get the sign. incl. '+' because it is valid! */
sign = *numstr;
if ('+' == sign || '-' == sign)
++numstr;

/* shift digits to left by one place each time
* and stick the new digit in.
*/
while ( (ch = *numstr) && isdigit(ch) )
{
result *= 10;
result += ch - '0';
++numstr;
}

/* return with sign. */
return (('-' == sign) ? -result : result);
}
#endif /* not __C99__ */

#ifdef TEST
#include <stdio.h>
int64_t __atoi64 (const char *numstr);

int
main (int argc, char **argv)
{
const char *numstr = "18726761288";

/* Implementation-specific:
* LONG_LONG_MAX as in limits.h = 9223372036854775807LL
* typedef long long int64_t;
*/

#ifdef __C99__
printf ("%s: %lld\n", numstr, __atoi64 (numstr));
#endif /* not __C99__ */

return 0;
}
#endif /* not TEST */

I wonder why this isn't working. Any help will be appreciated.
Nap time. :)

Regards,
Jonathan.

<off-topic>

mingw is a "typical" gcc distribution, as is available for many
platforms. It provides a tools like compiler and linker, and headers,
but no library. It uses the platform's standard run-time library, in
this case Microsoft's MSVC something or other DLL.

The *printf() implementation in Microsoft's C library merely ignores
the first 'l' in an "%lld" or other "%ll" conversion specifier, and
treats the argument as a long, not a 32-bit value.

You can verify this, if you have Visual C++, by building and running
the following program:

#include <stdio.h>


int main(void)
{
__int64 i64 = 187267612;
i64 *= 100;
i64 += 88;
printf("%lld\n", i64);
return 0;
}

The output will be the same that you received.

So if you want real long long support under Windows, you need an
implementation that provides its own library and supports it. These
include lcc-win32, Pelles C, and probably the gcc implementation that
runs under Cygwin, but I don't know this last one for sure.

</off-topic>
 
J

Jack Klein

Strange, gcc 3.0.4 (GNU/Linux / Duron 1200) and gcc 3.3.3 (Cygwin /
WinXP / P4) both give 18726761288: 18726761288. Could it be the library
not implementing the %lld correctly? What happens if you put the printf
as:

printf ("%s: %lld\n", numstr, __atoi64 (numstr) / 10);

You should get 1872676128 for the converted answer if your function is
working correctly.

(Incidentally, you need to #include <stddef.h> to get a definition of
NULL before your function uses it.)

Chris C

1. mingw and Intel's compiler use Microsoft's C library, and its
printf() implementation discards one of the 'l' characters in a
conversion specifier that starts with "%ll", perhaps in the assumption
that the programmer made a typographical error. In any case, trying
to convert a long long using a C90 library, and that's what it is, is
undefined.

2. Presumably he included <stdio.h> before calling printf(), and that
defines NULL.
 
J

Jack Klein

This is very clear. If you make (int)18726761288LL you
obtain 1546892104.

Your code seems correct to me. The compilers or their
implementation of printf are buggy.

Not buggy, mingw uses Microsoft's C library, the one provided in a DLL
and used by Windows system code. And it's not buggy, it's just a C90,
not a C99, library.
Note that the Intel compiler needs some extra switch to
get into "C99" mode. See the documentation.

If you are using gcc under windows it doesn't seem to work
with some C99 features.

I haven't used it in a while, but I seem to remember gcc running under
Cygwin works correctly. Again, the cygwin/gcc combination provides
its own libraries, instead of using Microsoft's.
 
C

Chris Croughton

<off-topic>

mingw is a "typical" gcc distribution, as is available for many
platforms. It provides a tools like compiler and linker, and headers,
but no library. It uses the platform's standard run-time library, in
this case Microsoft's MSVC something or other DLL.

Aha! I knew there must be something different...
The *printf() implementation in Microsoft's C library merely ignores
the first 'l' in an "%lld" or other "%ll" conversion specifier, and
treats the argument as a long, not a 32-bit value.

Why am I not surprised?
So if you want real long long support under Windows, you need an
implementation that provides its own library and supports it. These
include lcc-win32, Pelles C, and probably the gcc implementation that
runs under Cygwin, but I don't know this last one for sure.

I can say for sure (and did in an earlier message) -- it works fine. It
still isn't 100% compliant, though, as I recall it doesn't support %a or
the v (maxint) length modifier for integers. And some of the new macros
are missing from header files.
</off-topic>

As someone else said recently, this means that in practice what real C
programmers have to work to is the C89/90 standard, we can be pretty
sure in most cases that compilers will support that (there are a few
unfortunate programmers who still have to support things without
function prototypes). The C99 standard is largely irrelevant from the
point of view of writing portable code, and will be until the majority
of the C compilers available are C99 compliant (modulo bugs, of course).

For instance, I want to determine the 'best' floating point type for my
application (enough significant digits with the least storage to do
that) in the
preprocessor. Simple, right? Something like

#include <float.h>
#if FLT_MANT_DIG >= 10
typedef float my_float;
#if DBL_MANT_DIG >= 10
typedef double my_float;
#if LDBL_MANT_DIG >= 10
typedef long double my_float;
#else
# error No useful floating type!
#endif

Wrong! It's fine for C99, where the values are required to be constant
integer expressions, but C89 says:

Of the values in the <float.h> header, FLT_RADIX shall be a constant
expression suitable for use in #if preprocessing directives; all
other values need not be constant expressions. All except FLT_RADIX
and FLT_ROUNDS have separate names for all three floating-point
types. The floating-point model representation is provided for all
values except FLT_ROUNDS .

(para 2.2.4.1). So the only way to test them is to have a program which
includes float.h and then prints out a header file containing
appropriate definitions (for that and other reasons I have such a
program which calculates a load of stuff about the environment -- like
the number of bits in integer types -- and outputs a header file with
the information because things like sizeof aren't allowed in
preprocessor tests).

Chris C
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,525
Members
44,997
Latest member
mileyka

Latest Threads

Top