The Source code is maintained on GitHub and may be cloned via the following commands. A Live demo is hosted online, thanks to GitHub Pages.
mkdir -vp ~/git/hub/javascript-utilities cd ~/git/hub/javascript-utilities git clone [email protected]:javascript-utilities/decimal-to-base.git
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.
I am aware of (number).toString(radix)
for converting numbers to another base. However, the built-in 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 mostly concerned with improving the JavaScript, and TypeScript. The HTML and CSS are intended to be simple and functional.
Pleas post any suggestion to the JavaScript. Additionally 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 {number|string} decimal * @param {number|string} radix - default `16` * @param {boolean} verbose - default `false` * @param {string[]} symbols_list - default `[...'0123456789abcdefghijklmnopqrstuvwxyz']` * @returns {string} * @throws {SyntaxError|RangeError} * @author S0AndS0 * @license AGPL-3.0 * @see {link} - https://www.tutorialspoint.com/how-to-convert-decimal-to-hexadecimal * @see {link} - https://www.ecma-international.org/ecma-262/6.0/#sec-literals-numeric-literals * @example * decimalToBase(540, 16); * //> "0x21C" */ const decimalToBase = (decimal, radix = 16, verbose = false, symbols_list = [...'0123456789abcdefghijklmnopqrstuvwxyz']) => { decimal = Math.floor(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 or equal to 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 { box-sizing: border-box; } .container { max-width: 50%; position: relative; } .row { padding-top: 1rem; } .row::after { content: ''; position: absolute; left: 0; background-color: lightgrey; height: 0.2rem; width: 100%; } .label { font-weight: bold; font-size: 1.2rem; width: 19%; padding-right: 1%; } .text_input { float: right; width: 79%; }
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>Tests Decimal to Base</title> <!-- <link rel="stylesheet" href="assets/css/main.css"> --> <!-- <script type="text/javascript" src="assets/js/modules/decimal-to-base/decimal-to-base.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 AGPL-3.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('../decimal-to-base.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?
decimalToBase
function will throw aSyntaxError
orRangeError
, which should be caught by in-lined JavaScript within the HTML. But for some reason CodeReview has their own catcher which is adding an overlaid element, so it seems to be necessary to use the "Expand snippit" so that errors do not hide elements used for input/output.\$\endgroup\$radix must be less than max base -> ${max_base}
. Ifmax-base = 16
then this says "radix cannot be 16."\$\endgroup\$decimal
parameter values get caught?\$\endgroup\$