-1

So I am writing a program that takes input char by char and converts each char into a binary value. It then converts the binary to random letters, each letters ascii code value is even or odd, even representing 0's and odds representing 1's. My program fully works as intended EXCEPT with binary numbers that should have a 0 as the 1st digit.
For example.
"H" = "IVLSXDX" which converts to 1001000, the decimal value of "H".
Now the problem is with characters that have a preceding 0, such as:
"#" should convert to 100011, but in the end converts to 1000110, which is throwing off the value and turns "#" into "F".
Here is my code:

#include <stdio.h>
#include <stdlib.h>

/*Returns an even valued ascii uppercase letter. Random */
char genEven() {
    int value = (rand() % 13);
    char evens[13] = 
    {'B','D','F','H', 'J','L','N','P','R','T','V','X','Z'};
    return evens[value];
}

/** Returns an odd ascii uppercase letter. Random */
char genOdd() {
    int value = (rand() % 13);
    char odds[13] = 
    {'A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y'};
    return odds[value];
}

/* Given a decimal, returns 7 bit binary representation. */
int dToB(int n) {
    int remainder;
    int binary = 0; 
    int i = 1;
    while(n != 0) {
        remainder = n%2;
        n = n/2;
        binary= binary + (remainder*i);
        i = i*10;
    }
    return binary;
}

/*Reverses binary number for string conversion. */
int revBin(int n) {
    int remainder;
    int reversedNumber = 0;
    while(n != 0)
    {
        remainder = n%10;
        reversedNumber = reversedNumber*10 + remainder;
        n /= 10;
    }
    return reversedNumber;
}


/* Takes binary and converts to randomized letter combos. */
void bToS(int n) {
    int i = 7;
    int r = n % 10;
    char tmp;
    //FIX SO THAT IF A BINARY HAS A 0 in the highest order spot, print an even
    while(i > 0) {
        if(r == 0) {
        tmp = genEven();
            printf("(%c-%d)", tmp, tmp);
        } else {
        tmp = genOdd();
            printf("(%c-%d)",tmp,tmp);
        }
        n = n/10;
        r = n%10;
        i--;
    }
}

/* Discards to EOL. */
void discardToEOL() {
    char ch;
    do {
        scanf("%c", &ch);
    } while(ch != '\n');    
}

/* Encodes text.*/
int encode(void) {
    char ch;
    printf("? ");
    discardToEOL(); /*Discards any newlines from previous. */
    /*Get Input */
    do {
        scanf("%c", &ch);
    if (ch >= 0 && ch <= 128 && ch != 10) {
    printf("\nBinary rep of %c= %d\n", ch, dToB(ch));
    bToS(revBin(dToB(ch))); //Decimal to bin-> binary to string
    }
    } while(ch != '\n');    
    printf("\n");   
}

/** given a binary number, convert to decimal. */
int binToDec(int binary) 
{
    int decimal = 0, i = 1, remainder;
    /* Iterate until number becomes zero */
    while (binary != 0)
    {
        remainder = binary % 10;
        decimal = decimal + remainder * i;
        i = i * 2;
        binary = binary / 10;
    }
    printf("%c", decimal);
}

/* Decodes text.*/
int decode(void) {
    char ch;
    printf("? ");
    int i = 0;
    int bin = 0;
    discardToEOL(); /*Discards any newlines from previous. */
    /*Get Input */
    do {
        scanf("%c", &ch);
    if (ch >= 0 && ch <= 128 && ch != 10) {
        if(i <= 7) {
            if(ch % 2 == 0) {
                //Add 0 to end
                bin = bin * 10; //FIX
            } else {
                //Add 1 to end
                bin = (bin*10) +1; //FIX
            }
            i++;
        }
        if(i == 7) {
            //Send in for decimal conversion
            binToDec(bin);
            //Reset binary number
            bin = 0;
            i = 0;
        }
    }
    } while(ch != '\n');    
    printf("\n");
}

