Skip to content

Instantly share code, notes, and snippets.

@teyfix
Last active December 2, 2020 14:25
Show Gist options
  • Save teyfix/df897fc9c6e13958a93bc4b629b80988 to your computer and use it in GitHub Desktop.
Save teyfix/df897fc9c6e13958a93bc4b629b80988 to your computer and use it in GitHub Desktop.
base converting tool for javascript
class BaseConvert {
static defaultDigits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
static test() {
const expects = [
{
base: 64,
encode: [
{input: 150, output: 'CW'},
{input: 19238, output: 'Esm'},
{input: 497179179, output: 'dolor'},
],
decode: [
{input: 'js', output: 2284},
{input: 'abc', output: 108252},
{input: 'pen', output: 169895},
{input: 'Hello', output: 125458792},
],
},
];
const test = (label, expected, result) => {
if (expected === result) {
return;
}
console.log('Test failed:', label);
console.log('Expected:', expected);
console.log('Result:', result);
console.log();
};
for (const expect of expects) {
const bc = new BaseConvert(expect.base);
for (const encode of expect.encode) {
test(`Base${expect.base} encoding`, encode.output, bc.encode(encode.input));
}
for (const decode of expect.decode) {
test(`Base${expect.base} decoding`, decode.output, bc.decode(decode.input));
}
}
}
static assertNum({value, label, min, max, integer}) {
label = label || 'Input';
if ('number' !== typeof value) {
throw new TypeError(`${label} must be a number`);
}
if ('number' === typeof min && value < min) {
throw new RangeError(`${label} must be greater than or equal to ${min}`);
}
if ('number' === typeof max && value > max) {
throw new RangeError(`${label} must be less than or equal to ${max}`);
}
if (integer && !Number.isInteger(value)) {
throw new Error(`${label} must be an integer`);
}
}
static assertStr({value, label, minLength, duplicate, charset}) {
if ('string' !== typeof value) {
throw new TypeError(`${label} must be a string`);
}
if (value.length < minLength) {
throw new RangeError(`${label} must be longer than 2 characters`);
}
if (duplicate || charset) {
for (let i = 0; i < value.length; i++) {
const char = value[i];
if (duplicate) {
if (value.indexOf(char) !== value.lastIndexOf(char)) {
throw new Error(`${label} contains duplicate characters`);
}
}
if (charset) {
if (charset.indexOf(char) === -1) {
throw new Error(`${label} contains characters out of current charset`);
}
}
}
}
}
digits = '';
assertNum = BaseConvert.assertNum;
assertStr = BaseConvert.assertStr;
constructor(digits) {
if (null == digits) {
return;
}
const {assertNum, assertStr, defaultDigits} = BaseConvert;
if ('number' === typeof digits) {
assertNum({value: digits, label: 'Base', min: 2, max: BaseConvert.defaultDigits.length, integer: true});
this.digits = defaultDigits.slice(0, digits);
} else {
assertStr({value: digits, label: 'Charset', duplicate: true});
this.digits = digits;
}
}
encode(value) {
const {digits, assertNum} = this;
const base = digits.length;
let result = '';
assertNum({value, min: 0, integer: true});
do {
const digit = value % base;
result = digits.charAt(digit) + result;
value = Math.floor(value / base);
} while (value);
return result;
}
decode(value) {
const {digits, assertStr} = this;
const base = digits.length;
let result = 0;
assertStr({value, charset: digits});
for (const char of value) {
result = (result * base) + digits.indexOf(char);
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment