Why is this C code returning unexpected values?

217 Views Asked by At

I want to multiply every other digit by 2, starting with the number’s second-to-last digit, and then add those products’ digits together, but the first printed values seem completely nonsensical.

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

int len(long li);

int main(void)
{
    // Get credit card number
    long credit_card = 378282246310005;

    // Adding every second digit's double's digits, starting with the second to last
    int sum = 0;
    int digit_doubled;
    for (int i = 0; i < len(credit_card); i++)
    {
        if (i % 2 == 0)
        {
            continue;
        }
        digit_doubled = (int) (credit_card / pow(10, i)) % 10 * 2;
        printf("Digit doubled %i: %i\n", i, digit_doubled);
        for (int j = 0; j < len(digit_doubled); j++)
        {
            sum += (int) (digit_doubled / pow(10, j)) % 10;
        }
    }
}

int len(long li)
{
    if (li == 0)
    {
        return 1;
    }
    return floor(log10(li)) + 1;
}

I have tried modifying the expression to see what results I'd get. When I deleted the % 10 * 2 from the end of digit_doubled = (int) (credit_card / pow(10, i)) % 10 * 2;, I got results that indicate some kind of integer overflow, but I have absolutely no idea where it could be coming from since my program isn't really producing any high values anywhere.

3

There are 3 best solutions below

7
Allan Wind On BEST ANSWER

In the first loop when i=1 you cast a value greater than the maximum value that can be stored in an integer (2147483647) which cause it to be truncated and on my system go negative:

digit_doubled = (int) (credit_card / pow(10, i)) % 10 * 2; // =>
digit_doubled = (int) 37828224631000 % 10 * 2; / =>
digit_doubled = -1847312168 % 10 * 2; // =>
digit_doubled = -16;

I suggest you include stdint.h and use uint64_t instead of silently assume that a long is 64 bit. Alternatively, consider using a string as a credit number is an identifier not a number (even though it is written as one).

Mixing functions that operate on double for integer type values will open you up to floating point rounding errors. You could use uint64_t versions of these functions instead of double. Below I implemented a len() and my_pow10() functions for you. As the value of digit_doubled is at most 18 you can just inline that calculation instead of using a loop.

#include <stdio.h>
#include <stdint.h>

uint64_t my_pow10(uint64_t x) {
    if(x == 0) return 1;
    size_t v = 10;
    for(size_t i = 1; i < x; i++, v *= 10);
    return v;
}

size_t len(uint64_t v) {
    size_t i = 0;
    for(; v; i++, v /= 10);
    return i;
}

int main(void) {
    const uint64_t credit_card = 378282246310005;
    const uint8_t credit_card_len = len(credit_card);
    uint8_t sum = 0;
    for (uint8_t i = 1; i < credit_card_len; i += 2) {
        uint8_t digit_doubled = credit_card / my_pow10(i) % 10 * 2;
        printf("Digit doubled %hhu: %hhu\n", i, digit_doubled);
        sum += digit_doubled / 10 + digit_doubled % 10;
    }
    printf("sum = %u\n", sum);
}
0
Fe2O3 On

Having suggested using a LUT to deliver the single digit value required by the OP's question, here is a snippet of code to do just that:

unsigned long long copy = longValue; // perhaps correct on OP's compiler??

copy /= 10; // dispose of the checksum digit (rightmost)

// The following line depends on the OP's problem statement
// Is this digit odd (checksum digit being digit zero)
// or is this digit even? (checksum digit being disregarded.)
// Incl-/excl- next line to suit problem statement.
copy /= 10; // dispose of the rightmost "odd" digit

while( copy ) {
    int digit = copy % 10; // get "even" digit from the right end.

    // Crafted LUT to return correctly "doubled & folded" value of this digit.
    sum += "0246813579"[digit] - '0';

    copy /= 100; // "shift" value down by 100 (ie: 2 digits of value)
}

I leave it as an exercise for the OP to determine what characterises "even" and "odd" in the sequence of digits of a credit card "number". Does one count the checksum digit as '1' or not? For the OP to work out...

For fun, here's the loop after compaction.

for( /**/; copy; copy /= 100 )
    sum += "0246813579"[ copy % 10 ] - '0';

And, a more advanced version would simply calculate the desired value from the supplied value:

for( /**/; copy; copy /= 100 )
    sum += copy%5 * 2 + copy%10/5;

For those who (reasonably) question the validity of the formula above:

#include <stdio.h>

int main( void ) {
    for( int i = 0; i < 10; i++ ) {
        int j = 2 * i;

        printf( "Digit Value: %d ", i );
        printf( " x2 = %02d ", j );
        printf( "==> %d + %d ", j/10, j%10 );
        printf( "==> %d ", j/10 + j%10 );

        printf( "==== %d\n", i%5 * 2 + i%10/5 ); // <== Formula version
    }
    return 0;
}
Digit Value: 0  x2 = 00 ==> 0 + 0 ==> 0 ==== 0
Digit Value: 1  x2 = 02 ==> 0 + 2 ==> 2 ==== 2
Digit Value: 2  x2 = 04 ==> 0 + 4 ==> 4 ==== 4
Digit Value: 3  x2 = 06 ==> 0 + 6 ==> 6 ==== 6
Digit Value: 4  x2 = 08 ==> 0 + 8 ==> 8 ==== 8
Digit Value: 5  x2 = 10 ==> 1 + 0 ==> 1 ==== 1
Digit Value: 6  x2 = 12 ==> 1 + 2 ==> 3 ==== 3
Digit Value: 7  x2 = 14 ==> 1 + 4 ==> 5 ==== 5
Digit Value: 8  x2 = 16 ==> 1 + 6 ==> 7 ==== 7
Digit Value: 9  x2 = 18 ==> 1 + 8 ==> 9 ==== 9
0
TonyK On

You don't need floating-point arithmetic at all here, or logs or pows. You can use % 10 to extract the next digit, and / 10 to discard it. Like this:

#include <stdint.h>
#include <stdio.h>

int main(void)
{
    uint64_t credit_card = 378282246310005ULL;

    int sum = 0;
    while (credit_card != 0)
    {
        credit_card /= 10; // Discard rightmost digit
        int double_digit = 2 * (credit_card % 10);
        credit_card /= 10;
        sum += double_digit % 10;
        double_digit /= 10;
        sum += double_digit % 10;
    }
    printf ("%d\n", sum);
}