math.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /*
  2. * @licstart The following is the entire license notice for the JavaScript
  3. * code in this page.
  4. *
  5. * This file is part of oddjobs-dmg-calc.
  6. *
  7. * oddjobs-dmg-calc is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU Affero General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * oddjobs-dmg-calc is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  14. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
  15. * License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with oddjobs-dmg-calc. If not, see <https://www.gnu.org/licenses/>.
  19. *
  20. * @licend The above is the entire license notice for the JavaScript code in
  21. * this page.
  22. */
  23. /**
  24. * Returns the sum of the set {`n`, `n`+1, `n`+2, ..., `m`-3, `m`-2, `m`-1}.
  25. * Note that the upper bound is exclusive. Another way of saying this is: the
  26. * sum of all of the integers in the interval [`n`, `m`).
  27. *
  28. * This function assumes that `m >= n`, and that both `m` and `n` are integers,
  29. * but usable results are still returned even if these assumptions are
  30. * violated. Swapping `n` and `m` just flips the sign of the result, and
  31. * non-integral values for `n` and/or `m` will yield intermediate values, as
  32. * expected.
  33. *
  34. * This function is so called because it computes the difference between a
  35. * larger triangular number and a smaller triangular number.
  36. */
  37. export function trapezoid(n: number, m: number): number {
  38. return (m * (m - 1) - n * (n - 1)) / 2;
  39. }
  40. /**
  41. * Returns the sum of the set {`n`^2, (`n`+1)^2, (`n`+2)^2, ..., (`m`-3)^2,
  42. * (`m`-2)^2, (`m`-1)^2}. Note that the upper bound is exclusive. Another way
  43. * of saying this is: the sum of the squares of all of the integers in the
  44. * interval [`n`, `m`).
  45. *
  46. * This function assumes that `m >= n`, and that both `m` and `n` are integers,
  47. * but usable results are still returned even if these assumptions are
  48. * violated. Swapping `n` and `m` just flips the sign of the result, and
  49. * non-integral values for `n` and/or `m` will yield intermediate values, as
  50. * expected.
  51. *
  52. * This function is so called because it computes the difference between a
  53. * larger square pyramidal number and a smaller square pyramidal number. It
  54. * should probably be called `squareFrustum`, but `frustum` is cuter.
  55. */
  56. export function frustum(n: number, m: number): number {
  57. return (m * (m - 1) * (2 * m - 1) - n * (n - 1) * (2 * n - 1)) / 6;
  58. }
  59. /**
  60. * Gets the expected value for a uniform distribution over the interval
  61. * [`min`, `max`] that is **_actually not uniform_**, because the outcomes are
  62. * clamped to a minimum of 1, **and** the outcomes' fractional parts are
  63. * truncated.
  64. */
  65. export function truncClampedExpectation(a: number, b: number): number {
  66. if (a === b) {
  67. return Math.max(Math.trunc(a), 1);
  68. }
  69. if (b >= 1) {
  70. const [aInt, bInt] = [Math.trunc(a), Math.trunc(b)];
  71. return (
  72. ((a >= 1 ? aInt * (1 - (a % 1)) : 2 - a) +
  73. trapezoid(Math.max(aInt + 1, 2), bInt) +
  74. bInt * (b % 1)) /
  75. (b - a)
  76. );
  77. }
  78. return 1;
  79. }
  80. /**
  81. * Gets the expected value for a uniform distribution over the interval
  82. * [`min`, `max`] that is **_actually not uniform_**, because the outcomes are
  83. * clamped to a minimum of 1.
  84. */
  85. /*
  86. function clampedExpectation(min: number, max: number): number {
  87. if (min >= 1) {
  88. return (min + max) / 2;
  89. }
  90. // The logic below is there because it's possible that the lower end of the
  91. // damage range (and possibly the higher end as well) is strictly less than
  92. // 1, in which case we no longer have a uniform distribution! This means
  93. // no simple `(minValue + maxValue) / 2` will calculate the expectation for
  94. // us. Instead, we have to split the distribution out into two parts: the
  95. // clamped bit (everything at or below 1), which is always clamped to an
  96. // outcome of 1, and the uniform bit (everything above 1). These are then
  97. // weighted and summed. Note that it's possible for the uniform bit to
  98. // have a measure/norm of zero (particularly, in the case that
  99. // `maxValue <= 1`).
  100. if (min >= max) {
  101. return 1;
  102. }
  103. const rawRangeNorm = max - min;
  104. const rawClampedNorm = Math.min(1 - min, rawRangeNorm);
  105. const uniformExpected = (1 + max) / 2;
  106. const clampedWeight = Math.min(rawClampedNorm / rawRangeNorm, 1);
  107. const uniformWeight = 1 - clampedWeight;
  108. return clampedWeight + uniformWeight * uniformExpected;
  109. }
  110. */
  111. /**
  112. * Gets the variance for a uniform distribution over the interval [`a`, `b`]
  113. * that is **_actually not uniform_**, because the outcomes are clamped to a
  114. * minimum of 1, **and** the outcomes' fractional parts are truncated. The
  115. * `mu` parameter is the expectation of the distribution, which can be obtained
  116. * from the `truncClampedExpectation` function.
  117. *
  118. * This function assumes that `b >= a`.
  119. *
  120. * This function computes the variance based on its definition. In LaTeX:
  121. *
  122. * ```latex
  123. * \operatorname{Var}(X) = \operatorname{E}[\left(X - \mu\right)^2]
  124. * ```
  125. */
  126. export function truncClampedVariance(
  127. a: number,
  128. b: number,
  129. mu: number,
  130. ): number {
  131. if (a === b || b <= 1) {
  132. return 0;
  133. }
  134. const [aInt, bInt] = [Math.trunc(a), Math.trunc(b)];
  135. const oneMinusMu = 1 - mu;
  136. const bIntMinusMu = bInt - mu;
  137. return (
  138. ((a >= 1
  139. ? (aInt - mu) ** 2 * (1 - (a % 1))
  140. : (2 - a) * oneMinusMu ** 2) +
  141. frustum(Math.max(aInt + oneMinusMu, 2 - mu), bIntMinusMu) +
  142. bIntMinusMu ** 2 * (b % 1)) /
  143. (b - a)
  144. );
  145. }
  146. /**
  147. * Gets the variance for a uniform distribution over the interval [`a`, `b`]
  148. * that is **_actually not uniform_**, because the outcomes are clamped to a
  149. * minimum of 1. The `mu` parameter is the expectation of the distribution,
  150. * which can be obtained from the `clampedExpectation` function.
  151. *
  152. * This function assumes that `b >= a`, so if `b < a && b > 1`, you will get
  153. * `undefined`.
  154. *
  155. * In LaTeX:
  156. *
  157. * ```latex
  158. * \sigma^2 = \begin{cases}
  159. * 0 & \text{when } a = b \lor b \leq 1 \\
  160. * \frac{(b - \mu)^3 - (\text{max}\left\{a, 1\right\} - \mu)^3}{3(b - a)} +
  161. * (1 - \mu)^2\,\text{max}\!\left\{\frac{1 - a}{b - a}, 0\right\} &
  162. * \text{when } b > \text{max}\!\left\{a, 1\right\}
  163. * \end{cases}
  164. * ```
  165. */
  166. /*
  167. function clampedVariance(
  168. a: number,
  169. b: number,
  170. mu: number,
  171. ): number | undefined {
  172. if (a === b || b <= 1) {
  173. return 0;
  174. }
  175. if (a > b) {
  176. return;
  177. }
  178. const bMinusA = b - a;
  179. return (
  180. ((b - mu) ** 3 - (Math.max(a, 1) - mu) ** 3) / (3 * bMinusA) +
  181. (1 - mu) ** 2 * Math.max((1 - a) / bMinusA, 0)
  182. );
  183. }
  184. */