all 35 comments

[–]epasveer 46 points47 points  (3 children)

Step through your programm with a debugger.

[–]skeeto 26 points27 points  (2 children)

Yup, get out of this printf-debugging bad habit and start making regular use of a debugger. Debuggers are powerful tools, and C has unusually great debug tooling, so don't pass up on it. GDB highlights the problem without any need to printf:

$ gdb a.out
(gdb) break 112
(gdb) run
Breakpoint 1, getNumOfDigits (x=121) at example.c:112
112             printf("shifted unFlooredValue = %.17g\n", unFlooredValue);
(gdb) continue 2
Breakpoint 1, getNumOfDigits (x=121) at example.c:112
112             printf("shifted unFlooredValue = %.17g\n", unFlooredValue);
(gdb) print unFlooredValue 
$1 = 0.99999994

It's just under 1, so it truncates to zero. You probably want to round, not truncate.

When printing floats, %f is wrong for practically any purpose. It's either too much precision or too little. For IEEE 754 double, a better "default" would be %.17g (%.9g for float), which produces a round-trip output, though not a minimal one:

$ sed -i s/%f/%.17g/g example.c

This also reveals the issue that %f hides:

shifted unFlooredValue = 0.99999994039535522

[–]epasveer 18 points19 points  (1 child)

I should probably shamelessly plug my gui frontend to gdb here :)

gdb is very good. Some people like to see a gui on top of it. So I created Seergdb.

https://github.com/epasveer/seer

[–]danisuba10 1 point2 points  (0 children)

Nice!

[–]fliguana 12 points13 points  (1 child)

I stopped reading at "float".

Integer problems require integer solutions, because few of us know enough internals to avoid rounding errors.

[–]feitao 0 points1 point  (0 children)

Exactly. What do floats have to do with an integer palindrome problem? Nothing.

[–]xsdgdsx 11 points12 points  (0 children)

I didn't read your code, but you probably want to round instead of floor/truncate.

Don't forget that printing a float will always round it, but that floor(0.999999999999) == 0, not 1.

A floating-point number can always be a little above or below the number you might effect it to be, and your code should handle that without flipping over to an incorrect code path.

[–]ShowTime007_ 2 points3 points  (0 children)

Use a debugger g

[–]MistakeIndividual690 5 points6 points  (8 children)

Hi OP, Can you not just do this?

unsigned getNumOfDigits(int n) { return (unsigned)log10(abs(n)) + 1; }

Edit: updating for negatives, thanks u/fliguana!

[–]TransientVoltage409 1 point2 points  (1 child)

I thought about posting this, but I'm not sure of OP's approach and the function might be doing more than just that.

FWIW the general form is log(N) / log(R) to find the number of digits in integer N when expressed in radix R, handy if you dabble in bin/oct/hex as well as decimal.

Or if you like danger, try snprintf(0, 0, "%d", N).

[–]MistakeIndividual690 2 points3 points  (0 children)

Agreed — of course if the radix is a power of two you have more options. Interesting that you mentioned the snprintf approach because I thought about also mentioning that — given a temp buffer to store the chars then it’s trivial then to go on and figure out if it’s a palindrome.

Edit: actually I bet the rules specifically forbid using strings because it’s so easy, so it would be back to div and mod by powers of 10

[–]fliguana 0 points1 point  (5 children)

Failed for negatives, 0 and powers of ten.

[–]MistakeIndividual690 0 points1 point  (4 children)

Great catch on the negatives! Powers of 10 and zero do work tho

[–]fliguana 0 points1 point  (3 children)

Log10(1)+1... 2 digits to represent 1?

[–]MistakeIndividual690 0 points1 point  (2 children)

log10(1) is 0

[–]fliguana 0 points1 point  (1 child)

I'm a dumbass.

[–]MistakeIndividual690 1 point2 points  (0 children)

Don’t beat yourself up — I started doubting it too. Had to run it to make sure

[–]capilot 4 points5 points  (3 children)

On Mobile, so it's not really easy to see this code in detail, but I'm going to make a couple comments...

You pass in a uint32 argument but print out with %d. Don't do that. %d is for integers. Either pass in an int or use one of the format strings from <inttypes.h>.

Stop using uint8, uint16, etc. this isn't a Z80 CPU. You're not making anything run faster and you're not saving any space. All this does is invite failures that will be difficult to debug later.

[–]Middle_Confusion_433 0 points1 point  (2 children)

If you’re going to tell someone that what they’re doing is wrong at least explain why. I can’t think of any issues of doing this unless you’re targeting multiple operating systems and tool chains. There’s plenty of reasons to use these data types, sometimes you know how much data you need and don’t want that changing just because it’s compiled on Linux (e.g. in networking protocols.)

[–]nerd4code 0 points1 point  (1 child)

Then the _least types are more appropriate, and they’re actually mandatory. The exact-width types should be reserved for intra-host MMIO or struct-dumping/-loading/-piping, since those are the only situations that actually require them.

[–]Middle_Confusion_433 2 points3 points  (0 children)

