243 lines
7.1 KiB
JavaScript
243 lines
7.1 KiB
JavaScript
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/**
|
|
* @fileoverview Numeric base conversion library. Works for arbitrary bases and
|
|
* arbitrary length numbers.
|
|
*
|
|
* For base-64 conversion use base64.js because it is optimized for the specific
|
|
* conversion to base-64 while this module is generic. Base-64 is defined here
|
|
* mostly for demonstration purpose.
|
|
*
|
|
* TODO: Make base64 and baseN classes that have common interface. (Perhaps...)
|
|
*
|
|
*/
|
|
|
|
goog.provide('goog.crypt.baseN');
|
|
|
|
|
|
/**
|
|
* Base-2, i.e. '01'.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_BINARY = '01';
|
|
|
|
|
|
/**
|
|
* Base-8, i.e. '01234567'.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_OCTAL = '01234567';
|
|
|
|
|
|
/**
|
|
* Base-10, i.e. '0123456789'.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_DECIMAL = '0123456789';
|
|
|
|
|
|
/**
|
|
* Base-16 using lower case, i.e. '0123456789abcdef'.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL = '0123456789abcdef';
|
|
|
|
|
|
/**
|
|
* Base-16 using upper case, i.e. '0123456789ABCDEF'.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_UPPERCASE_HEXADECIMAL = '0123456789ABCDEF';
|
|
|
|
|
|
/**
|
|
* The more-known version of the BASE-64 encoding. Uses + and / characters.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_64 =
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
|
|
|
|
/**
|
|
* URL-safe version of the BASE-64 encoding.
|
|
* @type {string}
|
|
*/
|
|
goog.crypt.baseN.BASE_64_URL_SAFE =
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
|
|
|
|
/**
|
|
* Converts a number from one numeric base to another.
|
|
*
|
|
* The bases are represented as strings, which list allowed digits. Each digit
|
|
* should be unique. The bases can either be user defined, or any of
|
|
* goog.crypt.baseN.BASE_xxx.
|
|
*
|
|
* The number is in human-readable format, most significant digit first, and is
|
|
* a non-negative integer. Base designators such as $, 0x, d, b or h (at end)
|
|
* will be interpreted as digits, so avoid them. Leading zeros will be trimmed.
|
|
*
|
|
* Note: for huge bases the result may be inaccurate because of overflowing
|
|
* 64-bit doubles used by JavaScript for integer calculus. This may happen
|
|
* if the product of the number of digits in the input and output bases comes
|
|
* close to 10^16, which is VERY unlikely (100M digits in each base), but
|
|
* may be possible in the future unicode world. (Unicode 3.2 has less than 100K
|
|
* characters. However, it reserves some more, close to 1M.)
|
|
*
|
|
* @param {string} number The number to convert.
|
|
* @param {string} inputBase The numeric base the number is in (all digits).
|
|
* @param {string} outputBase Requested numeric base.
|
|
* @return {string} The converted number.
|
|
*/
|
|
goog.crypt.baseN.recodeString = function(number, inputBase, outputBase) {
|
|
if (outputBase == '') {
|
|
throw Error('Empty output base');
|
|
}
|
|
|
|
// Check if number is 0 (special case when we don't want to return '').
|
|
var isZero = true;
|
|
for (var i = 0, n = number.length; i < n; i++) {
|
|
if (number.charAt(i) != inputBase.charAt(0)) {
|
|
isZero = false;
|
|
break;
|
|
}
|
|
}
|
|
if (isZero) {
|
|
return outputBase.charAt(0);
|
|
}
|
|
|
|
var numberDigits = goog.crypt.baseN.stringToArray_(number, inputBase);
|
|
|
|
var inputBaseSize = inputBase.length;
|
|
var outputBaseSize = outputBase.length;
|
|
|
|
// result = 0.
|
|
var result = [];
|
|
|
|
// For all digits of number, starting with the most significant ...
|
|
for (var i = numberDigits.length - 1; i >= 0; i--) {
|
|
|
|
// result *= number.base.
|
|
var carry = 0;
|
|
for (var j = 0, n = result.length; j < n; j++) {
|
|
var digit = result[j];
|
|
// This may overflow for huge bases. See function comment.
|
|
digit = digit * inputBaseSize + carry;
|
|
if (digit >= outputBaseSize) {
|
|
var remainder = digit % outputBaseSize;
|
|
carry = (digit - remainder) / outputBaseSize;
|
|
digit = remainder;
|
|
} else {
|
|
carry = 0;
|
|
}
|
|
result[j] = digit;
|
|
}
|
|
while (carry) {
|
|
var remainder = carry % outputBaseSize;
|
|
result.push(remainder);
|
|
carry = (carry - remainder) / outputBaseSize;
|
|
}
|
|
|
|
// result += number[i].
|
|
carry = numberDigits[i];
|
|
var j = 0;
|
|
while (carry) {
|
|
if (j >= result.length) {
|
|
// Extend result with a leading zero which will be overwritten below.
|
|
result.push(0);
|
|
}
|
|
var digit = result[j];
|
|
digit += carry;
|
|
if (digit >= outputBaseSize) {
|
|
var remainder = digit % outputBaseSize;
|
|
carry = (digit - remainder) / outputBaseSize;
|
|
digit = remainder;
|
|
} else {
|
|
carry = 0;
|
|
}
|
|
result[j] = digit;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return goog.crypt.baseN.arrayToString_(result, outputBase);
|
|
};
|
|
|
|
|
|
/**
|
|
* Converts a string representation of a number to an array of digit values.
|
|
*
|
|
* More precisely, the digit values are indices into the number base, which
|
|
* is represented as a string, which can either be user defined or one of the
|
|
* BASE_xxx constants.
|
|
*
|
|
* Throws an Error if the number contains a digit not found in the base.
|
|
*
|
|
* @param {string} number The string to convert, most significant digit first.
|
|
* @param {string} base Digits in the base.
|
|
* @return {Array.<number>} Array of digit values, least significant digit
|
|
* first.
|
|
* @private
|
|
*/
|
|
goog.crypt.baseN.stringToArray_ = function(number, base) {
|
|
var index = {};
|
|
for (var i = 0, n = base.length; i < n; i++) {
|
|
index[base.charAt(i)] = i;
|
|
}
|
|
var result = [];
|
|
for (var i = number.length - 1; i >= 0; i--) {
|
|
var character = number.charAt(i);
|
|
var digit = index[character];
|
|
if (typeof digit == 'undefined') {
|
|
throw Error('Number ' + number +
|
|
' contains a character not found in base ' +
|
|
base + ', which is ' + character);
|
|
}
|
|
result.push(digit);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
|
|
/**
|
|
* Converts an array representation of a number to a string.
|
|
*
|
|
* More precisely, the elements of the input array are indices into the base,
|
|
* which is represented as a string, which can either be user defined or one of
|
|
* the BASE_xxx constants.
|
|
*
|
|
* Throws an Error if the number contains a digit which is outside the range
|
|
* 0 ... base.length - 1.
|
|
*
|
|
* @param {Array.<number>} number Array of digit values, least significant
|
|
* first.
|
|
* @param {string} base Digits in the base.
|
|
* @return {string} Number as a string, most significant digit first.
|
|
* @private
|
|
*/
|
|
goog.crypt.baseN.arrayToString_ = function(number, base) {
|
|
var n = number.length;
|
|
var chars = [];
|
|
var baseSize = base.length;
|
|
for (var i = n - 1; i >= 0; i--) {
|
|
var digit = number[i];
|
|
if (digit >= baseSize || digit < 0) {
|
|
throw Error('Number ' + number + ' contains an invalid digit: ' + digit);
|
|
}
|
|
chars.push(base.charAt(digit));
|
|
}
|
|
return chars.join('');
|
|
};
|