My C++ implementation below, ported to the browser so you can toggle bits, inject noise, and watch encode / decode run without a compiler. The logic matches the source on the right line-for-line.

Playground

11 data bits (click to toggle before encode)

inject:

16-bit block — click cells after encode to flip manually

stdout


            

Source (C++)

#include <iostream>
#include <vector>

using namespace std;

// Hamming (16, 11): indices 1..15, parity at powers of two (1, 2, 4, 8),
// master bit P0 at index 0.

vector<int> encode(const vector<int>& message) {
  vector<int> block(16, 0);
  int msgidx = 0;

  // Fill data slots only — skip parity positions 1, 2, 4, 8.
  // (idx & (idx - 1)) == 0 exactly when idx is a power of two.
  for (int idx = 1; idx < 16; idx++) {
    if ((idx & (idx - 1)) != 0) {
      block[idx] = message[msgidx++];
    }
  }

  // XOR indices of all 1-bits in positions 1..15.
  // The low four bits of that sum become P1, P2, P4, P8.
  int parity_sum = 0;
  for (int idx = 1; idx < 16; idx++) {
    if (block[idx] == 1) {
      parity_sum ^= idx;
    }
  }

  block[1] = parity_sum & 1;
  block[2] = (parity_sum >> 1) & 1;
  block[4] = (parity_sum >> 2) & 1;
  block[8] = (parity_sum >> 3) & 1;

  // P0: even parity over bits 1..15 (overall / master check).
  int p0 = 0;
  for (int idx = 1; idx < 16; idx++) {
    p0 ^= block[idx];
  }
  block[0] = p0;

  return block;
}

vector<int> decode(vector<int>& receivedBlock) {
  // Syndrome from P1..P8: zero means those checks pass.
  int syndrome = 0;
  for (int idx = 1; idx < 16; idx++) {
    if (receivedBlock[idx] == 1) {
      syndrome ^= idx;
    }
  }

  // Total parity over all 16 bits (uses P0 together with syndrome).
  int total_parity = 0;
  for (int idx = 0; idx < 16; idx++) {
    total_parity ^= receivedBlock[idx];
  }

  // Case 1: clean codeword
  if (syndrome == 0 && total_parity == 0) {
    cout << "No error detected" << endl;
  }
  // Case 2: only P0 flipped
  else if (syndrome == 0 && total_parity == 1) {
    cout << "Error in master bit" << endl;
    receivedBlock[0] ^= 1;
  }
  // Case 3: single-bit error at index == syndrome
  else if (syndrome != 0 && total_parity == 1) {
    cout << "single-bit error at position " << syndrome << endl;
    receivedBlock[syndrome] ^= 1;
    cout << "Corrected the error" << endl;
  }
  // Case 4: two-bit error (detect only)
  else {
    cout << "Two bit error detected" << endl;
    return {};
  }

  // Extract the 11 data bits from corrected positions.
  vector<int> message;
  for (int idx = 1; idx < 16; idx++) {
    if ((idx & (idx - 1)) != 0) {
      message.push_back(receivedBlock[idx]);
    }
  }
  return message;
}

void printmsg(const vector<int>& v) {
  for (int bit : v) {
    cout << bit;
  }
  cout << endl;
}

int main() {
  vector<int> message = {1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
  cout << "Original msg
";
  printmsg(message);

  auto encoded_data = encode(message);
  cout << "Encoded data is:
";
  printmsg(encoded_data);

  // Single-bit error example:
  // encoded_data[9] ^= 1;

  // Double-bit error (detected, not corrected):
  encoded_data[5] ^= 1;
  encoded_data[10] ^= 1;

  cout << "Received block:
";
  printmsg(encoded_data);
  auto decoded_data = decode(encoded_data);

  if (!decoded_data.empty()) {
    cout << "Received message:
";
    printmsg(decoded_data);
  }
  return 0;
}

Default message matches main(): 10110100110. Try double (5 & 10) then decode — same two-bit test as the source.