What would Bob do?
You've written an interactive program, but the user does not know what your program does. Therefore, Bob—the generic user—might accidentally write "test" instead of a number, and suddenly your program runs wild.
A first step in the right direction would be to tell the user what your program will do—as long as its interactive. Note that this is not C++ specific.
We can do so by starting with:
cout << "This program converts a binary number to a decimal one\n"
"Please state the length of your number: ";
But that's error prone. First of all, Bob might not know how long the binary number is. Try to check whether 1101011010101101 is 14 or 15 characters long. That's a trick question, those are 16 characters.
So instead, just let Bob write anything and then check how long it is and whether it was really a binary number.
Prefer std::string to char[N]
Instead of char binary[i], we will use a std::string binary. This makes the input a lot easier. However, to handle the input easier, let's write a function. Actually, let's write two:
bool is_binary_number(const std::string& binary){
return std::all_of(binary.begin(), binary.end(), [](char c){
return c == '0' || c == '1';
});
}
That's using C++11's all_of in combination with a lambda. In case you don't know that yet, here's the same variant written in a range-based for loop:
bool is_binary_number(const std::string& binary){
for(char c : binary) {
if(c != '0' && c != '1') {
return false;
}
}
return true;
}
But this is C++11 syntax. In case you're not familar with C++11 at all, it's almost the same as
bool is_binary_number(const std::string& binary){
for(size_t i = 0; i < binary.size(); ++i) {
if(binary[i] != '0' && binary[i] != '1') {
return false;
}
}
return true;
}
In case you don't know std::string & yet, that's a reference. A reference is an alias to an already existing object. A const reference is an alias where we cannot change the value:
int value = 15;
int& ref = value;
const int& cref = value;
ref = 12;
std::cout << value << std::endl; // prints 12, since we changed it via ref
std::cout << cref << std::endl; // prints 12, since we changed it via ref
// does not compile:
cref = 7; // error; this reference is read only!
The details are a little bit more complicated, but in the end, when we call is_binary_number(on_a_string), we won't make a copy of on_a_string.
Either way, now that we have a function to check whether a string is a binary number, we can write our function to ask Bob for input:
std::string ask_binary_number() {
std::cout << "Please enter a binary number: ";
while(true) {
std::string input;
std::cin >> input;
if(is_binary_number(input)) {
return input;
} else {
std::cout << "That wasn't a binary number, try again: ";
}
}
}
We can now use this in main:
#include <iostream>
#include <string> // necessary for std::string
int main () {
std::string binary = ask_binary_number();
...
}
Note that we don't need to touch main at all if we want to change the input method later on. We only need to change ask_binary_number. This makes it easy to ask the user for another number, or re-use ask_binary_number in another project.
Exercise: Usually, you don't want leading zeroes in binary numbers. Which function do you have to change to not allow leading zeroes in the result of ask_binary_number? What changes are necessary?
Keep it simple and stupid
In your for loop, you do two things at once: you print the binary number and convert the number to a decimal. Unless you know that this is a bottleneck in your program and you need cache locality or something similar to keep your code fast, do one thing and one thing only. This makes it a lot easier to change the code later without breaking something else.
We can split this functionality again into two functions:
void print_binary_with_spaces(const std::string & binary, size_t digits = 4){
// exercise; almost solved by your own code
}
unsigned long binary_to_decimal(const std::string & binary){
// exercise; almost solved by your own code
}
If you implement all functions given above, you end up with the following main:
#include <iostream>
#include <string>
// functions here (either completely or only their declaration)
int main() {
std::cout << "This program converts a binary number to a decimal one." << std::endl;
std::string binary = ask_binary_number();
std::cout << "Your binary number ";
print_binary_with_spaces(binary);
std::cout << " is " << binary_to_decimal(binary) << "in decimal"
<< std::endl;
}
Note that all complex interactions are moved into functions. However, as you have seen in ask_binary_number and is_binary_number those functions aren't complex themselves either. Keeping things simple is a principle (KISS) that's often applied, as is splitting the functionality (separation of concerns).
A concrete example of overly complex code
Why am I pointing to simplicity? Because your while loop is overly complex:
i = i * 2;
j = 1;
while (i)
{
if (i > j)
{
... // lets call this A
++j;
}
else
{
... // lets call this B
}
--i;
}
Essentially, you have the following loops:
for(j = 0; j < i; ++j)
{
A; // see above
}
for(--i; i > 0; --i)
{
B; // see above
}
If we used that in your program, it would look like this:
for(int j = 0; j < i; ++j){
cin >> binary[j];
}
while(i-- > 0){
cout << binary[i];
if (i%4 == 1)
cout << " ";
if (binary[i] == '1')
decimal += powerOfTwo;
powerOfTwo *= 2;
}
This is not only easier to understand, it's even shorter. So try to keep your loops simple.
cin, cout, using namespace std;. \$\endgroup\$