Skip to content

Proposal for clamp #319

Closed
Closed
@milancurcic

Description

@milancurcic

Intro

clamp(x, xmin, xmax) is a function that clamps (or, clips) an input scalar or array x so that the result is within given bounds xmin and xmax. For example:

res = clamp(0.9. 0.2, 0.8) ! res is 0.8
res = clamp(0.7. 0.2, 0.8) ! res is 0.7
res = clamp(0.1, 0.2, 0.8) ! res is 0.2

This function is common in standard libraries. I've used it occasionally in Fortran, and I use it often in Python. It provides an easy way to ensure that a variable with a finite (non-NaN, non-Inf) value stays within bounds.

Name

Python has numpy.clip, while C++, Julia, and Rust all have clamp. So clamp is more common. Both clamp and clip are equally meaningful to me. I have slight preference for clip because it's shorter and has a "sharper" sound to it.

What inspired me to open this proposal is reading that clamp was stabilized in Rust 1.50.0.

API

elemental real function clamp(x, xmin, xmax) result(res)
  real, intent(in) :: x ! input value to clip
  real, intent(in) :: xmin ! lower bound
  real, intent(in) :: xmax ! higher bound

Alternative names for the bounds could be low and high.

Implementation

There are a few options that I know of, each of which produce different assembly instructions, and performance depending on optimization levels, array sizes, and whether the clamping is in a loop or over a whole array in a single call.

A min/max clamp

This is the simplest implementation I could think of:

elemental real function clamp(x, xmin, xmax) result(res)
  real, intent(in) :: x, xmin, xmax
  res = min(max(x, xmin), xmax)
end function clamp

There are two intrinsic function calls here, but maybe a good compiler will optimize one away. I don't know.

An if/then/else clamp

elemental real function clamp(x, xmin, xmax) result(res)
  real, intent(in) :: x, xmin, xmax
  if (x < xmin) then
    res = xmin
  else if (x > xmax) then
    res = xmax
  else
    res = x
  end if
end function clamp

A branchless clamp

This avoids branching but introduces a floating-point error (~epsilon(x)) for some elements, even if x is not clipped.

elemental real function clamp(x, xmin, xmax) result(res)
  ! A branchless clamp.
  ! Credit: Mark Ransom (https://stackoverflow.com/a/7424900/827297)
  real, intent(in) :: x, xmin, xmax
  res = (x + xmin + abs(x - xmin)) / 2
  res = (res + xmax - abs(res - xmax)) / 2
end function clamp

What module would this go to?

I don't think we have an appropriate existing module yet, but stdlib_numeric or stdlib_math sound meaningful to me.

Metadata

Metadata

Assignees

Labels

easyDifficulty level is easy and good for starting into this projectgood first issueGood for newcomersideaProposition of an idea and opening an issue to discuss it

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions