Skip to content

Instantly share code, notes, and snippets.

@jketcham
Last active February 9, 2021 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jketcham/e3ad473a4d4dff92deb4e1fd8824be46 to your computer and use it in GitHub Desktop.
Save jketcham/e3ad473a4d4dff92deb4e1fd8824be46 to your computer and use it in GitHub Desktop.
Convert a string to a number
// ASCII character codes
const ZERO_CHAR = 48;
const ONE_CHAR = 49;
const TWO_CHAR = 50;
const THREE_CHAR = 51;
const FOUR_CHAR = 52;
const FIVE_CHAR = 53;
const SIX_CHAR = 54;
const SEVEN_CHAR = 55;
const EIGHT_CHAR = 56;
const NINE_CHAR = 57;
const HYPHEN_CHAR = 45;
const PERIOD_CHAR = 46;
// Mapping of ASCII character code to int
const NUMBER_MAP = {
[ZERO_CHAR]: 0,
[ONE_CHAR]: 1,
[TWO_CHAR]: 2,
[THREE_CHAR]: 3,
[FOUR_CHAR]: 4,
[FIVE_CHAR]: 5,
[SIX_CHAR]: 6,
[SEVEN_CHAR]: 7,
[EIGHT_CHAR]: 8,
[NINE_CHAR]: 9,
};
// Characters that should only occur in number once
const CHARS_LIMIT_ONE = [
HYPHEN_CHAR,
PERIOD_CHAR,
];
const VALID_CHARS = [
ZERO_CHAR,
ONE_CHAR,
TWO_CHAR,
THREE_CHAR,
FOUR_CHAR,
FIVE_CHAR,
SIX_CHAR,
SEVEN_CHAR,
EIGHT_CHAR,
NINE_CHAR,
HYPHEN_CHAR,
PERIOD_CHAR,
];
/**
* Verifies these conditions for conversion to a number
*
* - Only contains valid characters
* - No more than 1 occurence of a hyphen and period character
* - Hyphen can only occur at the beginning of the input
*/
function isValidInput(input) {
const charOccurences = {};
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (char === HYPHEN_CHAR && i > 0) {
return false;
}
if (!VALID_CHARS.includes(char)) {
return false;
}
const occurenceCount = charOccurences[char];
// Only one of these characters can exist in input
if (occurenceCount && CHARS_LIMIT_ONE.includes(char)) {
return false;
}
charOccurences[char] = 1;
}
return true;
}
function isNegative(input) {
return input[0] === HYPHEN_CHAR;
}
// Split input between digits before and after a decimal place
function splitDigits(input) {
const pivotIndex = input.findIndex((char) => char === PERIOD_CHAR);
if (pivotIndex === -1) {
return [input];
}
return [
input.slice(0, pivotIndex), // Digits before period
input.slice(pivotIndex + 1), // Digits after period
];
}
// Return the input as an array of ASCII codes
function convertToCharCodes(input) {
return input
.split('') // Seperate string into array of its characters
.map((char, index) => input.charCodeAt(index));
}
function convertToInt(input) {
if (!input) {
return 0;
}
let number = 0;
// Start at beginning of the input and move to the right
for (let i = 0; i < input.length; i++) {
const digit = NUMBER_MAP[input[i]];
const place = (input.length - i) - 1;
number += digit * Math.pow(10, place);
}
return number;
}
function convertToDecimal(input) {
if (!input) {
return 0.0;
}
let number = 0.0;
// Start from the end of the input and move to the left
for (let i = input.length - 1; i >= 0; i--) {
const digit = NUMBER_MAP[input[i]];
number += 0.1 * digit * Math.pow(0.1, i);
}
return number;
}
function stringToNumber(input) {
input = convertToCharCodes(input);
if (!isValidInput(input)) {
throw new Error('Input is invalid');
}
const negativeInput = isNegative(input);
// If number is negative, remove the hyphen from the input
input = negativeInput ? input.slice(1) : input;
const [intChars, decimalChars] = splitDigits(input);
const intNumber = convertToInt(intChars);
const decimalNumber = convertToDecimal(decimalChars);
return (intNumber + decimalNumber) * (negativeInput ? -1 : 1);
}
module.exports = stringToNumber;
const TEST_CASES = [
{ input: '123', output: 123 },
{ input: '-123', output: -123 },
{ input: '-123.456', output: -123.456 },
{ input: '123abc', output: null },
{ input: '--1234', output: null },
{ input: '123-', output: null },
{ input: '12.123.123', output: null },
];
TEST_CASES.forEach(({ input, output }) => {
try {
const result = stringToNumber(input);
console.log({ input, expectedResult: output, result, passes: output === result });
} catch (error) {
if (error.message === 'Input is invalid') {
console.log({ input, expectedResult: output, result: 'Invalid', passes: output === null });
} else {
console.log(`Unexpected error: ${error.message} while processing "${input}"`);
}
}
});
// Expected output:
// $ node stringToNumber.js
// { input: '123', expectedResult: 123, result: 123, passes: true }
// { input: '-123', expectedResult: -123, result: -123, passes: true }
// {
// input: '-123.456',
// expectedResult: -123.456,
// result: -123.456,
// passes: true
// }
// {
// input: '123abc',
// expectedResult: null,
// result: null,
// passes: true
// }
// {
// input: '--1234',
// expectedResult: null,
// result: null,
// passes: true
// }
// {
// input: '123-',
// expectedResult: null,
// result: null,
// passes: true
// }
// {
// input: '12.123.123',
// expectedResult: null,
// result: null,
// passes: true
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment