Source code is maintained on GitHub and may be cloned via…
mkdir vp ~/git/hub/javascriptutilities
cd ~/git/hub/javascriptutilities
git clone git@github.com:javascriptutilities/decimaltobase.git
… and a Live demo is hosted thanks to GitHub Pages.
The build target is ECMAScript version 6, and so far both manual tests and automated JestJS tests show that the decimalToBase
function functions as intended, both for Browser and NodeJS environments.
Aware of (_number_).toString(_radix_)
for converting numbers to another base I am, however, the builtin Number.toString()
method doesn’t seem to have options for prefixing Binary, Octal, or Heximal bases, among other features that I’m implementing.
Example Usage
decimalToBase(540, 16);
//> "0x21C"
I am concerned with improving the JavaScript, and TypeScript that builds the JavaScript; ie. the HTML and CSS are intended to be simple and functional.
Please direct suggestions, and point out any mistakes that I’ve made, in regards to JavaScript. If anyone has a clean way of handling floating point numbers that’d be super, because at this time the current implementation only handles integers.
"use strict";
/**
* Converts decimal to another base, eg. hex, octal, or binary
* @function decimalToBase
* @param {numberstring} decimal
* @param {numberstring} radix  default `16`
* @param {boolean} verbose  default `false`
* @param {string()} symbols_list  default `(...'0123456789abcdefghijklmnopqrstuvwxyz')`
* @returns {string}
* @throws {SyntaxErrorRangeError}
* @author S0AndS0
* @license AGPL3.0
* @see {link}  https://github.com/javascriptutilities/decimaltobase
* @example
* decimalToBase(540, 16);
* //> "0x21C"
*/
const decimalToBase = (decimal, radix = 16, verbose = false, symbols_list = (...'0123456789abcdefghijklmnopqrstuvwxyz')) => {
decimal = Number(decimal);
radix = Number(radix);
const max_base = symbols_list.length;
if (isNaN(decimal)) {
throw new SyntaxError('First argument is Not a Number');
}
else if (isNaN(radix)) {
throw new SyntaxError('radix is Not a Number');
}
else if (radix > max_base) {
throw new RangeError(`radix must be less than max base > ${max_base}`);
}
else if (radix < 2) {
throw new RangeError(`radix must be greater than 2`);
}
let prefix = '';
switch (radix) {
case 16: // Hexadecimal
prefix = '0x';
break;
case 8: // Octal
prefix = '0o';
break;
case 2: // Binary
prefix = '0b';
break;
}
if (radix >= 10 && decimal < 10) {
return `${prefix}${symbols_list(decimal)}`;
}
let converted = '';
let dividend = decimal;
while (dividend > 0) {
const remainder = dividend % radix;
const quotient = (dividend  remainder) / radix;
/* istanbul ignore next */
if (verbose) {
console.log(`dividend > ${dividend}`, `remainder > ${remainder}`, `quotient > ${quotient}`);
}
converted = `${symbols_list(remainder)}${converted}`;
dividend = quotient;
}
return `${prefix}${converted.toUpperCase()}`;
};
/* istanbul ignore next */
if (typeof module !== 'undefined') {
module.exports = decimalToBase;
}
*, *::before, *::after {
boxsizing: borderbox;
}
.container {
maxwidth: 50%;
position: relative;
}
.row {
paddingtop: 1rem;
}
.row::after {
content: '';
position: absolute;
left: 0;
backgroundcolor: lightgrey;
height: 0.2rem;
width: 100%;
}
.label {
fontweight: bold;
fontsize: 1.2rem;
width: 19%;
paddingright: 1%;
}
.text_input {
float: right;
width: 79%;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf8">
<title>Tests Decimal to Base</title>
<! <link rel="stylesheet" href="https://codereview.stackexchange.com/assets/css/main.css"> >
<! <script type="text/javascript" src="assets/js/modules/decimaltobase/decimaltobase.js"></script> >
<script type="text/javascript">
function updateOutput(_event) {
const decimal = document.getElementById('decimal').value;
const radix = document.getElementById('radix').value;
const output_element = document.getElementById('output');
let output_value = 'NaN';
try {
output_value = decimalToBase(decimal, radix)
} catch (e) {
if (e instanceof SyntaxError) {
console.error(e);
} else if (e instanceof RangeError) {
console.error(e);
} else {
throw e;
}
}
output_element.value = output_value;
}
window.addEventListener('load', (_event) => {
document.getElementById('decimal').addEventListener('input', updateOutput);
document.getElementById('radix').addEventListener('input', updateOutput);
});
</script>
</head>
<body>
<div class="container">
<div class="row">
<span class="label">Radix: </span>
<input type="text" class="text_input" id="radix" value="16">
</div>
<br>
<div class="row">
<span class="label">Input: </span>
<input type="text" class="text_input" id="decimal">
</div>
<br>
<div class="row">
<span class="label">Output: </span>
<input type="text" class="text_input" id="output" readonly>
</div>
</div>
</body>
</html>
For completeness here are the JestJS tests…
"use strict";
/**
* @author S0AndS0
* @copyright AGPL3.0
* @example <caption>Jest Tests for decimalToBase</caption>
* // Initialize new class instance and run tests
* const test_decimalToBase = new decimalToBase_Test();
* test_decimalToBase.runTests();
*/
class decimalToBase_Test {
constructor() {
this.decimalToBase = require('../decimaltobase.js');
this.decimal_limits = { min: 1, max: 16 };
this.base_configs = (
{
base: 2,
name: 'Binary'
},
{
base: 3,
name: 'Trinary'
},
{
base: 4,
name: 'Quaternary'
},
{
base: 5,
name: 'Quinary AKA Pental'
},
{
base: 6,
name: 'Senary AKA Heximal or Seximal'
},
{
base: 7,
name: 'Septenary'
},
{
base: 8,
name: 'Octal'
},
{
base: 9,
name: 'Nonary'
},
{
base: 10,
name: 'Decimal AKA Denary'
},
{
base: 11,
name: 'Undecimal'
},
{
base: 12,
name: 'Duodecimal AKA Dozenal or Uncial'
},
{
base: 13,
name: 'Tridecimal'
},
{
base: 14,
name: 'Tetradecimal'
},
{
base: 15,
name: 'Pentadecimal'
},
{
base: 16,
name: 'Hexadecimal'
}
);
}
/**
* Runs all tests for this module
*/
runTests() {
this.testsErrors();
this.testsConversion();
}
/**
* Uses `(Number).toString()` to check conversions, note this will only work for radix between `2` though `36`, and default `symbols_list`
*/
static doubleChecker(decimal, radix) {
decimal = Number(decimal);
radix = Number(radix);
let prefix = '';
switch (radix) {
case 2:
prefix = '0b';
break;
case 8:
prefix = '0o';
break;
case 16:
prefix = '0x';
break;
}
return `${prefix}${(decimal).toString(radix).toUpperCase()}`;
}
/**
* Tests available error states
*/
testsErrors() {
test('Is a `SyntaxError` thrown, when `decimal` parameter is not a number?', () => {
expect(() => {
this.decimalToBase('spam!', 10);
}).toThrow(SyntaxError);
});
test('Is a `SyntaxError` thrown, when `radix` parameter is not a number?', () => {
expect(() => {
this.decimalToBase(42, 'ham');
}).toThrow(SyntaxError);
});
test('Is a `RangeError` thrown, when `symbols_list` is not long enough?', () => {
expect(() => {
this.decimalToBase(42, 37);
}).toThrow(RangeError);
});
test('Is a `RangeError` thrown, when `radix` parameter is less than `2`?', () => {
expect(() => {
this.decimalToBase(42, 1);
}).toThrow(RangeError);
});
}
/**
* Loops through `this.base_configs` and tests decimal integers between `this.decimal_limits('min')` and `this.decimal_limits('max')`
*/
testsConversion() {
const min = this.decimal_limits('min');
const max = this.decimal_limits('max');
this.base_configs.forEach((config) => {
const { base } = config;
const { name } = config;
for (let decimal = min; decimal <= max; decimal++) {
const expected_value = this.constructor.doubleChecker(decimal, base);
test(`Base ${base}, does ${decimal} equal "${expected_value}" in ${name}?`, () => {
expect(this.decimalToBase(decimal, base)).toEqual(expected_value);
});
}
});
}
}
const test_decimalToBase = new decimalToBase_Test();
test_decimalToBase.runTests();
Questions

Are there any mistakes?

Have I missed any test cases?

Any suggestions on expanding this implementation to handle floating point numbers?

Any suggestions on making the code more readable?

Are there any additional features that are wanted?