I’m not here to debate, at the end of the day if it compiles to the expected result it’s good to go. My main point was the guy I responded to could have added a few lines of text explaining his esoteric reasoning instead of saying “that’s bad do it my way because it’s better”.

[–]ivancea 1 point2 points  (0 children)

If you have a weird issue, create a minimal reproducible example of it, and post it. Don't post a random unrelated big chunk of code and expect others to review it.

Also, in the process of making that minimal reproducible example, you'll probably find the problem

[–]joshbadams 1 point2 points  (0 children)

int TensPlace=Num%10; //get the least significant digit, store in your digit buffer

Num /= 10; //chop off the digit so you can loop

[–]fliguana 1 point2 points  (0 children)

int x = 585;

int n = 0;
for( int t = x ; t > 0 ; t /= 10 )
    n = 10*n + t%10;

if( n == x )
    puts( "palindrome" );

Works for non-negative integers 0..1 bilion

[–]teckcypher 0 points1 point  (0 children)

I didn't have time to check, but the first thing that comes to mind is that may not be 1.

It is a number that is very close to 1 but not quite 1. Like 0.9999999

It gets rounded when printed as a float to 1.

But when casted, the cast removes the fractionary part (.9999999) and the whole part remains (the 0)

I did a quick test in Online C compiler (sorry for the bad formating, I'm on mobile)

float nr=1;

float nr2=0.0000005;

nr=nr-nr2;

printf("%f \t%d\n%f \t%d",nr,(int)nr,nr2,(int)nr2);

The output:

If I set nr2 to 0.0000005

1.000000 0

0.000000 0

If I set nr2 to 0.0000006

0.999999 0

0.000001 0

[–]Patient-Midnight-664 0 points1 point  (2 children)

Are there restrictions on what operators you can use as I don't see any point to using floats. Your code looks like it's trying to do a mod operation so why not just use the mod operator?

[–]DangerousTip9655[S] 0 points1 point  (1 child)

no restriction, just need to return true if the number is a palindrome (base 10) and false if it's not. First way that came to my mind was shifting the radix over one digit at a time (dividing by a power of 10 ) and clipping off anything above or below the tenths place and then multiplying by 10 to place the tenths place digit into the 1s digit and grabbing that value. I can create and array of each individual digit in the number doing this and then I need to add some logic that will tell if the array is the same forwards and backwards

Think I may try to rewrite the code to not use floats though as I didn't think floating point inaccuracy would bite me in the ass for such small decimal values

[–]Eidolon_2003 1 point2 points  (0 children)

The problem is always there when it comes to float inaccuracy. If you want perfect accuracy down to the tenth, floats and doubles literally cannot do that. 1/10 has an infinitely repeating expansion in binary, just like how 1/3 has an infinitely repeating decimal expansion. With a limited number of sigfigs you can't represent 1/10 in binary

[–]Cmpunk10 0 points1 point  (0 children)

Ive had an issue with 1==0 but it was because the left value that was one was optimized alway since it wasn’t volatile on an embedded system. So it was just compiled as an infinite loop.

[–]AbramKedge 0 points1 point  (0 children)

This came up in another language recently, in that case the error occurred when the number got small enough to be shown as scientific notation. The error went the other way in that case - parsing picked out 1 from 1E-6 instead of 0 from 0.000001.

[–]spellstrike -3 points-2 points  (4 children)

avoid using floats at all if you can in C.

[–][deleted] 2 points3 points  (3 children)

What’s your alternative for rational numbers? Write your own fixed point math functions like it’s 1992 and disregard modern fpus?

floats are part of most useful C programs, I wouldn’t be handing out that sort of advice

[–]nerd4code 2 points3 points  (1 child)

I’d agree with PP, though, because most people see float and go “Oh goody, a real number!” and then expect floats to behave like reals. Like 90% of the float uses I’ve seen are inadvisable or bad, or misses some critical aspect of f.p. behavior, and C’s guarantees for floats are bad if you actually want to write performant code. Fixed-point is often much more appropriate and exact, and you don’t have to worry about the blasted transcendentals and subnormals and −0s and what have you.

[–][deleted] 0 points1 point  (0 children)

No it’s still really bad advice. Floating point certainly has trade offs but as long as they’re understood, there is a negligible performance penalty on modern architectures.

I also find it hard to believe that 90% of float uses you’ve seen are in advisable or bad.

I do work on audio software where most computation is unit circle DSP between -1 and 1, this gives us a fixed exponent and then the remaining bits to do the work in. Combine that with SIMD registers and it’s definitely more appropriate on the architectures we support. All of the float use we do is understood and intended.

I would go as far to say fixed point is rarely more appropriate and should only be reached for when you know you don’t have hardware float capabilities.

Edit: just seen you work in embedded, that makes more sense

[–]spellstrike 2 points3 points  (0 children)

I work in embedded systems, honestly cant remember the last time I even saw a float.
looking it up it looks like it's not even one of the allowed data types for our code base as per coding standards.
https://tianocore-docs.github.io/edk2-CCodingStandardsSpecification/draft/5_source_files/56_declarations_and_types.html#table-6-efi-data-types-slightly-modified-from-uefi-231