UNPKG

@devexperts/dxcharts-lite

Version:
159 lines (158 loc) 5.44 kB
/* * Copyright (C) 2019 - 2025 Devexperts Solutions IE Limited * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { MINUS_SIGN, TRUE_MINUS_SIGN } from './symbol-constants'; const MAX_DECIMAL_DIGITS = 14; // Array of powers of 10. Used in roundDecimal to walk through mantissa. const POW10 = []; for (let i = 0; i < MAX_DECIMAL_DIGITS + 1; i++) { POW10.push(Math.pow(10, i)); } const EPS = 5e-13; export class MathUtils { static roundToNearest(value, precision) { if (!isFinite(value)) { return value; } if (MathUtils.isZero(value)) { return 0.0; } if (value > 0) { value += EPS; } else if (value < 0) { value -= EPS; } return MathUtils.roundDecimal(Math.round(MathUtils.roundDecimal(value / precision)) * precision); } static roundUpToNearest(value, precision) { return MathUtils.roundDecimal(Math.ceil(value / precision)) * precision; } static roundDecimal(x) { if (isNaN(x) || x === Math.floor(x)) { return x; } // integer, NaN, or +/- inf const signum = Math.sign(x); const abs = Math.abs(x); const pow = Math.min(MAX_DECIMAL_DIGITS, MAX_DECIMAL_DIGITS - 1 - Math.floor(Math.log10(abs))); for (let i = pow; i >= 0; i--) { const mantissa = Math.floor(POW10[i] * abs + 0.5); if (mantissa < POW10[MAX_DECIMAL_DIGITS]) { return (signum * mantissa) / POW10[i]; } } // Mantissa >= 10^14 with fractions -- just round return Math.round(x); } static makeDecimal(value, precision, decimal, thousandsSeparator) { if (!isFinite(value)) { return ''; } if (isFinite(value)) { let stringValue = value.toFixed(precision); if (!thousandsSeparator) { return decimal ? stringValue.replace('.', decimal) : stringValue; } if (decimal === thousandsSeparator) { throw new Error('Grouping symbol cannot be the same as decimal separator.'); } const splittedValue = stringValue.split('.'); let integerPart = splittedValue[0]; const fractionPart = splittedValue[1]; if (Math.abs(value) >= 1000) { integerPart = integerPart.replace(RegExp('\\d(?=(\\d{3})+$)', 'g'), `$&${thousandsSeparator}`); } if (fractionPart) { stringValue = [integerPart, fractionPart].join(decimal); return stringValue; } else { return integerPart; } } return ''; } static compare(a, b, eps) { if (a > b + eps) { return +1; } else if (a < b - eps) { return -1; } else { return (isNaN(a) ? 1 : 0) - (isNaN(b) ? 1 : 0); } } static isZero(a) { return MathUtils.compare(a, 0, EPS) === 0; } static cutNumber(value, amountToCut, zeros = 0) { const cutMap = { K: v => v / 1000, M: v => v / 1000000, }; return cutMap[amountToCut](value).toFixed(zeros) + amountToCut; } static isExponential(a) { return /\de(\-|\+)\d/.test(a.toString()); } } /** * Checks if first and second number are differs by specified times * @param first {number} * @param second {number} * @param times {number} * @doc-tags utility,math */ export function isDiffersBy(first, second, times) { const diff = first / second; return diff >= times || diff <= 1 / times; } export function clamp(value, min, max) { return Math.max(min, Math.min(value, max)); } export function easeExpOut(value) { return 1 - (Math.pow(2, -10 * value) - 0.0009765625) * 1.0009775171065494; } /** * Returns the first finite number in a list of numbers. If no finite number is found, * returns NaN. * @param {...number} args - A list of numbers to search for a finite value. * @returns {number} The first finite number in the list, or NaN if no finite number is found. */ export function finite(...args) { for (const arg of args) { if (arg !== undefined && isFinite(arg)) { return arg; } } return NaN; } /** * These functions can be used only for chart coordinates because they all are working in positive coordinates!!! */ // eslint-disable-next-line no-bitwise export const floor = (value) => ~~value; // eslint-disable-next-line no-bitwise export const ceil = (value) => ~~(value + 1); // eslint-disable-next-line no-bitwise export const round = (value) => ~~(value + 0.5); // eslint-disable-next-line no-bitwise export const shiftRight = (value, shift) => value >> shift; export function countDecimalPlaces(number) { if (!Number.isFinite(number)) { throw new Error('Input must be a finite number.'); } const numberString = number.toString(); if (numberString.includes('.')) { return numberString.split('.')[1].length; } else { return 0; // No decimal places } } export const replaceMinusSign = (stringValue) => { return stringValue.replace(TRUE_MINUS_SIGN, MINUS_SIGN); };