number.js 19 KB

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