/* Main */
int main(void) {
  int c = 1;
  printf("C/c : Encode\nD/d : Decode\nQ/q : Quit\n");
  while(c) {
    printf("> "); 
    char choice;
    scanf(" %c", &choice);
    switch(choice) {
      case 'c':
        encode();
        break;
      case 'C':
        encode();
        break;
      case 'd':
        decode();
        break;
      case 'D':
        decode();
        break;
      case 'q':
        c = 0;
        break;
      case 'Q':
        c = 0;
        break;
      default:
        printf("Invalid Input.\n");  
        break;
    }
  }
}
Jacob
  • 113
  • 9
  • Why are you returning `int` for a `char` conversion to binary? (Also `uint8_t` would be a better type) Your `char` will have `8-bit` regardless of the letter it holds (the 6th bit being the *case bit* determining upper/lower case). Whether the 7th bit is populated or not is largely irrelevant. – David C. Rankin Jun 30 '17 at 02:46
  • Which return are you referring to – Jacob Jun 30 '17 at 02:48
  • Also I am unfamiliar with C, I am coming from Java. So any help would be appreciated – Jacob Jun 30 '17 at 02:48
  • When you reverse a number, how do you cope with trailing zeros? For example, 1011, 10110 and 101100 will all produce 1101 – samgak Jun 30 '17 at 02:49
  • `dToB` and `revBin`. You start with a `char` then convert to `int` which if stored as `little endian` will place the lower 8-bits first, etc.. – David C. Rankin Jun 30 '17 at 02:49
  • @samgak I don't, it ends up truncating it, so 010110 become 10110. – Jacob Jun 30 '17 at 02:51
  • It's not truncating, it's how you are creating the binary representation. If you want a padded binary number, you have to provide the padding. – David C. Rankin Jun 30 '17 at 02:52
  • @DavidC.Rankin So I am converting from one letter->to its binary value->then replacing all 1's with odd value ascii codes at random, and all 0s with even value ascii codes at random. So it returns a 7 digit char representing ti s binary value for encrypt. The decrypt part does the opposite obviously and takes those random letters, gets their even or odds, converts to binary, then to the decimal value, and displays as char. – Jacob Jun 30 '17 at 02:53
  • @DavidC.Rankin So does the uint8_t allow that padding? – Jacob Jun 30 '17 at 02:54
  • Here, it isn't for the padding, that is a number you control. I'll give a short example. – David C. Rankin Jun 30 '17 at 03:04
  • Ok, thank you that would be appreciated. Also, i checked the conversion with , and the binary value it gets is 1000000 which is accurate, but when I reverse the binary and send it to the function that returns the randomized string, the reversed binary comes out as just 0, so I think if i can somehow manipulate the revBin function to return 0000001, it would work properly – Jacob Jun 30 '17 at 03:07
  • But, the letters it returns for example for space are GPFHBJ, it is missing one zero at the end, which would bump it up to where it needs to be. – Jacob Jun 30 '17 at 03:08

1 Answers1

0

The problem you are having with padding your conversion is due to logic error. To provide padding for the conversion, you simply want to output 8-bits all the bits in your char. To output only 7-bits you output an unpadded representation of each character). It is up to you to account for each bit and each character you want to output.

By way of example, to output only a 7-bit representation of any character (0 <= c < 127), you can output an unpadded representation with the following:

/** unpadded binary representation of 'v'. */
void binprn (const uint64_t v)
{
    if (!v)  { putchar ('0'); return; };

    size_t sz = sizeof v * CHAR_BIT;
    uint64_t rem = 0;

    while (sz--)
        if ((rem = v >> sz))
            putchar ((rem & 1) ? '1' : '0');
}

If you want to pad to a specific number of bits, you must take that number of bits (whether 0 or 1), e.g.

/** binary representation of 'v' padded to 'sz' bits.
 *  the padding amount is limited to the number of
 *  bits in 'v'. valid range: 0 - sizeof v * CHAR_BIT.
 */
void binpad (const uint64_t v, size_t sz)
{
    if (!sz) { fprintf (stderr, "error: invalid sz.\n"); return; }
    if (!v)  { while (sz--) putchar ('0'); return; }

    if (sz > sizeof v * CHAR_BIT)
        sz = sizeof v * CHAR_BIT;

    while (sz--)
        putchar ((v >> sz & 1) ? '1' : '0');
}

(where CHAR_BIT is defined in <limits.h> (generally 8-bits per-char))

A short example would be:

#include <stdio.h>
#include <stdint.h>     /* for exact width types */
#include <limits.h>     /* for CHAR_BIT */

/** unpadded binary representation of 'v'. */
void binprn (const uint64_t v)
{
    if (!v)  { putchar ('0'); return; };

    size_t sz = sizeof v * CHAR_BIT;
    uint64_t rem = 0;

    while (sz--)
        if ((rem = v >> sz))
            putchar ((rem & 1) ? '1' : '0');
}

/** binary representation of 'v' padded to 'sz' bits.
 *  the padding amount is limited to the number of
 *  bits in 'v'. valid range: 0 - sizeof v * CHAR_BIT.
 */
void binpad (const uint64_t v, size_t sz)
{
    if (!sz) { fprintf (stderr, "error: invalid sz.\n"); return; }
    if (!v)  { while (sz--) putchar ('0'); return; }

    if (sz > sizeof v * CHAR_BIT)
        sz = sizeof v * CHAR_BIT;

    while (sz--)
        putchar ((v >> sz & 1) ? '1' : '0');
}

int main (void) {

    /* output binary `a` - `z` unpadded */
    for (int i = 'a'; i <= 'z'; i++) {
        printf (" %c - ", i);
        binprn (i);
        putchar ('\n');
    }

    putchar ('\n');

    /* output binary `a` - `z` padded to CHAR_BIT bits */
    for (int i = 'a'; i <= 'z'; i++) {
        printf (" %c - ", i);
        binpad (i, CHAR_BIT);
        putchar ('\n');
    }

    return 0;
}

Example Use/Output

$ ./bin/binchar
 a - 1100001
 b - 1100010
 c - 1100011
 d - 1100100
 <snip>
 x - 1111000
 y - 1111001
 z - 1111010

 a - 01100001
 b - 01100010
 c - 01100011
 d - 01100100
 <snip>
 x - 01111000
 y - 01111001
 z - 01111010

Now Turning to Your Actual Problems

You have other significant "irregularities" in your code that may cause you headaches. Parts of your code are doing what you want them to do, others are full of traps for the unwary. Let's start with discardToEOL. ch must be type int to detect EOF. Also, do not use scanf to get a single character (or int in this case), use getchar, e.g.

/* Discards to EOL. */
void discardToEOL ()
{
    int c;
    for (c = getchar (); c != '\n' && c != EOF; c = getchar()) {}
}

You have a similar problem with ch in decode, it should be an int which would allow simplified input with getchar. Additionally, you are not converting the ASCII character you read to a decimal value. Your logic for adding 0 or 1 to the end of bin is also flawed. You can rework it similar to:

/* Decodes binary text.*/
void decode (void)
{
    int ch, i = 0, bin = 0;

    discardToEOL ();    /* discards all chars in input buffer (stdin) */

    printf ("? ");

    for (;;) {
        /* loop reading ch until valid ch received, checking for EOF */
        while ((ch = getchar()) != '\n' && ch != EOF) {
            if (ch == '0' || ch == '1')
                break;
            fprintf (stderr, "error: invalid input.\n? ");
        }
        if (ch == EOF) {    /* alway check EOF */
            fprintf (stderr, "error: user canceled input.\n");
            exit (EXIT_SUCCESS);
        }
        if (ch == '\n')     /* if ch = '\n', your done */
            break;

        ch -= '0';  /* convert ASCII character to decimal value */

        if (i <= 7) {
            /* short form of what is commented below */
            bin = (ch & 1) ? (bin << 1) | 1 : bin << 1;
            // if (!(ch & 1))
            //     bin <<= 1;              /* add 0 to end */
            // else
            //     bin = (bin << 1) | 1;   /* add 1 to end */
            i++;
        }
        if (i == 7) {
            binToDec (bin); /* send in for decimal conversion */
            bin = i = 0;    /* reset bin and i */
        }
    }

    printf ("\n");
}

Next, the binToDec conversion is wrong. ("What did the duck say about those lines?"). Not that any conversion is required as you are passing an int anyway, but for the learning value of using the bits to arrive at an integer value, you multiply and shift, not modulo and divide, e.g. something similar to:

/** given a binary number, convert to decimal. */
void binToDec (int binary)
{
    int decimal = 0, i = 1;

    /* Iterate until number becomes zero */
    while (binary != 0) {
        decimal += (binary & 1) * i;    /* your conversion was wrong */
        i = i * 2;
        binary >>= 1;
    }
    printf ("%c [%d]", decimal, decimal);
}

Your encode largely works, although your lack of input validation will kill you. You can do something like the following to improve your chances:

/* Encodes text.*/
void encode (void)
{
    char ch;

    discardToEOL ();        /*Discards any newlines from previous. */

    printf ("? ");

    do {
        int retval;     /* always ALWAYS check the return of scanf */

        while ((retval = scanf ("%c", &ch)) != 1) { /*Get Input */
            if (retval == EOF) {    /* alway check EOF */
                fprintf (stderr, "error: user canceled input.\n");
                exit (EXIT_SUCCESS);
            }
            fprintf (stderr, "error: invalid input.\n? ");
        }
        if (ch >= 0 && ch != '\n') {
            /* tack your zero on here :) */
            printf ("\nBinary rep of %c= 0%d\n", ch, dToB (ch));
            bToS (revBin (dToB (ch)));  //Decimal to bin-> binary to string
        }
    } while (ch != '\n');
    printf ("\n");
}

(note: you can change scanf for getchar here as well (and again in main), but that is an exercise left to you)

The remaining functions are relatively unchanged. I would make a few tweaks to genEven/Odd to tidy them up as follows, e.g.

/*Returns an even valued ascii uppercase letter. Random */
char genEven ()
{
    char evens[] =
        { 'B', 'D', 'F', 'H', 'J', 'L', 'N', 'P', 'R', 'T', 'V', 'X', 'Z' };
    return evens[rand() % (sizeof evens / sizeof *evens)];
}

