TypeScript Tutorial: Creating a Simple Interactive Number Converter

Converting numbers between different bases (decimal, binary, hexadecimal, etc.) is a fundamental concept in computer science. Whether you’re a beginner exploring the intricacies of data representation or an intermediate developer working with low-level systems, understanding number conversion is crucial. Manually converting numbers can be tedious and error-prone. This tutorial will guide you through creating a simple, interactive number converter using TypeScript, allowing you to easily convert between decimal, binary, hexadecimal, and octal formats. We’ll break down the concepts, provide clear code examples, and address common pitfalls to ensure you grasp the fundamentals.

Understanding Number Bases

Before diving into the code, let’s briefly review the number bases we’ll be working with:

  • Decimal (Base-10): This is the number system we use daily, with digits ranging from 0 to 9.
  • Binary (Base-2): Uses only two digits: 0 and 1. This is the foundation of digital systems.
  • Hexadecimal (Base-16): Uses digits 0-9 and letters A-F (representing 10-15). It’s often used to represent binary data in a more compact form.
  • Octal (Base-8): Uses digits 0-7. Historically used in computing, less common now than hex.

Each number base represents numbers using different powers of its base. For example, in decimal, the place values are powers of 10 (1, 10, 100, etc.). In binary, they are powers of 2 (1, 2, 4, 8, etc.).

Setting Up the Project

Let’s set up a basic TypeScript project. If you don’t have Node.js and npm (or yarn) installed, you’ll need to do that first. Then, create a new project directory and initialize it:

mkdir number-converter
cd number-converter
npm init -y

Next, install TypeScript:

npm install typescript --save-dev

Create a tsconfig.json file in your project root. This file configures the TypeScript compiler. A basic configuration looks like this:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

This configuration compiles TypeScript code to ES5 JavaScript, uses CommonJS modules, outputs the compiled files to a dist directory, enables strict type checking, and enables support for ES module interop.

Create a src directory and a file named converter.ts inside it. This is where we’ll write our code.

Implementing the Conversion Logic

We’ll create functions to convert between the different number bases. We will focus on decimal to binary, hexadecimal, and octal, and vice versa. Let’s start with decimal to other bases:


// Function to convert decimal to binary
function decimalToBinary(decimal: number): string {
  if (decimal === 0) {
    return "0";
  }

  let binary = "";
  while (decimal > 0) {
    binary = (decimal % 2) + binary;
    decimal = Math.floor(decimal / 2);
  }
  return binary;
}

// Function to convert decimal to hexadecimal
function decimalToHexadecimal(decimal: number): string {
  let hexadecimal = "";
  const hexChars = "0123456789ABCDEF";

  if (decimal === 0) {
    return "0";
  }

  while (decimal > 0) {
    hexadecimal = hexChars[decimal % 16] + hexadecimal;
    decimal = Math.floor(decimal / 16);
  }
  return hexadecimal;
}

// Function to convert decimal to octal
function decimalToOctal(decimal: number): string {
  let octal = "";

  if (decimal === 0) {
    return "0";
  }

  while (decimal > 0) {
    octal = (decimal % 8) + octal;
    decimal = Math.floor(decimal / 8);
  }
  return octal;
}

Now, let’s implement the conversion from other bases to decimal:


// Function to convert binary to decimal
function binaryToDecimal(binary: string): number {
  let decimal = 0;
  for (let i = 0; i < binary.length; i++) {
    if (binary[i] !== '0' && binary[i] !== '1') {
      throw new Error("Invalid binary string");
    }
    decimal = decimal * 2 + parseInt(binary[i], 10);
  }
  return decimal;
}

// Function to convert hexadecimal to decimal
function hexadecimalToDecimal(hexadecimal: string): number {
  let decimal = 0;
  const hexChars = {
    '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
    'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,
    'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15
  };

  for (let i = 0; i < hexadecimal.length; i++) {
    const char = hexadecimal[i];
    if (hexChars[char] === undefined) {
      throw new Error("Invalid hexadecimal string");
    }
    decimal = decimal * 16 + hexChars[char];
  }
  return decimal;
}

// Function to convert octal to decimal
function octalToDecimal(octal: string): number {
  let decimal = 0;
  for (let i = 0; i < octal.length; i++) {
    const digit = parseInt(octal[i], 10);
    if (isNaN(digit) || digit  7) {
      throw new Error("Invalid octal string");
    }
    decimal = decimal * 8 + digit;
  }
  return decimal;
}

Building the Interactive Interface (Basic Console Interaction)

For simplicity, we’ll create a basic console-based interface. We’ll prompt the user for input, determine the conversion type, and display the result.


import * as readline from 'readline';

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function main() {
  rl.question('Enter the number to convert: ', (inputNumber) => {
    rl.question('Enter the base of the input (decimal, binary, hexadecimal, octal): ', (inputBase) => {
      rl.question('Enter the target base (decimal, binary, hexadecimal, octal): ', (targetBase) => {
        try {
          let result: string | number | undefined;
          const num = inputNumber;

          switch (inputBase.toLowerCase()) {
            case 'decimal':
              switch (targetBase.toLowerCase()) {
                case 'binary':
                  result = decimalToBinary(parseInt(num, 10));
                  break;
                case 'hexadecimal':
                  result = decimalToHexadecimal(parseInt(num, 10));
                  break;
                case 'octal':
                  result = decimalToOctal(parseInt(num, 10));
                  break;
                default:
                  console.log('Invalid target base.');
                  break;
              }
              break;
            case 'binary':
              switch (targetBase.toLowerCase()) {
                case 'decimal':
                  result = binaryToDecimal(num);
                  break;
                default:
                  console.log('Invalid target base.');
                  break;
              }
              break;
            case 'hexadecimal':
              switch (targetBase.toLowerCase()) {
                case 'decimal':
                  result = hexadecimalToDecimal(num);
                  break;
                default:
                  console.log('Invalid target base.');
                  break;
              }
              break;
            case 'octal':
              switch (targetBase.toLowerCase()) {
                case 'decimal':
                  result = octalToDecimal(num);
                  break;
                default:
                  console.log('Invalid target base.');
                  break;
              }
              break;
            default:
              console.log('Invalid input base.');
              break;
          }

          if (result !== undefined) {
            console.log('Result: ', result);
          }
        } catch (error: any) {
          console.error('Error during conversion:', error.message);
        }
        rl.close();
      });
    });
  });
}

