| 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS-IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | /** |
| 16 | * @fileoverview Additional mathematical functions. |
| 17 | */ |
| 18 | |
| 19 | goog.provide('goog.math'); |
| 20 | |
| 21 | goog.require('goog.array'); |
| 22 | goog.require('goog.asserts'); |
| 23 | |
| 24 | |
| 25 | /** |
| 26 | * Returns a random integer greater than or equal to 0 and less than {@code a}. |
| 27 | * @param {number} a The upper bound for the random integer (exclusive). |
| 28 | * @return {number} A random integer N such that 0 <= N < a. |
| 29 | */ |
| 30 | goog.math.randomInt = function(a) { |
| 31 | return Math.floor(Math.random() * a); |
| 32 | }; |
| 33 | |
| 34 | |
| 35 | /** |
| 36 | * Returns a random number greater than or equal to {@code a} and less than |
| 37 | * {@code b}. |
| 38 | * @param {number} a The lower bound for the random number (inclusive). |
| 39 | * @param {number} b The upper bound for the random number (exclusive). |
| 40 | * @return {number} A random number N such that a <= N < b. |
| 41 | */ |
| 42 | goog.math.uniformRandom = function(a, b) { |
| 43 | return a + Math.random() * (b - a); |
| 44 | }; |
| 45 | |
| 46 | |
| 47 | /** |
| 48 | * Takes a number and clamps it to within the provided bounds. |
| 49 | * @param {number} value The input number. |
| 50 | * @param {number} min The minimum value to return. |
| 51 | * @param {number} max The maximum value to return. |
| 52 | * @return {number} The input number if it is within bounds, or the nearest |
| 53 | * number within the bounds. |
| 54 | */ |
| 55 | goog.math.clamp = function(value, min, max) { |
| 56 | return Math.min(Math.max(value, min), max); |
| 57 | }; |
| 58 | |
| 59 | |
| 60 | /** |
| 61 | * The % operator in JavaScript returns the remainder of a / b, but differs from |
| 62 | * some other languages in that the result will have the same sign as the |
| 63 | * dividend. For example, -1 % 8 == -1, whereas in some other languages |
| 64 | * (such as Python) the result would be 7. This function emulates the more |
| 65 | * correct modulo behavior, which is useful for certain applications such as |
| 66 | * calculating an offset index in a circular list. |
| 67 | * |
| 68 | * @param {number} a The dividend. |
| 69 | * @param {number} b The divisor. |
| 70 | * @return {number} a % b where the result is between 0 and b (either 0 <= x < b |
| 71 | * or b < x <= 0, depending on the sign of b). |
| 72 | */ |
| 73 | goog.math.modulo = function(a, b) { |
| 74 | var r = a % b; |
| 75 | // If r and b differ in sign, add b to wrap the result to the correct sign. |
| 76 | return (r * b < 0) ? r + b : r; |
| 77 | }; |
| 78 | |
| 79 | |
| 80 | /** |
| 81 | * Performs linear interpolation between values a and b. Returns the value |
| 82 | * between a and b proportional to x (when x is between 0 and 1. When x is |
| 83 | * outside this range, the return value is a linear extrapolation). |
| 84 | * @param {number} a A number. |
| 85 | * @param {number} b A number. |
| 86 | * @param {number} x The proportion between a and b. |
| 87 | * @return {number} The interpolated value between a and b. |
| 88 | */ |
| 89 | goog.math.lerp = function(a, b, x) { |
| 90 | return a + x * (b - a); |
| 91 | }; |
| 92 | |
| 93 | |
| 94 | /** |
| 95 | * Tests whether the two values are equal to each other, within a certain |
| 96 | * tolerance to adjust for floating point errors. |
| 97 | * @param {number} a A number. |
| 98 | * @param {number} b A number. |
| 99 | * @param {number=} opt_tolerance Optional tolerance range. Defaults |
| 100 | * to 0.000001. If specified, should be greater than 0. |
| 101 | * @return {boolean} Whether {@code a} and {@code b} are nearly equal. |
| 102 | */ |
| 103 | goog.math.nearlyEquals = function(a, b, opt_tolerance) { |
| 104 | return Math.abs(a - b) <= (opt_tolerance || 0.000001); |
| 105 | }; |
| 106 | |
| 107 | |
| 108 | // TODO(user): Rename to normalizeAngle, retaining old name as deprecated |
| 109 | // alias. |
| 110 | /** |
| 111 | * Normalizes an angle to be in range [0-360). Angles outside this range will |
| 112 | * be normalized to be the equivalent angle with that range. |
| 113 | * @param {number} angle Angle in degrees. |
| 114 | * @return {number} Standardized angle. |
| 115 | */ |
| 116 | goog.math.standardAngle = function(angle) { |
| 117 | return goog.math.modulo(angle, 360); |
| 118 | }; |
| 119 | |
| 120 | |
| 121 | /** |
| 122 | * Normalizes an angle to be in range [0-2*PI). Angles outside this range will |
| 123 | * be normalized to be the equivalent angle with that range. |
| 124 | * @param {number} angle Angle in radians. |
| 125 | * @return {number} Standardized angle. |
| 126 | */ |
| 127 | goog.math.standardAngleInRadians = function(angle) { |
| 128 | return goog.math.modulo(angle, 2 * Math.PI); |
| 129 | }; |
| 130 | |
| 131 | |
| 132 | /** |
| 133 | * Converts degrees to radians. |
| 134 | * @param {number} angleDegrees Angle in degrees. |
| 135 | * @return {number} Angle in radians. |
| 136 | */ |
| 137 | goog.math.toRadians = function(angleDegrees) { |
| 138 | return angleDegrees * Math.PI / 180; |
| 139 | }; |
| 140 | |
| 141 | |
| 142 | /** |
| 143 | * Converts radians to degrees. |
| 144 | * @param {number} angleRadians Angle in radians. |
| 145 | * @return {number} Angle in degrees. |
| 146 | */ |
| 147 | goog.math.toDegrees = function(angleRadians) { |
| 148 | return angleRadians * 180 / Math.PI; |
| 149 | }; |
| 150 | |
| 151 | |
| 152 | /** |
| 153 | * For a given angle and radius, finds the X portion of the offset. |
| 154 | * @param {number} degrees Angle in degrees (zero points in +X direction). |
| 155 | * @param {number} radius Radius. |
| 156 | * @return {number} The x-distance for the angle and radius. |
| 157 | */ |
| 158 | goog.math.angleDx = function(degrees, radius) { |
| 159 | return radius * Math.cos(goog.math.toRadians(degrees)); |
| 160 | }; |
| 161 | |
| 162 | |
| 163 | /** |
| 164 | * For a given angle and radius, finds the Y portion of the offset. |
| 165 | * @param {number} degrees Angle in degrees (zero points in +X direction). |
| 166 | * @param {number} radius Radius. |
| 167 | * @return {number} The y-distance for the angle and radius. |
| 168 | */ |
| 169 | goog.math.angleDy = function(degrees, radius) { |
| 170 | return radius * Math.sin(goog.math.toRadians(degrees)); |
| 171 | }; |
| 172 | |
| 173 | |
| 174 | /** |
| 175 | * Computes the angle between two points (x1,y1) and (x2,y2). |
| 176 | * Angle zero points in the +X direction, 90 degrees points in the +Y |
| 177 | * direction (down) and from there we grow clockwise towards 360 degrees. |
| 178 | * @param {number} x1 x of first point. |
| 179 | * @param {number} y1 y of first point. |
| 180 | * @param {number} x2 x of second point. |
| 181 | * @param {number} y2 y of second point. |
| 182 | * @return {number} Standardized angle in degrees of the vector from |
| 183 | * x1,y1 to x2,y2. |
| 184 | */ |
| 185 | goog.math.angle = function(x1, y1, x2, y2) { |
| 186 | return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1, |
| 187 | x2 - x1))); |
| 188 | }; |
| 189 | |
| 190 | |
| 191 | /** |
| 192 | * Computes the difference between startAngle and endAngle (angles in degrees). |
| 193 | * @param {number} startAngle Start angle in degrees. |
| 194 | * @param {number} endAngle End angle in degrees. |
| 195 | * @return {number} The number of degrees that when added to |
| 196 | * startAngle will result in endAngle. Positive numbers mean that the |
| 197 | * direction is clockwise. Negative numbers indicate a counter-clockwise |
| 198 | * direction. |
| 199 | * The shortest route (clockwise vs counter-clockwise) between the angles |
| 200 | * is used. |
| 201 | * When the difference is 180 degrees, the function returns 180 (not -180) |
| 202 | * angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10. |
| 203 | * angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20. |
| 204 | */ |
| 205 | goog.math.angleDifference = function(startAngle, endAngle) { |
| 206 | var d = goog.math.standardAngle(endAngle) - |
| 207 | goog.math.standardAngle(startAngle); |
| 208 | if (d > 180) { |
| 209 | d = d - 360; |
| 210 | } else if (d <= -180) { |
| 211 | d = 360 + d; |
| 212 | } |
| 213 | return d; |
| 214 | }; |
| 215 | |
| 216 | |
| 217 | /** |
| 218 | * Returns the sign of a number as per the "sign" or "signum" function. |
| 219 | * @param {number} x The number to take the sign of. |
| 220 | * @return {number} -1 when negative, 1 when positive, 0 when 0. |
| 221 | */ |
| 222 | goog.math.sign = function(x) { |
| 223 | return x == 0 ? 0 : (x < 0 ? -1 : 1); |
| 224 | }; |
| 225 | |
| 226 | |
| 227 | /** |
| 228 | * JavaScript implementation of Longest Common Subsequence problem. |
| 229 | * http://en.wikipedia.org/wiki/Longest_common_subsequence |
| 230 | * |
| 231 | * Returns the longest possible array that is subarray of both of given arrays. |
| 232 | * |
| 233 | * @param {Array.<Object>} array1 First array of objects. |
| 234 | * @param {Array.<Object>} array2 Second array of objects. |
| 235 | * @param {Function=} opt_compareFn Function that acts as a custom comparator |
| 236 | * for the array ojects. Function should return true if objects are equal, |
| 237 | * otherwise false. |
| 238 | * @param {Function=} opt_collectorFn Function used to decide what to return |
| 239 | * as a result subsequence. It accepts 2 arguments: index of common element |
| 240 | * in the first array and index in the second. The default function returns |
| 241 | * element from the first array. |
| 242 | * @return {!Array.<Object>} A list of objects that are common to both arrays |
| 243 | * such that there is no common subsequence with size greater than the |
| 244 | * length of the list. |
| 245 | */ |
| 246 | goog.math.longestCommonSubsequence = function( |
| 247 | array1, array2, opt_compareFn, opt_collectorFn) { |
| 248 | |
| 249 | var compare = opt_compareFn || function(a, b) { |
| 250 | return a == b; |
| 251 | }; |
| 252 | |
| 253 | var collect = opt_collectorFn || function(i1, i2) { |
| 254 | return array1[i1]; |
| 255 | }; |
| 256 | |
| 257 | var length1 = array1.length; |
| 258 | var length2 = array2.length; |
| 259 | |
| 260 | var arr = []; |
| 261 | for (var i = 0; i < length1 + 1; i++) { |
| 262 | arr[i] = []; |
| 263 | arr[i][0] = 0; |
| 264 | } |
| 265 | |
| 266 | for (var j = 0; j < length2 + 1; j++) { |
| 267 | arr[0][j] = 0; |
| 268 | } |
| 269 | |
| 270 | for (i = 1; i <= length1; i++) { |
| 271 | for (j = 1; j <= length2; j++) { |
| 272 | if (compare(array1[i - 1], array2[j - 1])) { |
| 273 | arr[i][j] = arr[i - 1][j - 1] + 1; |
| 274 | } else { |
| 275 | arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]); |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | // Backtracking |
| 281 | var result = []; |
| 282 | var i = length1, j = length2; |
| 283 | while (i > 0 && j > 0) { |
| 284 | if (compare(array1[i - 1], array2[j - 1])) { |
| 285 | result.unshift(collect(i - 1, j - 1)); |
| 286 | i--; |
| 287 | j--; |
| 288 | } else { |
| 289 | if (arr[i - 1][j] > arr[i][j - 1]) { |
| 290 | i--; |
| 291 | } else { |
| 292 | j--; |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | return result; |
| 298 | }; |
| 299 | |
| 300 | |
| 301 | /** |
| 302 | * Returns the sum of the arguments. |
| 303 | * @param {...number} var_args Numbers to add. |
| 304 | * @return {number} The sum of the arguments (0 if no arguments were provided, |
| 305 | * {@code NaN} if any of the arguments is not a valid number). |
| 306 | */ |
| 307 | goog.math.sum = function(var_args) { |
| 308 | return /** @type {number} */ (goog.array.reduce(arguments, |
| 309 | function(sum, value) { |
| 310 | return sum + value; |
| 311 | }, 0)); |
| 312 | }; |
| 313 | |
| 314 | |
| 315 | /** |
| 316 | * Returns the arithmetic mean of the arguments. |
| 317 | * @param {...number} var_args Numbers to average. |
| 318 | * @return {number} The average of the arguments ({@code NaN} if no arguments |
| 319 | * were provided or any of the arguments is not a valid number). |
| 320 | */ |
| 321 | goog.math.average = function(var_args) { |
| 322 | return goog.math.sum.apply(null, arguments) / arguments.length; |
| 323 | }; |
| 324 | |
| 325 | |
| 326 | /** |
| 327 | * Returns the unbiased sample variance of the arguments. For a definition, |
| 328 | * see e.g. http://en.wikipedia.org/wiki/Variance |
| 329 | * @param {...number} var_args Number samples to analyze. |
| 330 | * @return {number} The unbiased sample variance of the arguments (0 if fewer |
| 331 | * than two samples were provided, or {@code NaN} if any of the samples is |
| 332 | * not a valid number). |
| 333 | */ |
| 334 | goog.math.sampleVariance = function(var_args) { |
| 335 | var sampleSize = arguments.length; |
| 336 | if (sampleSize < 2) { |
| 337 | return 0; |
| 338 | } |
| 339 | |
| 340 | var mean = goog.math.average.apply(null, arguments); |
| 341 | var variance = goog.math.sum.apply(null, goog.array.map(arguments, |
| 342 | function(val) { |
| 343 | return Math.pow(val - mean, 2); |
| 344 | })) / (sampleSize - 1); |
| 345 | |
| 346 | return variance; |
| 347 | }; |
| 348 | |
| 349 | |
| 350 | /** |
| 351 | * Returns the sample standard deviation of the arguments. For a definition of |
| 352 | * sample standard deviation, see e.g. |
| 353 | * http://en.wikipedia.org/wiki/Standard_deviation |
| 354 | * @param {...number} var_args Number samples to analyze. |
| 355 | * @return {number} The sample standard deviation of the arguments (0 if fewer |
| 356 | * than two samples were provided, or {@code NaN} if any of the samples is |
| 357 | * not a valid number). |
| 358 | */ |
| 359 | goog.math.standardDeviation = function(var_args) { |
| 360 | return Math.sqrt(goog.math.sampleVariance.apply(null, arguments)); |
| 361 | }; |
| 362 | |
| 363 | |
| 364 | /** |
| 365 | * Returns whether the supplied number represents an integer, i.e. that is has |
| 366 | * no fractional component. No range-checking is performed on the number. |
| 367 | * @param {number} num The number to test. |
| 368 | * @return {boolean} Whether {@code num} is an integer. |
| 369 | */ |
| 370 | goog.math.isInt = function(num) { |
| 371 | return isFinite(num) && num % 1 == 0; |
| 372 | }; |
| 373 | |
| 374 | |
| 375 | /** |
| 376 | * Returns whether the supplied number is finite and not NaN. |
| 377 | * @param {number} num The number to test. |
| 378 | * @return {boolean} Whether {@code num} is a finite number. |
| 379 | */ |
| 380 | goog.math.isFiniteNumber = function(num) { |
| 381 | return isFinite(num) && !isNaN(num); |
| 382 | }; |
| 383 | |
| 384 | |
| 385 | /** |
| 386 | * Returns the precise value of floor(log10(num)). |
| 387 | * Simpler implementations didn't work because of floating point rounding |
| 388 | * errors. For example |
| 389 | * <ul> |
| 390 | * <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3. |
| 391 | * <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15. |
| 392 | * <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1. |
| 393 | * </ul> |
| 394 | * @param {number} num A floating point number. |
| 395 | * @return {number} Its logarithm to base 10 rounded down to the nearest |
| 396 | * integer if num > 0. -Infinity if num == 0. NaN if num < 0. |
| 397 | */ |
| 398 | goog.math.log10Floor = function(num) { |
| 399 | if (num > 0) { |
| 400 | var x = Math.round(Math.log(num) * Math.LOG10E); |
| 401 | return x - (parseFloat('1e' + x) > num); |
| 402 | } |
| 403 | return num == 0 ? -Infinity : NaN; |
| 404 | }; |
| 405 | |
| 406 | |
| 407 | /** |
| 408 | * A tweaked variant of {@code Math.floor} which tolerates if the passed number |
| 409 | * is infinitesimally smaller than the closest integer. It often happens with |
| 410 | * the results of floating point calculations because of the finite precision |
| 411 | * of the intermediate results. For example {@code Math.floor(Math.log(1000) / |
| 412 | * Math.LN10) == 2}, not 3 as one would expect. |
| 413 | * @param {number} num A number. |
| 414 | * @param {number=} opt_epsilon An infinitesimally small positive number, the |
| 415 | * rounding error to tolerate. |
| 416 | * @return {number} The largest integer less than or equal to {@code num}. |
| 417 | */ |
| 418 | goog.math.safeFloor = function(num, opt_epsilon) { |
| 419 | goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0); |
| 420 | return Math.floor(num + (opt_epsilon || 2e-15)); |
| 421 | }; |
| 422 | |
| 423 | |
| 424 | /** |
| 425 | * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for |
| 426 | * details. |
| 427 | * @param {number} num A number. |
| 428 | * @param {number=} opt_epsilon An infinitesimally small positive number, the |
| 429 | * rounding error to tolerate. |
| 430 | * @return {number} The smallest integer greater than or equal to {@code num}. |
| 431 | */ |
| 432 | goog.math.safeCeil = function(num, opt_epsilon) { |
| 433 | goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0); |
| 434 | return Math.ceil(num - (opt_epsilon || 2e-15)); |
| 435 | }; |