Your main needs work. From a basic standpoint start with main being a function of type int and it returns a value. See See What should main() return in C and C++?. You have the same lack of validation that will kill you. You can also tidy things up by converting the input character to lower-case (either using the tolower function in <ctype.h>, or, since this exercise is about understanding bits, by simply making sure bit-5 (the 6th bit) is 1, and if not setting it to 1. Putting those pieces together, you could do something like:

/* Main */
int main (void)
{
    int c = 1;

    printf ("C/c : Encode\nD/d : Decode\nQ/q : Quit\n");

    while (c) {
        char choice;
        int retval;

        printf ("> ");

        for (;;) {  /* loop until input conditions satisified */
            while ((retval = scanf (" %c", &choice)) != 1) {
                if (retval == EOF) {    /* check for manual EOF */
                    fprintf (stderr, "error: user canceled input.\n");
                    return 0;
                }
                discardToEOL ();    /* protect against multi char input */
                fprintf (stderr, "error: invalid input.\n> ");
            }
            if (((choice >> 5) & 1) == 0)   /* if lower-case bit not set */
                choice |= (1 << 5);         /* set lower-case bit */

            /* validate choice is 'c', 'd' or 'q' */
            if (choice == 'c' || choice == 'd' || choice == 'q')
                break;

            discardToEOL ();    /* protect against multi char input */
            fprintf (stderr, "error: invalid choice.\n> ");
        }

        switch (choice) {   /* handle choice */
            case 'c':
                encode ();
                break;
            case 'd':
                decode ();
                break;
            case 'q':
                c = 0;
                break;
            default:
                printf ("Invalid Input.\n");
                break;
        }
    }

    return 0;
}

If you tidy all things up, you can expect the following example input/output:

Example Use/Input

$ ./bin/cbd
C/c : Encode
D/d : Decode
Q/q : Quit
> c
? abcd

Binary rep of a= 01100001
(A-65)(S-83)(X-88)(J-74)(D-68)(D-68)(Y-89)(H-72)
Binary rep of b= 01100010
(C-67)(O-79)(F-70)(H-72)(B-66)(I-73)(Z-90)(D-68)
Binary rep of c= 01100011
(S-83)(U-85)(V-86)(X-88)(P-80)(Q-81)(G-71)(H-72)
Binary rep of d= 01100100
(G-71)(K-75)(F-70)(H-72)(U-85)(J-74)(T-84)(Z-90)
> d
? 1100001
a [97]
> d
? 1100011
c [99]
> foo
error: invalid choice.
> Q

There are many ways to do this and I have left you a bit to do. Go though things line-by-line, character-by-character of the code while talking to the duck. If you are still stumped, let me know and I'm happy to help you further.

David C. Rankin
  • 69,681
  • 6
  • 44
  • 72
  • I kind of see what's going on here, but it is way over my head on how I would implement this code into my own. Would I want to replace my dToB function with the binpad function that you provided and have it return instead of print? The second output is seems to be what I need as it has leading 0's – Jacob Jun 30 '17 at 03:22
  • Well, your code has a bit of misunderstanding on how to test `odd/even`. You test the `0-bit`, e.g `if (val & 1) {...it's odd...} else {...it's even...}`. Above each of the function just spits out the `0000000` or `00000000` (7 or 8 bits) of any character provided (or any number for that matter). You only care about characters, so the 7-bits of the ASCII values. Give it a go, I'm happy to help further, but I try to avoid doing all the work for you (I'm happy to help you along the way) – David C. Rankin Jun 30 '17 at 03:27
  • I will give you a hint. You can randomly change the character by toggling bits on/off. To do so, all you need to turn any bit on/off with the bit toggled `n` being between `0-7` is use an `XOR` (e.g. `newchar = char ^= (1 << n);`) (you must then check it results in a valid character, e.g. `a-z` or `A-Z`). You will love C, just *slow down*, take it bit-by-bit `:)` – David C. Rankin Jun 30 '17 at 03:35
  • "bit by bit" hahaha I appreciate the help. – Jacob Jun 30 '17 at 03:36
  • For learning, always compile with *warnings enabled*, e.g. `-Wall -Wextra` in your compile string and do not accept code until it compiles without warning. When you run into problems .... see [**How to debug small programs**](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) and talk to the duck... (really, the duck...) When that fails, edit your question and **add** the current code you are working on (don't **replace** the code in your original question -- answer before the change won't make sense), and I'm happy to help further. – David C. Rankin Jun 30 '17 at 03:39
  • @Jacob, I took pity, you needed an obvious bit of help with a number of points in your code. Learning C isn't a race. It takes a level of understanding that just takes time. There is no other language (aside from assembler) that gives you the flexibility and control that C does. That is largely due to it providing low-level access without any safety net. Learning C is learning how to code so you never rely on a safety net. Enjoy the learning process. Learning C will make you a better programmer, no matter what language you find yourself coding in. – David C. Rankin Jun 30 '17 at 07:12