// 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} 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 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(''); };