@devexperts/dxcharts-lite
Version:
155 lines (154 loc) • 5.26 kB
JavaScript
/*
* 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/.
*/
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, -5 * 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
}
}