number.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. /*
  41. * A third-party license is embeded for some of the code in this file:
  42. * The method "quantile" was copied from "d3.js".
  43. * (See more details in the comment of the method below.)
  44. * The use of the source code of this file is also subject to the terms
  45. * and consitions of the license of "d3.js" (BSD-3Clause, see
  46. * </licenses/LICENSE-d3>).
  47. */
  48. import * as zrUtil from 'zrender/lib/core/util';
  49. var RADIAN_EPSILON = 1e-4; // Although chrome already enlarge this number to 100 for `toFixed`, but
  50. // we sill follow the spec for compatibility.
  51. var ROUND_SUPPORTED_PRECISION_MAX = 20;
  52. function _trim(str) {
  53. return str.replace(/^\s+|\s+$/g, '');
  54. }
  55. /**
  56. * Linear mapping a value from domain to range
  57. * @param val
  58. * @param domain Domain extent domain[0] can be bigger than domain[1]
  59. * @param range Range extent range[0] can be bigger than range[1]
  60. * @param clamp Default to be false
  61. */
  62. export function linearMap(val, domain, range, clamp) {
  63. var d0 = domain[0];
  64. var d1 = domain[1];
  65. var r0 = range[0];
  66. var r1 = range[1];
  67. var subDomain = d1 - d0;
  68. var subRange = r1 - r0;
  69. if (subDomain === 0) {
  70. return subRange === 0 ? r0 : (r0 + r1) / 2;
  71. } // Avoid accuracy problem in edge, such as
  72. // 146.39 - 62.83 === 83.55999999999999.
  73. // See echarts/test/ut/spec/util/number.js#linearMap#accuracyError
  74. // It is a little verbose for efficiency considering this method
  75. // is a hotspot.
  76. if (clamp) {
  77. if (subDomain > 0) {
  78. if (val <= d0) {
  79. return r0;
  80. } else if (val >= d1) {
  81. return r1;
  82. }
  83. } else {
  84. if (val >= d0) {
  85. return r0;
  86. } else if (val <= d1) {
  87. return r1;
  88. }
  89. }
  90. } else {
  91. if (val === d0) {
  92. return r0;
  93. }
  94. if (val === d1) {
  95. return r1;
  96. }
  97. }
  98. return (val - d0) / subDomain * subRange + r0;
  99. }
  100. /**
  101. * Convert a percent string to absolute number.
  102. * Returns NaN if percent is not a valid string or number
  103. */
  104. export function parsePercent(percent, all) {
  105. switch (percent) {
  106. case 'center':
  107. case 'middle':
  108. percent = '50%';
  109. break;
  110. case 'left':
  111. case 'top':
  112. percent = '0%';
  113. break;
  114. case 'right':
  115. case 'bottom':
  116. percent = '100%';
  117. break;
  118. }
  119. if (typeof percent === 'string') {
  120. if (_trim(percent).match(/%$/)) {
  121. return parseFloat(percent) / 100 * all;
  122. }
  123. return parseFloat(percent);
  124. }
  125. return percent == null ? NaN : +percent;
  126. }
  127. export function round(x, precision, returnStr) {
  128. if (precision == null) {
  129. precision = 10;
  130. } // Avoid range error
  131. precision = Math.min(Math.max(0, precision), ROUND_SUPPORTED_PRECISION_MAX); // PENDING: 1.005.toFixed(2) is '1.00' rather than '1.01'
  132. x = (+x).toFixed(precision);
  133. return returnStr ? x : +x;
  134. }
  135. /**
  136. * Inplacd asc sort arr.
  137. * The input arr will be modified.
  138. */
  139. export function asc(arr) {
  140. arr.sort(function (a, b) {
  141. return a - b;
  142. });
  143. return arr;
  144. }
  145. /**
  146. * Get precision.
  147. */
  148. export function getPrecision(val) {
  149. val = +val;
  150. if (isNaN(val)) {
  151. return 0;
  152. } // It is much faster than methods converting number to string as follows
  153. // let tmp = val.toString();
  154. // return tmp.length - 1 - tmp.indexOf('.');
  155. // especially when precision is low
  156. // Notice:
  157. // (1) If the loop count is over about 20, it is slower than `getPrecisionSafe`.
  158. // (see https://jsbench.me/2vkpcekkvw/1)
  159. // (2) If the val is less than for example 1e-15, the result may be incorrect.
  160. // (see test/ut/spec/util/number.test.ts `getPrecision_equal_random`)
  161. if (val > 1e-14) {
  162. var e = 1;
  163. for (var i = 0; i < 15; i++, e *= 10) {
  164. if (Math.round(val * e) / e === val) {
  165. return i;
  166. }
  167. }
  168. }
  169. return getPrecisionSafe(val);
  170. }
  171. /**
  172. * Get precision with slow but safe method
  173. */
  174. export function getPrecisionSafe(val) {
  175. // toLowerCase for: '3.4E-12'
  176. var str = val.toString().toLowerCase(); // Consider scientific notation: '3.4e-12' '3.4e+12'
  177. var eIndex = str.indexOf('e');
  178. var exp = eIndex > 0 ? +str.slice(eIndex + 1) : 0;
  179. var significandPartLen = eIndex > 0 ? eIndex : str.length;
  180. var dotIndex = str.indexOf('.');
  181. var decimalPartLen = dotIndex < 0 ? 0 : significandPartLen - 1 - dotIndex;
  182. return Math.max(0, decimalPartLen - exp);
  183. }
  184. /**
  185. * Minimal dicernible data precisioin according to a single pixel.
  186. */
  187. export function getPixelPrecision(dataExtent, pixelExtent) {
  188. var log = Math.log;
  189. var LN10 = Math.LN10;
  190. var dataQuantity = Math.floor(log(dataExtent[1] - dataExtent[0]) / LN10);
  191. var sizeQuantity = Math.round(log(Math.abs(pixelExtent[1] - pixelExtent[0])) / LN10); // toFixed() digits argument must be between 0 and 20.
  192. var precision = Math.min(Math.max(-dataQuantity + sizeQuantity, 0), 20);
  193. return !isFinite(precision) ? 20 : precision;
  194. }
  195. /**
  196. * Get a data of given precision, assuring the sum of percentages
  197. * in valueList is 1.
  198. * The largest remainer method is used.
  199. * https://en.wikipedia.org/wiki/Largest_remainder_method
  200. *
  201. * @param valueList a list of all data
  202. * @param idx index of the data to be processed in valueList
  203. * @param precision integer number showing digits of precision
  204. * @return percent ranging from 0 to 100
  205. */
  206. export function getPercentWithPrecision(valueList, idx, precision) {
  207. if (!valueList[idx]) {
  208. return 0;
  209. }
  210. var sum = zrUtil.reduce(valueList, function (acc, val) {
  211. return acc + (isNaN(val) ? 0 : val);
  212. }, 0);
  213. if (sum === 0) {
  214. return 0;
  215. }
  216. var digits = Math.pow(10, precision);
  217. var votesPerQuota = zrUtil.map(valueList, function (val) {
  218. return (isNaN(val) ? 0 : val) / sum * digits * 100;
  219. });
  220. var targetSeats = digits * 100;
  221. var seats = zrUtil.map(votesPerQuota, function (votes) {
  222. // Assign automatic seats.
  223. return Math.floor(votes);
  224. });
  225. var currentSum = zrUtil.reduce(seats, function (acc, val) {
  226. return acc + val;
  227. }, 0);
  228. var remainder = zrUtil.map(votesPerQuota, function (votes, idx) {
  229. return votes - seats[idx];
  230. }); // Has remainding votes.
  231. while (currentSum < targetSeats) {
  232. // Find next largest remainder.
  233. var max = Number.NEGATIVE_INFINITY;
  234. var maxId = null;
  235. for (var i = 0, len = remainder.length; i < len; ++i) {
  236. if (remainder[i] > max) {
  237. max = remainder[i];
  238. maxId = i;
  239. }
  240. } // Add a vote to max remainder.
  241. ++seats[maxId];
  242. remainder[maxId] = 0;
  243. ++currentSum;
  244. }
  245. return seats[idx] / digits;
  246. }
  247. /**
  248. * Solve the floating point adding problem like 0.1 + 0.2 === 0.30000000000000004
  249. * See <http://0.30000000000000004.com/>
  250. */
  251. export function addSafe(val0, val1) {
  252. var maxPrecision = Math.max(getPrecision(val0), getPrecision(val1)); // const multiplier = Math.pow(10, maxPrecision);
  253. // return (Math.round(val0 * multiplier) + Math.round(val1 * multiplier)) / multiplier;
  254. var sum = val0 + val1; // // PENDING: support more?
  255. return maxPrecision > ROUND_SUPPORTED_PRECISION_MAX ? sum : round(sum, maxPrecision);
  256. } // Number.MAX_SAFE_INTEGER, ie do not support.
  257. export var MAX_SAFE_INTEGER = 9007199254740991;
  258. /**
  259. * To 0 - 2 * PI, considering negative radian.
  260. */
  261. export function remRadian(radian) {
  262. var pi2 = Math.PI * 2;
  263. return (radian % pi2 + pi2) % pi2;
  264. }
  265. /**
  266. * @param {type} radian
  267. * @return {boolean}
  268. */
  269. export function isRadianAroundZero(val) {
  270. return val > -RADIAN_EPSILON && val < RADIAN_EPSILON;
  271. } // eslint-disable-next-line
  272. var TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d{1,2})(?::(\d{1,2})(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line
  273. /**
  274. * @param value valid type: number | string | Date, otherwise return `new Date(NaN)`
  275. * These values can be accepted:
  276. * + An instance of Date, represent a time in its own time zone.
  277. * + Or string in a subset of ISO 8601, only including:
  278. * + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06',
  279. * + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01 12:22:33.123',
  280. * + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-01T12:22:33-05:00',
  281. * all of which will be treated as local time if time zone is not specified
  282. * (see <https://momentjs.com/>).
  283. * + Or other string format, including (all of which will be treated as loacal time):
  284. * '2012', '2012-3-1', '2012/3/1', '2012/03/01',
  285. * '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
  286. * + a timestamp, which represent a time in UTC.
  287. * @return date Never be null/undefined. If invalid, return `new Date(NaN)`.
  288. */
  289. export function parseDate(value) {
  290. if (value instanceof Date) {
  291. return value;
  292. } else if (typeof value === 'string') {
  293. // Different browsers parse date in different way, so we parse it manually.
  294. // Some other issues:
  295. // new Date('1970-01-01') is UTC,
  296. // new Date('1970/01/01') and new Date('1970-1-01') is local.
  297. // See issue #3623
  298. var match = TIME_REG.exec(value);
  299. if (!match) {
  300. // return Invalid Date.
  301. return new Date(NaN);
  302. } // Use local time when no timezone offset specifed.
  303. if (!match[8]) {
  304. // match[n] can only be string or undefined.
  305. // But take care of '12' + 1 => '121'.
  306. return new Date(+match[1], +(match[2] || 1) - 1, +match[3] || 1, +match[4] || 0, +(match[5] || 0), +match[6] || 0, match[7] ? +match[7].substring(0, 3) : 0);
  307. } // Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
  308. // https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment).
  309. // For example, system timezone is set as "Time Zone: America/Toronto",
  310. // then these code will get different result:
  311. // `new Date(1478411999999).getTimezoneOffset(); // get 240`
  312. // `new Date(1478412000000).getTimezoneOffset(); // get 300`
  313. // So we should not use `new Date`, but use `Date.UTC`.
  314. else {
  315. var hour = +match[4] || 0;
  316. if (match[8].toUpperCase() !== 'Z') {
  317. hour -= +match[8].slice(0, 3);
  318. }
  319. return new Date(Date.UTC(+match[1], +(match[2] || 1) - 1, +match[3] || 1, hour, +(match[5] || 0), +match[6] || 0, match[7] ? +match[7].substring(0, 3) : 0));
  320. }
  321. } else if (value == null) {
  322. return new Date(NaN);
  323. }
  324. return new Date(Math.round(value));
  325. }
  326. /**
  327. * Quantity of a number. e.g. 0.1, 1, 10, 100
  328. *
  329. * @param val
  330. * @return
  331. */
  332. export function quantity(val) {
  333. return Math.pow(10, quantityExponent(val));
  334. }
  335. /**
  336. * Exponent of the quantity of a number
  337. * e.g., 1234 equals to 1.234*10^3, so quantityExponent(1234) is 3
  338. *
  339. * @param val non-negative value
  340. * @return
  341. */
  342. export function quantityExponent(val) {
  343. if (val === 0) {
  344. return 0;
  345. }
  346. var exp = Math.floor(Math.log(val) / Math.LN10);
  347. /**
  348. * exp is expected to be the rounded-down result of the base-10 log of val.
  349. * But due to the precision loss with Math.log(val), we need to restore it
  350. * using 10^exp to make sure we can get val back from exp. #11249
  351. */
  352. if (val / Math.pow(10, exp) >= 10) {
  353. exp++;
  354. }
  355. return exp;
  356. }
  357. /**
  358. * find a “nice” number approximately equal to x. Round the number if round = true,
  359. * take ceiling if round = false. The primary observation is that the “nicest”
  360. * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
  361. *
  362. * See "Nice Numbers for Graph Labels" of Graphic Gems.
  363. *
  364. * @param val Non-negative value.
  365. * @param round
  366. * @return Niced number
  367. */
  368. export function nice(val, round) {
  369. var exponent = quantityExponent(val);
  370. var exp10 = Math.pow(10, exponent);
  371. var f = val / exp10; // 1 <= f < 10
  372. var nf;
  373. if (round) {
  374. if (f < 1.5) {
  375. nf = 1;
  376. } else if (f < 2.5) {
  377. nf = 2;
  378. } else if (f < 4) {
  379. nf = 3;
  380. } else if (f < 7) {
  381. nf = 5;
  382. } else {
  383. nf = 10;
  384. }
  385. } else {
  386. if (f < 1) {
  387. nf = 1;
  388. } else if (f < 2) {
  389. nf = 2;
  390. } else if (f < 3) {
  391. nf = 3;
  392. } else if (f < 5) {
  393. nf = 5;
  394. } else {
  395. nf = 10;
  396. }
  397. }
  398. val = nf * exp10; // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
  399. // 20 is the uppper bound of toFixed.
  400. return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
  401. }
  402. /**
  403. * This code was copied from "d3.js"
  404. * <https://github.com/d3/d3/blob/9cc9a875e636a1dcf36cc1e07bdf77e1ad6e2c74/src/arrays/quantile.js>.
  405. * See the license statement at the head of this file.
  406. * @param ascArr
  407. */
  408. export function quantile(ascArr, p) {
  409. var H = (ascArr.length - 1) * p + 1;
  410. var h = Math.floor(H);
  411. var v = +ascArr[h - 1];
  412. var e = H - h;
  413. return e ? v + e * (ascArr[h] - v) : v;
  414. }
  415. /**
  416. * Order intervals asc, and split them when overlap.
  417. * expect(numberUtil.reformIntervals([
  418. * {interval: [18, 62], close: [1, 1]},
  419. * {interval: [-Infinity, -70], close: [0, 0]},
  420. * {interval: [-70, -26], close: [1, 1]},
  421. * {interval: [-26, 18], close: [1, 1]},
  422. * {interval: [62, 150], close: [1, 1]},
  423. * {interval: [106, 150], close: [1, 1]},
  424. * {interval: [150, Infinity], close: [0, 0]}
  425. * ])).toEqual([
  426. * {interval: [-Infinity, -70], close: [0, 0]},
  427. * {interval: [-70, -26], close: [1, 1]},
  428. * {interval: [-26, 18], close: [0, 1]},
  429. * {interval: [18, 62], close: [0, 1]},
  430. * {interval: [62, 150], close: [0, 1]},
  431. * {interval: [150, Infinity], close: [0, 0]}
  432. * ]);
  433. * @param list, where `close` mean open or close
  434. * of the interval, and Infinity can be used.
  435. * @return The origin list, which has been reformed.
  436. */
  437. export function reformIntervals(list) {
  438. list.sort(function (a, b) {
  439. return littleThan(a, b, 0) ? -1 : 1;
  440. });
  441. var curr = -Infinity;
  442. var currClose = 1;
  443. for (var i = 0; i < list.length;) {
  444. var interval = list[i].interval;
  445. var close_1 = list[i].close;
  446. for (var lg = 0; lg < 2; lg++) {
  447. if (interval[lg] <= curr) {
  448. interval[lg] = curr;
  449. close_1[lg] = !lg ? 1 - currClose : 1;
  450. }
  451. curr = interval[lg];
  452. currClose = close_1[lg];
  453. }
  454. if (interval[0] === interval[1] && close_1[0] * close_1[1] !== 1) {
  455. list.splice(i, 1);
  456. } else {
  457. i++;
  458. }
  459. }
  460. return list;
  461. function littleThan(a, b, lg) {
  462. return a.interval[lg] < b.interval[lg] || a.interval[lg] === b.interval[lg] && (a.close[lg] - b.close[lg] === (!lg ? 1 : -1) || !lg && littleThan(a, b, 1));
  463. }
  464. }
  465. /**
  466. * [Numberic is defined as]:
  467. * `parseFloat(val) == val`
  468. * For example:
  469. * numeric:
  470. * typeof number except NaN, '-123', '123', '2e3', '-2e3', '011', 'Infinity', Infinity,
  471. * and they rounded by white-spaces or line-terminal like ' -123 \n ' (see es spec)
  472. * not-numeric:
  473. * null, undefined, [], {}, true, false, 'NaN', NaN, '123ab',
  474. * empty string, string with only white-spaces or line-terminal (see es spec),
  475. * 0x12, '0x12', '-0x12', 012, '012', '-012',
  476. * non-string, ...
  477. *
  478. * @test See full test cases in `test/ut/spec/util/number.js`.
  479. * @return Must be a typeof number. If not numeric, return NaN.
  480. */
  481. export function numericToNumber(val) {
  482. var valFloat = parseFloat(val);
  483. return valFloat == val // eslint-disable-line eqeqeq
  484. && (valFloat !== 0 || typeof val !== 'string' || val.indexOf('x') <= 0) // For case ' 0x0 '.
  485. ? valFloat : NaN;
  486. }
  487. /**
  488. * Definition of "numeric": see `numericToNumber`.
  489. */
  490. export function isNumeric(val) {
  491. return !isNaN(numericToNumber(val));
  492. }
  493. /**
  494. * Use random base to prevent users hard code depending on
  495. * this auto generated marker id.
  496. * @return An positive integer.
  497. */
  498. export function getRandomIdBase() {
  499. return Math.round(Math.random() * 9);
  500. }
  501. /**
  502. * Get the greatest common dividor
  503. *
  504. * @param {number} a one number
  505. * @param {number} b the other number
  506. */
  507. export function getGreatestCommonDividor(a, b) {
  508. if (b === 0) {
  509. return a;
  510. }
  511. return getGreatestCommonDividor(b, a % b);
  512. }
  513. /**
  514. * Get the least common multiple
  515. *
  516. * @param {number} a one number
  517. * @param {number} b the other number
  518. */
  519. export function getLeastCommonMultiple(a, b) {
  520. if (a == null) {
  521. return b;
  522. }
  523. if (b == null) {
  524. return a;
  525. }
  526. return a * b / getGreatestCommonDividor(a, b);
  527. }