Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > Portability Problem; Minimal Example

Reply
Thread Tools

Portability Problem; Minimal Example

 
 
dgoodmaniii@gmail.com
Guest
Posts: n/a
 
      08-05-2010
Summary: code works correctly on x86_64; fails on i686;
both systems running the same kernel and the same operating
system. When running the code through valgrind, however, it
works on both systems. valgrind finds 0 errors in 0
contexts. A minimal example which reproduces this errors is
attached to the end.

To compile in all instances: gcc -lm -o doztest doztest.c

Long story: I'm writing a base-ten to base-twelve
converter, very simple, that works fine on my x86_64 box,
running Debian GNU/Linux (stable) on a 2.6.26 kernel. It
passed all my tests; in particularly, it correctly converted
this:

0.3333333333333333 --> 0;4000

When I cloned the repository and built it on my i686 box,
however, running the same Debian on the same kernel, this is
what came out:

0.3333333333333333 --> 0;3000
0.3333333333333 --> 0;3EEE

This is wrong, of course, but I can't figure out how. So I
ran the following:

valgrind -v --leak-check=full

followed by my program. It says there are 0 errors from 0
contexts; however, when run through valgrind the proper
answer comes out! That is, the code that produces this:

0.3333333333333333 --> 0;3000

when run by itself, produces this:

0.3333333333333333 --> 0;4000

when run through valgrind. I'm completely mystified. So I
isolated the code that seems to be causing the trouble,
which is the dectodoz function, which only calls two of my
own functions, and have posted it here as a minimal example.
This code reproduces the same strange results in all contexts.

>>>>>>>

#include<stdio.h>
#include<float.h>
#include<math.h>
#include<string.h>

void reverse(char *s)
{
int i, j;
char tmp;
size_t length;

length = strlen(s) - 1;
for (i=0, j=length; i<j; ++i, --j) {
tmp = *(s+i);
*(s+i) = *(s+j);
*(s+j) = tmp;
}
}

char dozenify(char num)
{
switch (num) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6:
case 7: case 8: case 9:
return (num % 10) + '0';
case 10:
return 'X';
case 11:
return 'E';
}
}

int dectodoz(char *doznum, double decnum)
{
int i = 0; int sign = 0; int j = 0;
double wholedec; /* whole number portion of decnum */
double partholder; /* someplace for modf to dump integral */

if (decnum < 0) {
decnum = -decnum;
sign = 1;
}
partholder = modf(decnum,&wholedec);
decnum -= wholedec;
while (wholedec >= 12) {
*(doznum+(i++)) = dozenify(fmod(wholedec,12.0));
wholedec /= 12;
}
*(doznum+(i++)) = dozenify(fmod(wholedec,12));
if (sign == 1)
*(doznum+(i++)) = '-';
*(doznum+i) = '\0';
reverse(doznum);
if (decnum > 0) {
*(doznum+(i++)) = ';';
for (i=i; i <= DBL_MAX_10_EXP; ++i) {
*(doznum+i) = dozenify((int)(decnum * 12));
decnum = modf(decnum*12,&partholder);
}
*(doznum+i) = '\0';
}
return 0;
}

int main(void)
{
char doznum[2000] = "";
double decnum = 0.33333333333333333333333333333333333;

dectodoz(doznum,decnum);
printf("%s\n",doznum);
return 0;
}
<<<<<<<
 
Reply With Quote
 
 
 
 
Seebs
Guest
Posts: n/a
 
      08-05-2010
On 2010-08-05, <> wrote:
> followed by my program. It says there are 0 errors from 0
> contexts; however, when run through valgrind the proper
> answer comes out! That is, the code that produces this:
>
> 0.3333333333333333 --> 0;3000


Wait, 3000 or 3EEE?

More interestingly...

> when run through valgrind. I'm completely mystified. So I
> isolated the code that seems to be causing the trouble,
> which is the dectodoz function, which only calls two of my
> own functions, and have posted it here as a minimal example.
> This code reproduces the same strange results in all contexts.


When I run this, I get:

0;400000000000000000000000000000000000000000000000 00000000000000000000
00000000000000000000000000000000000000000000000000 00000000000000000000
00000000000000000000000000000000000000000000000000 00000000000000000000
00000000000000000000000000000000000000000000000000 00000000000000000000
00000000000000000000000000000

(line breaks added).

This may have to do with the value of DBL_MAX_10_EXP being different
on different targets, but I remain a bit confused.

> void reverse(char *s)
> {
> int i, j;
> char tmp;
> size_t length;
>
> length = strlen(s) - 1;
> for (i=0, j=length; i<j; ++i, --j) {
> tmp = *(s+i);
> *(s+i) = *(s+j);
> *(s+j) = tmp;
> }
> }


This seems like it should be irrelevant, because after all, it wouldn't
be able to generate E's from whole cloth.

Let's remove it, for now, since it's irrelevant (we hope) in this case.

> char dozenify(char num)
> {
> switch (num) {
> case 0: case 1: case 2: case 3: case 4: case 5: case 6:
> case 7: case 8: case 9:
> return (num % 10) + '0';
> case 10:
> return 'X';
> case 11:
> return 'E';
> }
> }


It isn't obvious why you use 'char' num instead of, say, 'int' num here,
but looks harmless.

> int dectodoz(char *doznum, double decnum)
> {
> int i = 0; int sign = 0; int j = 0;
> double wholedec; /* whole number portion of decnum */
> double partholder; /* someplace for modf to dump integral */
>
> if (decnum < 0) {
> decnum = -decnum;
> sign = 1;
> }
> partholder = modf(decnum,&wholedec);
> decnum -= wholedec;


After you have done this, 'partholder' and 'decnum' are identical. I would
say drop one of them.

> while (wholedec >= 12) {
> *(doznum+(i++)) = dozenify(fmod(wholedec,12.0));
> wholedec /= 12;
> }


Okay, you're doing floating point math where you don't need to, but okay.

> *(doznum+(i++)) = dozenify(fmod(wholedec,12));


> if (sign == 1)
> *(doznum+(i++)) = '-';
> *(doznum+i) = '\0';


> reverse(doznum);


And we can remove this "reverse" and it shouldn't matter (for this specific
case) and further simplifies the code. And in any event, it doesn't seem
that this part is producing "wrong" results.

> if (decnum > 0) {
> *(doznum+(i++)) = ';';
> for (i=i; i <= DBL_MAX_10_EXP; ++i) {
> *(doznum+i) = dozenify((int)(decnum * 12));
> decnum = modf(decnum*12,&partholder);
> }
> *(doznum+i) = '\0';
> }
> return 0;
> }


Hmm.

Seems to me you could simplify this quite a bit. Let's assume we're not
concerned with the accuracy of the whole result, but merely with reproducing
the apparent bug.

int dectodoz(char *doznum, double decnum)
{
int i = 0;
double wholedec; /* whole number portion of decnum */

decnum = modf(decnum,&wholedec);
if (decnum > 0) {
*(doznum++) = ';';
for (i=0; i <= DBL_MAX_10_EXP; ++i) {
*(doznum++) = dozenify((int)(decnum * 12));
decnum = modf(decnum*12,&wholedec);
}
*(doznum++) = '\0';
}
return 0;
}

This seems as though it should do the same thing. We drop the unneeded
elaborate indexing into doznum. (Since it's a parameter, we're working
with a local copy of the pointer, so we can just iterate through it.)

This produces the same results, for me. We can even clean up that inner
loop a bit more:

decnum = modf(decnum * 12, &wholedec);
*(doznum++) = dozenify(wholedec);

And I still get the same results.

-s
--
Copyright 2010, all wrongs reversed. Peter Seebach / usenet-
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
 
Reply With Quote
 
 
 
 
Seebs
Guest
Posts: n/a
 
      08-05-2010
On 2010-08-05, Seebs <usenet-> wrote:
>> if (decnum > 0) {
>> *(doznum+(i++)) = ';';
>> for (i=i; i <= DBL_MAX_10_EXP; ++i) {
>> *(doznum+i) = dozenify((int)(decnum * 12));
>> decnum = modf(decnum*12,&partholder);
>> }
>> *(doznum+i) = '\0';


I messed around with this more.

for (i=0; i <= DBL_MAX_10_EXP; ++i) {
#ifdef HAX
printf("%f\n", decnum * 12);
#endif
*(doznum++) = dozenify((int)(decnum * 12));
decnum = modf(decnum*12,&wholedec);
if (decnum == 0)
break;
}

On several versions of gcc I've tried, compiling -DHAX produces
4.000000
;4
and compiling without -DHAX produces
;3

So I think this is a compiler bug.

By happy coincidence, it occurs in a compiler for which I have access to an
active support arrangement. I'm filing a bug report to see what obvious
explanation I missed.

-s
--
Copyright 2010, all wrongs reversed. Peter Seebach / usenet-
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
 
Reply With Quote
 
Seebs
Guest
Posts: n/a
 
      08-05-2010
On 2010-08-05, Seebs <usenet-> wrote:
> I messed around with this more.


And a bit more.

> for (i=0; i <= DBL_MAX_10_EXP; ++i) {
> #ifdef HAX
> printf("%f\n", decnum * 12);
> #endif
> *(doznum++) = dozenify((int)(decnum * 12));
> decnum = modf(decnum*12,&wholedec);
> if (decnum == 0)
> break;
> }


> On several versions of gcc I've tried, compiling -DHAX produces
> 4.000000
> ;4
> and compiling without -DHAX produces
> ;3


Changing "decnum * 12* to "decnum" in printf undoes this change.
Printing .30f, "decnum" is a bunch of threes and then some smaller
digits (which I'd expect), and "decnum * 12" is EXACTLY 4.0000.

Which I did not expect.

I think someone's doing a premature optimization.

-s
--
Copyright 2010, all wrongs reversed. Peter Seebach / usenet-
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
 
Reply With Quote
 
dgoodmaniii@gmail.com
Guest
Posts: n/a
 
      08-05-2010
Thanks for the responses.

>> for (i=0; i <= DBL_MAX_10_EXP; ++i) {
>> #ifdef HAX
>> printf("%f\n", decnum * 12);
>> #endif
>> *(doznum++) = dozenify((int)(decnum * 12));
>> decnum = modf(decnum*12,&wholedec);
>> if (decnum == 0)
>> break;
>> }

>
>> On several versions of gcc I've tried, compiling -DHAX produces
>> 4.000000
>> ;4
>> and compiling without -DHAX produces
>> ;3


gcc 4.3.2 (Debian 4.3.2-1.1), target i486-linux-gnu, doesn't
do this on the i686 machine. Compiling with -DHAX produces
4.000000
;3
This holds true whether I've got .30 in printf or not.

> I think someone's doing a premature optimization.


Do you mean me, or the compiler? I normally wouldn't dare
ask the question except that you've mentioned you think it
might be a compiler problem. Is there something I can do
about this? E.g., set a compiler flag, use a different
compiler? Or am I stuck for now running my program only on
an AMD64 machine?
 
Reply With Quote
 
Seebs
Guest
Posts: n/a
 
      08-05-2010
On 2010-08-05, <> wrote:
> gcc 4.3.2 (Debian 4.3.2-1.1), target i486-linux-gnu, doesn't
> do this on the i686 machine. Compiling with -DHAX produces
> 4.000000
> ;3
> This holds true whether I've got .30 in printf or not.


Interesting.

> Do you mean me, or the compiler? I normally wouldn't dare
> ask the question except that you've mentioned you think it
> might be a compiler problem. Is there something I can do
> about this? E.g., set a compiler flag, use a different
> compiler? Or am I stuck for now running my program only on
> an AMD64 machine?


Answer: This is well-known.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323

You should be able to make it go away with:

-msse -mfpmath=sse

because your problem is that the x87 FPU is annoying and quirky.

-s
--
Copyright 2010, all wrongs reversed. Peter Seebach / usenet-
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
 
Reply With Quote
 
James Dow Allen
Guest
Posts: n/a
 
      08-06-2010
On Aug 5, 12:16*pm, dgoodman...@gmail.com wrote:
> Summary: *code works correctly on x86_64; fails on i686;
> this:
> 0.3333333333333333 --> 0;4000
> ... [changed incorrectly to this]:
> 0.3333333333333333 --> 0;3000


What is happening, quite simply, is that you have
a double quite close in value to an integer, specifically
4.000000 in your case. Some library routines seem
to treat it as having integer part 4, but when you pass
it to dozenify(char num = ~ 4.000000) dozenify sees 3.
This is a commonish peril of floating point.

The question arises: Are your compiler and library
conforming to The Standard, or are they instead doing
that which they are not allowed to do? I don't know
the answer to that question.

I *do* know this problem would never arise the way I
program. On the precept that if you want to have a
calculation always give the same result, then do that
calculation only once, I write
int int_part = f;
when I need such an integer, and use int_part thereafter.

I'll admit I program this way as much to avoid confusing
*myself* as to avoid confusing compiler, but your
example suggests my way was right all along.

James Dow Allen
 
Reply With Quote
 
James Dow Allen
Guest
Posts: n/a
 
      08-06-2010
On Aug 6, 3:50*pm, James Dow Allen <jdallen2...@yahoo.com> wrote:
> ... if you want to have a
> calculation always give the same result, then do that
> calculation only once ...


BTW, here's a smaller program to exhibit the same "faulty"
behavior under gcc:

#include <stdio.h>

int main()
{
double x1, x2;

x1 = 0.33333333333333333333;
x2 = x1 * 3;
printf("%d %d\n", (int)(x1 * 3), (int)x2);
return 0;
}

James Dow Allen
 
Reply With Quote
 
dgoodmaniii@gmail.com
Guest
Posts: n/a
 
      08-08-2010
> Answer: This is well-known.
>
> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
>
> You should be able to make it go away with:
>
> -msse -mfpmath=sse
>
> because your problem is that the x87 FPU is annoying and quirky.


Wow. I've heard programmers complain about the chipset, but
I've never experienced the problem. I refused to even
consider that this might be a hardware problem; I'm shocked
that it was one.

This solution didn't work; but perusing the link provided, I
included:
-march=pentium4 -msse -mfpmath=sse
and all was well. The difference is that in x86_64
chipsets, I've learned, gcc compiles for this SSE FPU by
default, while in 32-bit mode gcc compiles for x87 by
default. It seems that all "newer" processors have this SSE
FPU (in 200, so it's probably safe. I'll have to include
a warning for those who try to use it with older x87 FPUs.

Thank you very much for puzzling this out for me. I'm
positive I never would have figured it out on my own.
 
Reply With Quote
 
Noob
Guest
Posts: n/a
 
      08-09-2010
dgoodmaniii wrote:

> -march=pentium4 -msse -mfpmath=sse
> and all was well. The difference is that in x86_64
> chipsets, I've learned, gcc compiles for this SSE FPU by
> default, while in 32-bit mode gcc compiles for x87 by
> default. It seems that all "newer" processors have this SSE
> FPU (in 200, so it's probably safe. I'll have to include
> a warning for those who try to use it with older x87 FPUs.


FYI

http://en.wikipedia.org/wiki/SSE2#No...upporting_SSE2
http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
std::list of class pointers, understanding problem (with minimal example) Frank Steinmetzger C++ 4 07-29-2010 04:12 PM
std::list of class pointers, understanding problem (with minimal example) Frank Steinmetzger C++ 0 07-28-2010 10:54 AM
Wanted: Help making minimal example of delegation work in ASP/C# Siegfried Heintze ASP .Net 0 11-25-2006 04:20 PM
'example.com' == 'example.com.' => false... is this intended? Sam Roberts Ruby 15 02-07-2005 04:36 PM
ASPNET_WP.EXE Hangs (W2K/IIS5/ASP.NET 1.1) With Minimal Load Ken Barrett ASP .Net 5 11-16-2003 08:03 PM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57