This is my second C++ program. Again, I wrote it all by myself. I only started learning C++ yesterday.
The concept of the program is simple; it should read input from std::cin
and print the output to std::cout
.
There are two functions that implement the bulk of the logic: a function to interpret numerals from a given base to its numerical value, and a function to represent a given number in a given base.
The base must be between 2 and 36. The reason is simple: I represent the number as strings, and I follow the convention of hexadecimal. Hexadecimal uses Arabic digits 0-9 and Basic Latin Alphabet a-f. There are 26 letters in the alphabet; if I use all of them I can represent base-36 numerals. For higher bases more symbols would be needed, but there aren't any good extensions to this.
My script only processes integers; it supports negative numbers and does some input validation. I use the ASCII character code to get the numerical value directly by decreasing the character code according to its value, and the case of the input can be mixed.
I stop the execution of the script if any character is not in the base-36 character set, or it has the same or higher numerical value than the base. I didn't do overflow and underflow checking though; those are currently beyond me.
The logic to interpret numerals from base
is simple: initialize a variable n
to 0, for each digit in string, multiply n
by base
, add the numerical value of digit to n
and assign the result back to n
, done.
The logic to represent numbers is its direct opposite: keep doing integer division with number
as numerator and base
as denominator, get the remainder as digit, collect the string representation in reverse order, and assign the quotient to number
. Stop the loop when the number is 0.
The program should keep asking for commands and print the execution result to console, until the input equals 'q'
. The commands should be a space delimited string consisting of 3 fields: the first field is the number/numeral to process, which can be in any supported base; the third is the base to convert from/to, which must be in decimal; The second is the keyword, it must be either "to"
or "from"
.
The keyword determines which function should be called, if it is "to"
, the command means converting the number represented in decimal to the target base, else if it is "from"
, the command means to interpret the numeral in the given base.
I also wrote the function to split the string entirely by myself.
Code
#include <format>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
std::vector<std::string> split_string(std::string str, std::string delim) {
std::vector<std::string> fields;
int length = delim.length();
int pos = 0;
std::string field = "";
std::string buffer = "";
for (char& c : str) {
if (c == delim[pos]) {
buffer += c;
pos++;
}
else {
field += buffer + c;
buffer = "";
pos = 0;
}
if (pos == length) {
fields.push_back(field);
field = "";
buffer = "";
pos = 0;
}
}
if (field != "") {
fields.push_back(field);
}
return fields;
}
uint64_t interpret_base(std::string str, int base = 10) {
if (base < 2 or base > 36) {
throw std::invalid_argument(std::format("base {} is not between 2 and 36", base));
}
uint64_t number = 0;
for (char& c : str) {
int digit = c;
if (digit < 58) {
digit -= 48;
}
else if (digit < 91)
{
digit -= 55;
}
else {
digit -= 87;
}
if (digit < 0 or digit >= base) {
throw std::invalid_argument(std::format("input string is not a valid integer in base {}", base));
}
number = number * base + digit;
}
return number;
}
std::string base_repr(uint64_t number, int base) {
if (base < 2 or base > 36) {
throw std::invalid_argument(std::format("input base {} is between 2 to 36", base));
}
std::string repr = "";
int digit;
while (number) {
uint64_t quotient = number / base;
digit = number - quotient * base;
number = quotient;
if (digit < 10) {
digit += 48;
}
else {
digit += 87;
}
repr = char(digit) + repr;
}
return repr;
}
std::string execute_command(std::string command) {
std::vector<std::string> fields = split_string(command, " ");
int size = fields.size();
if (size != 3) {
throw std::invalid_argument("command must have three fields");
}
std::string option = fields[1];
int base = interpret_base(fields[2]);
std::string sign = "";
std::string numeral = fields[0];
char first = numeral[0];
if (first == '-' or first == '+') {
if (first == '-') {
sign = "-";
}
numeral = numeral.substr(1, numeral.size() - 1);
}
if (option == "from") {
return sign + std::to_string(interpret_base(numeral, base));
}
else if (option == "to") {
uint64_t number = interpret_base(numeral);
return sign + base_repr(number, base);
}
else {
throw std::invalid_argument("option is not supported");
}
}
int main() {
std::string input;
std::cout << "input a numeral, operation and base (use space as delimiter)\n";
while (true) {
std::cout << ">>> ";
std::getline(std::cin, input);
if (input == "q") {
break;
}
std::cout << execute_command(input) + '\n';
}
return 0;
}
Test
PS C:\Users\Xeni> C:\Users\Xeni\source\repos\number_converter\x64\Release\number_converter.exe
input a numeral, operation and base (use space as delimiter)
>>> -123456789 to 36
-21i3v9
>>> -21i3v9 from 36
-123456789
>>> stirlingite from 36
105370617716134466
>>> 105370617716134466 to 36
stirlingite
>>> 242516 to 36
574k
>>> 574k from 36
242516
>>> 75bcd15 from 16
123456789
>>> 123456789 to 16
75bcd15
>>> q
PS C:\Users\Xeni>
How can it be improved?
I now use unsigned 64-bit integer for maximum representable integer range, I just add the sign as a string. And this is as far as I can go.