number.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  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 embedded 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.js';
  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 (zrUtil.isString(percent)) {
  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 remainder 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 seats = getPercentSeats(valueList, precision);
  211. return seats[idx] || 0;
  212. }
  213. /**
  214. * Get a data of given precision, assuring the sum of percentages
  215. * in valueList is 1.
  216. * The largest remainder method is used.
  217. * https://en.wikipedia.org/wiki/Largest_remainder_method
  218. *
  219. * @param valueList a list of all data
  220. * @param precision integer number showing digits of precision
  221. * @return {Array<number>}
  222. */
  223. export function getPercentSeats(valueList, precision) {
  224. var sum = zrUtil.reduce(valueList, function (acc, val) {
  225. return acc + (isNaN(val) ? 0 : val);
  226. }, 0);
  227. if (sum === 0) {
  228. return [];
  229. }
  230. var digits = Math.pow(10, precision);
  231. var votesPerQuota = zrUtil.map(valueList, function (val) {
  232. return (isNaN(val) ? 0 : val) / sum * digits * 100;
  233. });
  234. var targetSeats = digits * 100;
  235. var seats = zrUtil.map(votesPerQuota, function (votes) {
  236. // Assign automatic seats.
  237. return Math.floor(votes);
  238. });
  239. var currentSum = zrUtil.reduce(seats, function (acc, val) {
  240. return acc + val;
  241. }, 0);
  242. var remainder = zrUtil.map(votesPerQuota, function (votes, idx) {
  243. return votes - seats[idx];
  244. }); // Has remainding votes.
  245. while (currentSum < targetSeats) {
  246. // Find next largest remainder.
  247. var max = Number.NEGATIVE_INFINITY;
  248. var maxId = null;
  249. for (var i = 0, len = remainder.length; i < len; ++i) {
  250. if (remainder[i] > max) {
  251. max = remainder[i];
  252. maxId = i;
  253. }
  254. } // Add a vote to max remainder.
  255. ++seats[maxId];
  256. remainder[maxId] = 0;
  257. ++currentSum;
  258. }
  259. return zrUtil.map(seats, function (seat) {
  260. return seat / digits;
  261. });
  262. }
  263. /**
  264. * Solve the floating point adding problem like 0.1 + 0.2 === 0.30000000000000004
  265. * See <http://0.30000000000000004.com/>
  266. */
  267. export function addSafe(val0, val1) {
  268. var maxPrecision = Math.max(getPrecision(val0), getPrecision(val1)); // const multiplier = Math.pow(10, maxPrecision);
  269. // return (Math.round(val0 * multiplier) + Math.round(val1 * multiplier)) / multiplier;
  270. var sum = val0 + val1; // // PENDING: support more?
  271. return maxPrecision > ROUND_SUPPORTED_PRECISION_MAX ? sum : round(sum, maxPrecision);
  272. } // Number.MAX_SAFE_INTEGER, ie do not support.
  273. export var MAX_SAFE_INTEGER = 9007199254740991;
  274. /**
  275. * To 0 - 2 * PI, considering negative radian.
  276. */
  277. export function remRadian(radian) {
  278. var pi2 = Math.PI * 2;
  279. return (radian % pi2 + pi2) % pi2;
  280. }
  281. /**
  282. * @param {type} radian
  283. * @return {boolean}
  284. */
  285. export function isRadianAroundZero(val) {
  286. return val > -RADIAN_EPSILON && val < RADIAN_EPSILON;
  287. } // eslint-disable-next-line
  288. 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
  289. /**
  290. * @param value valid type: number | string | Date, otherwise return `new Date(NaN)`
  291. * These values can be accepted:
  292. * + An instance of Date, represent a time in its own time zone.
  293. * + Or string in a subset of ISO 8601, only including:
  294. * + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06',
  295. * + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01 12:22:33.123',
  296. * + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-01T12:22:33-05:00',
  297. * all of which will be treated as local time if time zone is not specified
  298. * (see <https://momentjs.com/>).
  299. * + Or other string format, including (all of which will be treated as local time):
  300. * '2012', '2012-3-1', '2012/3/1', '2012/03/01',
  301. * '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
  302. * + a timestamp, which represent a time in UTC.
  303. * @return date Never be null/undefined. If invalid, return `new Date(NaN)`.
  304. */
  305. export function parseDate(value) {
  306. if (value instanceof Date) {
  307. return value;
  308. } else if (zrUtil.isString(value)) {
  309. // Different browsers parse date in different way, so we parse it manually.
  310. // Some other issues:
  311. // new Date('1970-01-01') is UTC,
  312. // new Date('1970/01/01') and new Date('1970-1-01') is local.
  313. // See issue #3623
  314. var match = TIME_REG.exec(value);
  315. if (!match) {
  316. // return Invalid Date.
  317. return new Date(NaN);
  318. } // Use local time when no timezone offset is specified.
  319. if (!match[8]) {
  320. // match[n] can only be string or undefined.
  321. // But take care of '12' + 1 => '121'.
  322. 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);
  323. } // Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
  324. // https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment).
  325. // For example, system timezone is set as "Time Zone: America/Toronto",
  326. // then these code will get different result:
  327. // `new Date(1478411999999).getTimezoneOffset(); // get 240`
  328. // `new Date(1478412000000).getTimezoneOffset(); // get 300`
  329. // So we should not use `new Date`, but use `Date.UTC`.
  330. else {
  331. var hour = +match[4] || 0;
  332. if (match[8].toUpperCase() !== 'Z') {
  333. hour -= +match[8].slice(0, 3);
  334. }
  335. 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));
  336. }
  337. } else if (value == null) {
  338. return new Date(NaN);
  339. }
  340. return new Date(Math.round(value));
  341. }
  342. /**
  343. * Quantity of a number. e.g. 0.1, 1, 10, 100
  344. *
  345. * @param val
  346. * @return
  347. */
  348. export function quantity(val) {
  349. return Math.pow(10, quantityExponent(val));
  350. }
  351. /**
  352. * Exponent of the quantity of a number
  353. * e.g., 1234 equals to 1.234*10^3, so quantityExponent(1234) is 3
  354. *
  355. * @param val non-negative value
  356. * @return
  357. */
  358. export function quantityExponent(val) {
  359. if (val === 0) {
  360. return 0;
  361. }
  362. var exp = Math.floor(Math.log(val) / Math.LN10);
  363. /**
  364. * exp is expected to be the rounded-down result of the base-10 log of val.
  365. * But due to the precision loss with Math.log(val), we need to restore it
  366. * using 10^exp to make sure we can get val back from exp. #11249
  367. */
  368. if (val / Math.pow(10, exp) >= 10) {
  369. exp++;
  370. }
  371. return exp;
  372. }
  373. /**
  374. * find a “nice” number approximately equal to x. Round the number if round = true,
  375. * take ceiling if round = false. The primary observation is that the “nicest”
  376. * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
  377. *
  378. * See "Nice Numbers for Graph Labels" of Graphic Gems.
  379. *
  380. * @param val Non-negative value.
  381. * @param round
  382. * @return Niced number
  383. */
  384. export function nice(val, round) {
  385. var exponent = quantityExponent(val);
  386. var exp10 = Math.pow(10, exponent);
  387. var f = val / exp10; // 1 <= f < 10
  388. var nf;
  389. if (round) {
  390. if (f < 1.5) {
  391. nf = 1;
  392. } else if (f < 2.5) {
  393. nf = 2;
  394. } else if (f < 4) {
  395. nf = 3;
  396. } else if (f < 7) {
  397. nf = 5;
  398. } else {
  399. nf = 10;
  400. }
  401. } else {
  402. if (f < 1) {
  403. nf = 1;
  404. } else if (f < 2) {
  405. nf = 2;
  406. } else if (f < 3) {
  407. nf = 3;
  408. } else if (f < 5) {
  409. nf = 5;
  410. } else {
  411. nf = 10;
  412. }
  413. }
  414. val = nf * exp10; // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
  415. // 20 is the uppper bound of toFixed.
  416. return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
  417. }
  418. /**
  419. * This code was copied from "d3.js"
  420. * <https://github.com/d3/d3/blob/9cc9a875e636a1dcf36cc1e07bdf77e1ad6e2c74/src/arrays/quantile.js>.
  421. * See the license statement at the head of this file.
  422. * @param ascArr
  423. */
  424. export function quantile(ascArr, p) {
  425. var H = (ascArr.length - 1) * p + 1;
  426. var h = Math.floor(H);
  427. var v = +ascArr[h - 1];
  428. var e = H - h;
  429. return e ? v + e * (ascArr[h] - v) : v;
  430. }
  431. /**
  432. * Order intervals asc, and split them when overlap.
  433. * expect(numberUtil.reformIntervals([
  434. * {interval: [18, 62], close: [1, 1]},
  435. * {interval: [-Infinity, -70], close: [0, 0]},
  436. * {interval: [-70, -26], close: [1, 1]},
  437. * {interval: [-26, 18], close: [1, 1]},
  438. * {interval: [62, 150], close: [1, 1]},
  439. * {interval: [106, 150], close: [1, 1]},
  440. * {interval: [150, Infinity], close: [0, 0]}
  441. * ])).toEqual([
  442. * {interval: [-Infinity, -70], close: [0, 0]},
  443. * {interval: [-70, -26], close: [1, 1]},
  444. * {interval: [-26, 18], close: [0, 1]},
  445. * {interval: [18, 62], close: [0, 1]},
  446. * {interval: [62, 150], close: [0, 1]},
  447. * {interval: [150, Infinity], close: [0, 0]}
  448. * ]);
  449. * @param list, where `close` mean open or close
  450. * of the interval, and Infinity can be used.
  451. * @return The origin list, which has been reformed.
  452. */
  453. export function reformIntervals(list) {
  454. list.sort(function (a, b) {
  455. return littleThan(a, b, 0) ? -1 : 1;
  456. });
  457. var curr = -Infinity;
  458. var currClose = 1;
  459. for (var i = 0; i < list.length;) {
  460. var interval = list[i].interval;
  461. var close_1 = list[i].close;
  462. for (var lg = 0; lg < 2; lg++) {
  463. if (interval[lg] <= curr) {
  464. interval[lg] = curr;
  465. close_1[lg] = !lg ? 1 - currClose : 1;
  466. }
  467. curr = interval[lg];
  468. currClose = close_1[lg];
  469. }
  470. if (interval[0] === interval[1] && close_1[0] * close_1[1] !== 1) {
  471. list.splice(i, 1);
  472. } else {
  473. i++;
  474. }
  475. }
  476. return list;
  477. function littleThan(a, b, lg) {
  478. 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));
  479. }
  480. }
  481. /**
  482. * [Numeric is defined as]:
  483. * `parseFloat(val) == val`
  484. * For example:
  485. * numeric:
  486. * typeof number except NaN, '-123', '123', '2e3', '-2e3', '011', 'Infinity', Infinity,
  487. * and they rounded by white-spaces or line-terminal like ' -123 \n ' (see es spec)
  488. * not-numeric:
  489. * null, undefined, [], {}, true, false, 'NaN', NaN, '123ab',
  490. * empty string, string with only white-spaces or line-terminal (see es spec),
  491. * 0x12, '0x12', '-0x12', 012, '012', '-012',
  492. * non-string, ...
  493. *
  494. * @test See full test cases in `test/ut/spec/util/number.js`.
  495. * @return Must be a typeof number. If not numeric, return NaN.
  496. */
  497. export function numericToNumber(val) {
  498. var valFloat = parseFloat(val);
  499. return valFloat == val // eslint-disable-line eqeqeq
  500. && (valFloat !== 0 || !zrUtil.isString(val) || val.indexOf('x') <= 0) // For case ' 0x0 '.
  501. ? valFloat : NaN;
  502. }
  503. /**
  504. * Definition of "numeric": see `numericToNumber`.
  505. */
  506. export function isNumeric(val) {
  507. return !isNaN(numericToNumber(val));
  508. }
  509. /**
  510. * Use random base to prevent users hard code depending on
  511. * this auto generated marker id.
  512. * @return An positive integer.
  513. */
  514. export function getRandomIdBase() {
  515. return Math.round(Math.random() * 9);
  516. }
  517. /**
  518. * Get the greatest common divisor.
  519. *
  520. * @param {number} a one number
  521. * @param {number} b the other number
  522. */
  523. export function getGreatestCommonDividor(a, b) {
  524. if (b === 0) {
  525. return a;
  526. }
  527. return getGreatestCommonDividor(b, a % b);
  528. }
  529. /**
  530. * Get the least common multiple.
  531. *
  532. * @param {number} a one number
  533. * @param {number} b the other number
  534. */
  535. export function getLeastCommonMultiple(a, b) {
  536. if (a == null) {
  537. return b;
  538. }
  539. if (b == null) {
  540. return a;
  541. }
  542. return a * b / getGreatestCommonDividor(a, b);
  543. }