Skip to content

Conversation

lionel-rowe
Copy link
Contributor

@lionel-rowe lionel-rowe commented Sep 12, 2025

Closes #6803

  • clamp: Clamp a number within a range. Based on Math.clamp proposal, with notable differences being:
    • The current proposal specifies TypeErrors for NaN inputs, whereas my version returns NaNs, because throwing TypeErrors seems rather unexpected given the corresponding behavior of Math.min and Math.max.
    • [min, max] as a tuple rather than two separate arguments. I think this way works better for @std (to keep number of args within the style guide) and TypeScript (friendly tuple handling), plus it makes it visually apparent which arg is the input number and which are the limits.
  • modulo: Floor modulo operation, in contrast with JS's % remainder operator. Based on Python's % (floor modulo) operator and modulus math proposal.
  • round-to: Round a number to a specified number of decimal places using various rounding strategies. Basically Number#toFixed but returning a number instead of a string.

Probably worth kicking the wheels for things like floating-point rounding issues, in cases where those are avoidable or optimizable.

@lionel-rowe lionel-rowe requested a review from kt3k as a code owner September 12, 2025 13:50
@lionel-rowe lionel-rowe force-pushed the math-package branch 4 times, most recently from 5b9bc11 to 98eb5e1 Compare September 12, 2025 14:00
Copy link

codecov bot commented Sep 12, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.17%. Comparing base (91d1d55) to head (be0467a).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6823   +/-   ##
=======================================
  Coverage   94.16%   94.17%           
=======================================
  Files         573      577    +4     
  Lines       42400    42430   +30     
  Branches     6732     6741    +9     
=======================================
+ Hits        39928    39958   +30     
  Misses       2422     2422           
  Partials       50       50           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lionel-rowe lionel-rowe force-pushed the math-package branch 2 times, most recently from dac2f0f to 132a0be Compare September 13, 2025 08:02
Comment on lines +23 to +32
export function modulo(num: number, modulus: number): number {
if (!Number.isFinite(num) || Number.isNaN(modulus) || modulus === 0) {
return NaN;
}
if (num === 0) return modulus < 0 ? -0 : 0;
if (modulus === Infinity) return num < 0 ? Infinity : num;
if (modulus === -Infinity) return num > 0 ? -Infinity : num;

return (num % modulus + modulus) % modulus;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an alternative implementation for modulo.

export function modulo(num: number, modulus: number): number {
  num %= modulus;
  if (num === 0) {
    num = modulus < 0 ? -0 : 0;
  } else if ((num < 0) !== (modulus < 0)) {
    num += modulus;
  }
  return num;
}

In my benchmarks (code below), it was faster than the current implementation in the common case where both inputs are finite and nonzero, although slower when either input is infinite, NaN or 0. All tests still pass.

// `baseline` and `modulo` are different implementations.
// `cases` is taken from the python parity test.
const sink = new Set<number>();
for (const [a, b] of cases) {
  const group = Deno.inspect([a, b]);

  Deno.bench("baseline", { group }, () => {
    sink.add(baseline(a, b));
  });

  Deno.bench("modulo", { group }, () => {
    sink.add(modulo(a, b));
  });
}

Comment on lines +20 to +22
export function clamp(num: number, limits: [min: number, max: number]): number {
const [min, max] = limits;
if (min > max) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Passing limits as an array means that this array needs to be allocated prior to passing it to this function. Given that math functions are often used in high performance scenarios it might be more desirable to avoid the need to allocate at all for determining whether a value is in-between two others.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Common math utilities
3 participants