math.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /**
  2. * @module lib/math
  3. * This module contains various math functions - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#math)
  4. */
  5. import type { Stringifiable } from "./types.js";
  6. /** Ensures the passed {@linkcode value} always stays between {@linkcode min} and {@linkcode max} */
  7. export function clamp(value: number, min: number, max: number): number
  8. /** Ensures the passed {@linkcode value} always stays between 0 and {@linkcode max} */
  9. export function clamp(value: number, max: number): number
  10. /** Ensures the passed {@linkcode value} always stays between {@linkcode min} and {@linkcode max} - if `max` isn't given, it defaults to `min` and `min` defaults to 0 */
  11. export function clamp(value: number, min: number, max?: number): number {
  12. if(typeof max !== "number") {
  13. max = min;
  14. min = 0;
  15. }
  16. return Math.max(Math.min(value, max), min);
  17. }
  18. /**
  19. * Transforms the value parameter from the numerical range `range1min` to `range1max` to the numerical range `range2min` to `range2max`
  20. * For example, you can map the value 2 in the range of 0-5 to the range of 0-10 and you'd get a 4 as a result.
  21. */
  22. export function mapRange(value: number, range1min: number, range1max: number, range2min: number, range2max: number): number;
  23. /**
  24. * Transforms the value parameter from the numerical range `0` to `range1max` to the numerical range `0` to `range2max`
  25. * For example, you can map the value 2 in the range of 0-5 to the range of 0-10 and you'd get a 4 as a result.
  26. */
  27. export function mapRange(value: number, range1max: number, range2max: number): number;
  28. /**
  29. * Transforms the value parameter from one numerical range to another.
  30. * For example, you can map the value 2 in the range of 0-5 to the range of 0-10 and you'd get a 4 as a result.
  31. */
  32. export function mapRange(value: number, range1min: number, range1max: number, range2min?: number, range2max?: number): number {
  33. if(typeof range2min === "undefined" || typeof range2max === "undefined") {
  34. range2max = range1max;
  35. range1max = range1min;
  36. range2min = range1min = 0;
  37. }
  38. if(Number(range1min) === 0.0 && Number(range2min) === 0.0)
  39. return value * (range2max / range1max);
  40. return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
  41. }
  42. /**
  43. * Returns a random number between {@linkcode min} and {@linkcode max} (inclusive)
  44. * Set {@linkcode enhancedEntropy} to true to use `crypto.getRandomValues()` for better cryptographic randomness (this also makes it take longer to generate)
  45. */
  46. export function randRange(min: number, max: number, enhancedEntropy?: boolean): number
  47. /**
  48. * Returns a random number between 0 and {@linkcode max} (inclusive)
  49. * Set {@linkcode enhancedEntropy} to true to use `crypto.getRandomValues()` for better cryptographic randomness (this also makes it take longer to generate)
  50. */
  51. export function randRange(max: number, enhancedEntropy?: boolean): number
  52. /**
  53. * Returns a random number between {@linkcode min} and {@linkcode max} (inclusive)
  54. * Set {@linkcode enhancedEntropy} to true to use `crypto.getRandomValues()` for better cryptographic randomness (this also makes it take longer to generate)
  55. */
  56. export function randRange(...args: (number | boolean | undefined)[]): number {
  57. let min: number, max: number, enhancedEntropy = false;
  58. // using randRange(min, max)
  59. if(typeof args[0] === "number" && typeof args[1] === "number")
  60. [ min, max ] = args;
  61. // using randRange(max)
  62. else if(typeof args[0] === "number" && typeof args[1] !== "number") {
  63. min = 0;
  64. [ max ] = args;
  65. }
  66. else
  67. throw new TypeError(`Wrong parameter(s) provided - expected (number, boolean|undefined) or (number, number, boolean|undefined) but got (${args.map(a => typeof a).join(", ")}) instead`);
  68. if(typeof args[2] === "boolean")
  69. enhancedEntropy = args[2];
  70. else if(typeof args[1] === "boolean")
  71. enhancedEntropy = args[1];
  72. min = Number(min);
  73. max = Number(max);
  74. if(isNaN(min) || isNaN(max))
  75. return NaN;
  76. if(min > max)
  77. throw new TypeError("Parameter \"min\" can't be bigger than \"max\"");
  78. if(enhancedEntropy) {
  79. const uintArr = new Uint8Array(1);
  80. crypto.getRandomValues(uintArr);
  81. return Number(Array.from(
  82. uintArr,
  83. (v) => Math.round(mapRange(v, 0, 255, min, max)).toString(10),
  84. ).join(""));
  85. }
  86. else
  87. return Math.floor(Math.random() * (max - min + 1)) + min;
  88. }
  89. /**
  90. * Calculates the amount of digits in the given number - the given number or string will be passed to the `Number()` constructor.
  91. * Returns NaN if the number is invalid.
  92. * @param num The number to count the digits of
  93. * @param withDecimals Whether to count the decimal places as well (defaults to true)
  94. * @example ```ts
  95. * digitCount(); // NaN
  96. * digitCount(0); // 1
  97. * digitCount(123); // 3
  98. * digitCount(123.456); // 6
  99. * digitCount(Infinity); // Infinity
  100. * ```
  101. */
  102. export function digitCount(num: number | Stringifiable, withDecimals = true): number {
  103. num = Number((!["string", "number"].includes(typeof num)) ? String(num) : num);
  104. if(typeof num === "number" && isNaN(num))
  105. return NaN;
  106. const [intPart, decPart] = num.toString().split(".");
  107. const intDigits = intPart === "0"
  108. ? 1
  109. : Math.floor(Math.log10(Math.abs(Number(intPart))) + 1);
  110. const decDigits = withDecimals && decPart
  111. ? decPart.length
  112. : 0;
  113. return intDigits + decDigits;
  114. }
  115. /**
  116. * Rounds {@linkcode num} to a fixed amount of decimal places, specified by {@linkcode fractionDigits} (supports negative values to round to the nearest power of 10).
  117. * @example ```ts
  118. * roundFixed(234.567, -2); // 200
  119. * roundFixed(234.567, -1); // 230
  120. * roundFixed(234.567, 0); // 235
  121. * roundFixed(234.567, 1); // 234.6
  122. * roundFixed(234.567, 2); // 234.57
  123. * roundFixed(234.567, 3); // 234.567
  124. * ```
  125. */
  126. export function roundFixed(num: number, fractionDigits: number): number {
  127. const scale = 10 ** fractionDigits;
  128. return Math.round(num * scale) / scale;
  129. }
  130. /**
  131. * Checks if the {@linkcode bitSet} has the {@linkcode checkVal} set
  132. * @example ```ts
  133. * // the two vertically adjacent bits are tested for:
  134. * bitSetHas(
  135. * 0b1110,
  136. * 0b0010,
  137. * ); // true
  138. *
  139. * bitSetHas(
  140. * 0b1110,
  141. * 0b0001,
  142. * ); // false
  143. *
  144. * // with TS enums (or JS maps):
  145. * enum MyEnum {
  146. * A = 1, B = 2, C = 4,
  147. * D = 8, E = 16, F = 32,
  148. * }
  149. *
  150. * const myBitSet = MyEnum.A | MyEnum.B;
  151. * bitSetHas(myBitSet, MyEnum.B); // true
  152. * bitSetHas(myBitSet, MyEnum.F); // false
  153. * ```
  154. */
  155. export function bitSetHas<TType extends number | bigint>(bitSet: TType, checkVal: TType): boolean {
  156. return (bitSet & checkVal) === checkVal;
  157. }