Search code examples
arrayscscanf64-bitlimit

Why does a high-value input prevent an array from using the actual input value in C?


I'm making a function that takes a value using scanf_s and converts that into a binary value. The function works perfectly... until I put in a really high value.

I'm also doing this on VS 2019 in x64 in C

And in case it matters, I'm using

main(int argc, char* argv[]) 

for the main function.

Since I'm not sure what on earth is happening, here's the whole code I guess.

BinaryGet()
{
// Declaring lots of stuff
int x, y, z, d, b, c;
int counter = 0;
int doubler = 1;
int getb;
int binarray[2000] = { 0 };

// I only have to change things to 1 now, am't I smart?
int binappend[2000] = { 0 };


// Get number
printf("Gimme a number\n");
scanf_s("%d", &getb);



// Because why not
printf("\n");

// Get the amount of binary places to be used (how many times getb divides by 2)
x = getb;
while (x > 1)
{
    d = x;
    counter += 1;

    // Tried x /= 2, gave me infinity loop ;(
    x = d / 2;
}


// Fill the array with binary values (i.e. 1, 2, 4, 8, 16, 32, etc)
for (b = 1; b <= counter; b++)
{
    binarray[b] = doubler * 2;

    doubler *= 2;

    
}


// Compare the value of getb to binary values, subtract and repeat until getb = 0)
c = getb;
for (y = counter; c >= 1; y--)
{   

    // Printing c at each subtraction

    
    printf("\n%d\n", c);

    // If the value of c (a temp variable) compares right to the binary value, subtract that binary value
    // and put a 1 in that spot in binappend, the 1 and 0 list
    if (c >= binarray[y])
    {

        c -= binarray[y];
        binappend[y] += 1;
    }


    // Prevents buffer under? runs
    if (y <= 0)
    {
        break;
    }
}

// Print the result
for (z = 0; z <= counter; z++)
{
    printf("%d", binappend[z]);
}
}

The problem is that when I put in the value 999999999999999999 (18 digits) it just prints 0 once and ends the function. The value of the digits doesn't matter though, 18 ones will have the same result.

However, when I put in 17 digits, it gives me this:

99999999999999999

// This is the input value after each subtraction
1569325055

495583231

495583231

227147775

92930047

25821183

25821183

9043967

655359

655359

655359

655359

131071

131071

131071

65535

32767

16383

8191

4095

2047

1023

511

255

127

63

31

15

7

3

1


// This is the binary
1111111111111111100100011011101

The binary value it gives me is 31 digits. I thought that it was weird that at 32, a convenient number, it gimps out, so I put in the value of the 32nd binary place minus 1 (2,147,483,647) and it worked. But adding 1 to that gives me 0.

Changing the type of array (unsigned int and long) didn't change this. Neither did changing the value in the brackets of the arrays. I tried searching to see if it's a limit of scanf_s, but found nothing.

I know for sure (I think) it's not the arrays, but probably something dumb I'm doing with the function. Can anyone help please? I'll give you a long-distance high five.


Solution

  • The problem is indeed related to the power-of-two size of the number you've noticed, but it's in this call:

    scanf_s("%d", &getb);
    

    The %d argument means it is reading into a signed integer, which on your platform is probably 32 bits, and since it's signed it means it can go up to 2³¹-1 in the positive direction.

    The conversion specifiers used by scanf() and related functions can accept larger sizes of data types though. For example %ld will accept a long int, and %lld will accept a long long int. Check the data type sizes for your platform, because a long int and an int might actually be the same size (32 bits) eg. on Windows.

    So if you use %lld instead, you should be able to read larger numbers, up to the range of a long long int, but make sure you change the target (getb) to match! Also if you're not interested in negative numbers, let the type system help you out and use an unsigned type: %llu for an unsigned long long.

    Some details:

    1. If scanf or its friends fail, the value in getb is indeterminate ie. uninitialised, and reading from it is undefined behaviour (UB). UB is an extremely common source of bugs in C, and you want to avoid it. Make sure your code only reads from getb if scanf tells you it worked.

    2. In fact, in general it is not possible to avoid UB with scanf unless you're in complete control of the input (eg. you wrote it out previously with some other, bug free, software). While you can check the return value of scanf and related functions (it will return the number of fields it converts), its behaviour is undefined if, say, a field is too large to fit into the data type you have for it.

    3. There's a lot more detail on scanf etc. here.

    4. To avoid problems with not knowing what size an int is, or if a long int is different on this platform or that, there is also the header stdint.h which defines integer types of a specific width eg. int64_t. These also have macros for use with scanf() like SCNd64. These are available from C99 onwards, but note that Windows' support of C99 in its compilers is incomplete and may not include this.

    5. Don't be so hard on yourself, you're not dumb, C is a hard language to master and doesn't follow modern idioms that have developed since it was first designed.