Explanation
Think of how base 10 works.
909 = 900 + 9
= (9 * 100) + (0 * 10) + (9 * 1)
= (9 * 10**2) + (0 * 10**1) + (9 * 10**0)
As you can see, a natural number in base 10 can be seen as a sum where each term is in the form of:
digit * base**digit_position
This is true for any base:
base 2 : 0b101 = (0b1 * 2**2) + (0b0 * 2**1) + (0b1 * 2**0)
base 16 : 0xF0F = (0xF * 16**2) + (0x0 * 16**1) + (0xF * 16**0)
Therefore, here is a possible abstraction of a natural number:
function natural_number (base, digits) {
var sum = 0;
for (var i = 0; i < digits.length; i++) {
digit = digits[i];
digit_position = digits.length - (i + 1);
sum += digit * base**digit_position;
}
return sum;
}
> | natural_number(2, [1, 0, 1]) // 1 * 2**2 + 1 * 2**0
< | 5
> | natural_number(10, [1, 0, 1]) // 1 * 10**2 + 1 * 10**0
< | 101
> | natural_number(16, [1, 0, 1]) // 1 * 16**2 + 1 * 16**0
< | 257
Your own function takes only binary numbers (base 2). In this case digit can be either 0 or 1, that's all. We know that it's useless to multiply something by 0 or 1, so the addition can be replaced with:
if (digit === 1) {
sum += 2**digit_position;
}
Which is the equivalent of:
if (num[num.length - (i + 1)] === '1') {
dec += 2 ** i;
}
Do you get it? :-)
Alternative
You don't feel confortable with the exponentiation operator (**)? There is a workaround. Did you ever notice that multiplying a number by 10 is nothing more than shifting its digits one time to the left?
909 * 10 = 9090
Actually, shifting a number to the left boils down to multiplying this number by its base:
number *= base
This is true for any base:
base 2 : 0b11 * 2 = 0b110
base 16 : 0xBEE * 16 + 0xF = 0xBEE0 + 0xF = 0xBEEF
Based on this, we can build an algorithm to convert an array of digits into a number. A trace of execution with [9,0,9] in base 10 as input would look like this:
init | 0 | n = 0
add 9 | 9 | n += 9
shift | 90 | n *= 10
add 0 | 90 | n += 0
shift | 900 | n *= 10
add 9 | 909 | n += 9
Here is a possible implementation:
function natural_number (base, digits) {
var n = 0;
for (var i = 0; i < digits.length; i++) {
n += digits[i];
if (i + 1 < digits.length) {
n *= base;
}
}
return n;
}
Of course this function works the same as before, and there is a good reason for that. Indeed, unroll the for loop that computes [9,0,9] in base 10, you get this:
return ((0 + 9) * 10 + 0) * 10 + 9;
Then expand this expression:
((0 + 9) * 10 + 0) * 10 + 9
= (0 + 9) * 10 * 10 + 0 * 10 + 9
= 9 * 10 * 10 + 0 * 10 + 9
= 9 * 10**2 + 0 * 10**1 + 9 * 10**0
Do you recognize the equation discussed earlier? :-)
Bonus
Reverse function:
function explode_natural_number (base, number) {
var remainder, exploded = [];
while (number) {
remainder = number % base;
exploded.unshift(remainder);
number = (number - remainder) / base;
}
return exploded.length ? exploded : [0];
}
> | explode_natural_number(2, 5)
< | [1, 0, 1]
> | explode_natural_number(3, 5) // base 3 (5 = 1 * 3**1 + 2 * 3**0) :-)
< | [1, 2]
> | explode_natural_number(16, natural_number(16, [11, 14, 14, 15])) // 0xBEEF
< | [11, 14, 14, 15]
String to number and number to string:
function parse_natural_number (number, base) {
var ZERO = 48, A = 65; // ASCII codes
return natural_number(base, number.split("").map(function (digit) {
return digit.toUpperCase().charCodeAt(0);
}).map(function (code) {
return code - (code < A ? ZERO : A - 10);
}));
}
function stringify_natural_number (number, base) {
var ZERO = 48, A = 65; // ASCII codes
return String.fromCharCode.apply(
String, explode_natural_number(base, number).map(function (digit) {
return digit + (digit < 10 ? ZERO : A - 10);
})
);
}
> | stringify_natural_number(parse_natural_number("48879", 10), 16)
< | "BEEF"
> | parse_natural_number("10", 8)
< | 8
More levels of abstraction for convenience:
function bin_to_dec (number) {
return parse_natural_number(number, 2);
}
function oct_to_dec (number) {
return parse_natural_number(number, 8);
}
function dec_to_dec (number) {
return parse_natural_number(number, 10);
}
function hex_to_dec (number) {
return parse_natural_number(number, 16);
}
function num_to_dec (number) {
switch (number[0] + number[1]) {
case "0b" : return bin_to_dec(number.slice(2));
case "0x" : return hex_to_dec(number.slice(2));
default : switch (number[0]) {
case "0" : return oct_to_dec(number.slice(1));
default : return dec_to_dec(number);
}
}
}
> | oct_to_dec("10")
< | 8
> | num_to_dec("010")
< | 8
> | 010 // :-)
< | 8
function dec_to_bin (number) {
return stringify_natural_number(number, 2);
}
> | dec_to_bin(8)
< | "1000"