main();

This code does the following:

  • Imports the readline module to handle console input.
  • Creates a readline interface.
  • Prompts the user for the number, input base, and target base.
  • Uses a switch statement to determine the conversion logic based on the input and target bases.
  • Calls the appropriate conversion functions.
  • Displays the result or an error message.

To run this code, compile it using the TypeScript compiler:

tsc

Then, execute the compiled JavaScript file using Node.js:

node dist/converter.js

Adding Error Handling

Robust error handling is essential. The provided code includes basic error handling for invalid input strings (e.g., non-binary characters in a binary string). However, you can enhance this further:

  • Input Validation: Validate user input to ensure it’s in the correct format (e.g., only numbers for decimal input, valid hex characters).
  • Range Checks: Ensure the input number is within a reasonable range to prevent potential overflow issues.
  • Specific Error Messages: Provide more descriptive error messages to guide the user.

Here’s an example of improved input validation within the main function:


// Inside the main function, before conversion logic:
if (isNaN(Number(inputNumber))) {
  console.error('Error: Invalid input number. Please enter a valid number.');
  rl.close();
  return;
}

Enhancements and Features

Here are some ways to enhance the number converter:

  • GUI Interface: Create a graphical user interface (GUI) using a framework like React, Angular, or Vue.js for a more user-friendly experience. This would involve HTML, CSS, and JavaScript, and would allow for a more interactive design.
  • Real-time Conversion: Implement real-time conversion, where the output updates dynamically as the user types in the input field.
  • History: Store the conversion history, allowing the user to view and reuse previous conversions.
  • More Bases: Add support for other number bases, such as base-3 or base-5.
  • Unit Testing: Write unit tests to ensure the correctness of your conversion functions. This is crucial for maintaining code quality as the project grows.
  • Input Field Validation: Implement real-time validation of input fields to prevent invalid characters or formats.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid or fix them:

  • Incorrect Base Conversion Logic: Double-check your formulas and algorithms for converting between number bases. Test them thoroughly with different inputs.
  • Type Errors: TypeScript helps prevent type-related errors, but ensure your code is type-safe. Use type annotations and interfaces to define your data structures.
  • Input Handling Issues: Handle user input carefully. Validate the input to prevent unexpected behavior. Use `parseInt()` with the correct base (e.g., `parseInt(input, 10)` for decimal).
  • Scope Issues: Be mindful of variable scope. Declare variables within the appropriate scope to prevent unintended side effects.
  • Ignoring Edge Cases: Test your code with edge cases, such as zero, negative numbers (if applicable), and very large numbers, to ensure it handles all scenarios correctly.

Summary / Key Takeaways

In this tutorial, we’ve built a simple, interactive number converter using TypeScript. We’ve covered the basics of number bases, implemented conversion functions, and created a basic console interface. We’ve also discussed error handling, potential enhancements, and common pitfalls. The key takeaways are:

  • Understanding number bases (decimal, binary, hexadecimal, octal).
  • Implementing conversion logic using TypeScript functions.
  • Creating a basic interactive interface.
  • Importance of error handling and input validation.
  • Potential enhancements for a more advanced converter.

FAQ

Q: What is the purpose of the tsconfig.json file?
A: The tsconfig.json file configures the TypeScript compiler, specifying how to compile your TypeScript code (e.g., target ECMAScript version, module system, output directory, and strictness settings).

Q: How do I run the TypeScript code?
A: First, compile the TypeScript code using the command tsc. This generates JavaScript files in the dist directory (or the directory you specified in tsconfig.json). Then, run the JavaScript files using Node.js, e.g., node dist/converter.js.

Q: What are the benefits of using TypeScript?
A: TypeScript adds static typing to JavaScript, which helps catch errors early, improves code readability and maintainability, and provides better tooling support (e.g., autocompletion, refactoring). It also allows you to use modern JavaScript features while still being compatible with older browsers through transpilation.

Q: How can I add a GUI to the converter?
A: You can create a GUI using a framework like React, Angular, or Vue.js. This would involve creating HTML elements for input fields and output display, using CSS for styling, and writing JavaScript (or TypeScript) code to handle user interactions and update the display based on the conversion results.

Q: How can I improve the error handling?
A: You can improve error handling by validating user input more thoroughly (e.g., checking for valid characters, ranges, and formats), providing more specific and informative error messages, and using try-catch blocks to handle potential exceptions during the conversion process.

The journey of understanding number conversion and building this simple tool is a great stepping stone towards more complex programming tasks. By practicing and experimenting with the code, you’ll not only grasp the fundamentals of number bases but also strengthen your TypeScript skills. Remember to continuously refine and expand upon the code, exploring the various enhancements and features discussed. Building this converter is just the beginning of your exploration into the exciting world of software development. As you delve deeper, you’ll discover the power of TypeScript and its ability to help you create robust and scalable applications. Embrace the challenges, learn from your mistakes, and enjoy the process of bringing your ideas to life through code.