Hacker News new | past | comments | ask | show | jobs | submit login
Math.min(Math.max(num, min), max) (twitter.com/jaffathecake)
238 points by tosh on Aug 20, 2020 | hide | past | favorite | 271 comments



I find that the fact that the functions min and max have the same name as the variables min and max increases cognitive load which makes it harder to think about it.

I find the following easier to read :

  Math.min(Math.max(num, lower_bound), upper_bound)


Easy to remember, but may take some time to grasp:

  Arrays.sort( {lower_bound, num, upper_bound} )[1];
Next challenge: teach the optimizer to make that almost as fast as the min/max way ;-)

(You can’t reduce it to the min/max call because it also works if you accidentally pass a lower bound that’s larger than the upper bound. Worst-case, the above takes 3 comparisons, unless at least two of the inputs are constants)


> Next challenge: teach the optimizer to make that almost as fast as the min/max way ;-)

I did exactly this for my PhD!

https://chrisseaton.com/phd/


I'm glad things like this are being worked on. I have been writing a set of implementations of the nBody benchmark in JavaScript using various forms of abstractions. In an ideal world they should all take the same speed since they perform the same fundamental task and produce the same result. They just represent different scales of optimization effort.

It's interesting seeing the difference between vectors in arrays vs objects and if you do immutable versions. The trickiest to optimize form is using a micro vector library which uses closures and array map().

    var vop = op => ((a, b) => (a.map((v, i) => op(v, b[i]))));
    var vdiff = vop((a, b) => a - b);
    var vequals = (a, b) => {  return (vdiff(a, b).reduce((c, d) => c + Math.abs(d), 0)) === 0;};
    var vadd = vop((a, b) => a + b);
    var vdot = (a, b) => a.reduce((ac, av, i) => ac += av * b[i], 0);
    var vlength = a => Math.sqrt(vdot(a, a));
    var vscale = (a, b) => a.map(v => v * b);
    var vdistance = (a, b) => vlength(vdiff(a, b));
Currently on my lowly atom laptop and FireFox. The version using mutable objects is ten times faster than the mapping immutable array version. I live in hope that one day there will be an optimizer that turns

    var transpose = matrix => (matrix.reduce(($, row) => row.map((_, i) => [...($[i] || []), row[i]]), []));
    var multiply = (a, b, ...rest) => (!b) ? a : multiply(a.map((p => transpose(b).map(q => vdot(p, q)))), ...rest);
Into a CPU optimum (or dare I suggest GPU) version of a matrix multiply.


For those reading, this work went into TruffleRuby, which implements Ruby on top of Truffle/GraalVM.


Very noble, but I'm assuming (hoping) the technique will be more generally applicable than Ruby?


Wait, how is that not trivial?


> how is that not trivial?

It could be trivial to implement an optimisation which does this for that exact code. But what are you going to do? Hand-code an optimisation for every similar thing people could write? I implemented a general solution.

So it also works through metaprogramming:

    [1, 2, 3].send(:sort).send(:[], 1)
Through user-defined sorting order:

    [1, 2, 3].sort_by { |a, b| b <=> a }[1]
When nested:

    [[1, 2].sort[1], 3].sort[0]
And so on.

Note that it also needs to be transparent to debuggers and profilers, it needs to handle multiple method redefinitions (for example what happens if someone redefines the sorting order for integers).

It's not a pattern-matching optimization - it's partial evaluation enabled by a new kind of polymorphic inline cache.


> But what are you going to do? Hand-code an optimisation for every similar thing people could write?

While I appreciate that you solved the general problem, I wonder if there are legs here. Specifically, could one mine GitHub to find automatically common patterns that one could write specific optimizers for, or at minimum, leverage that to learn what semi-general cases are worth optimizing? To my knowledge optimizing compilers already do have effectively handlers for common operations, but I don’t know if anyone has leveraged “big data” to help guide this.

IIRC, various tweaks and optimizations in Java were guided by Sun analyzing their own code based. GitHub is just so much bigger, and polyglot.


It's not just a hardcoded optimization for that construct.


You know what's great about that? The order of the arguments doesn't matter. So all the debate about "should it be num, min, max or min, num, max"- your solution does not care. Put them in any order you like!

You've redefined the problem from clamping a given value into picking the middle value from 3. This is a lovely way to re-interpret it.


Well, the order of the arguments does matter. Sorting three values requires 2.67 comparisons where clamping a value between two other values requires exactly 2. There are plenty of contexts where cleverly avoiding a problem by doing 33% more work isn't viewed as desirable.


True, but I think there are more situations where minimizing the chance of programmer error, now or in the future, is more important than a micro-optimization that will never matter. All depends on context.


Beware that in JavaScript, the language that this tweet is about, the sort function sorts alphabetically by default.

    [100, 10, 11].sort()[1] === 100


Most popular programming language folks. You give it 3 numbers and it treats them as 3 strings of characters...


Wait, really?

BRB, just checking some code…


Yep, the sort method converts values to strings before comparing. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


I was confused why the author claims this doesn't work, then I saw JavaScript... ah


If you are going to call a library function, just call median().


I think some of us (most? all?) develop a tendency to act/be clever instead of act/be clear. When I resist that temptation, I ask for something like:

    clamped(num, range=[ lower_bound, upper_bound ]);
and then have no interest in how this actually gets implemented.


this doesn't work for NaN, e.g {0d, Double.NaN, 1d} returns 1d.


Does NaN have an "order" in the set of reals or integers or whatever? I would have no idea what to expect from `min(NaN, x)` or max same. But is it specified by an IEEE standard or something?


Both min and max should return NaN, if any of their parameters is NaN. Sorting can be defined where to place the NaNs (head/tail) but it's largely irrelevant in this case as simply the substitution won't be permitted by any compiler.

NaN is part of IEEE754 but of course it's not a 'real' number (integer numbers don't have NaNs)

Edit: you can consider NaN (and to a degree both infinities) as an exception, once it occurs - it has to be propagated. Any operation involving NaN should be returning NaN, any operation comparing NaN to anything has to return 'false'. That includes "if (NaN == NaN)". boolean isNaN(double d) is effectively "return d != d;"


> Both min and max should return NaN, if any of their parameters is NaN.

That's not a given, though. IEEE 754-2008 defined min and max as returning the non-NaN parameter. They have been removed in IEEE 754-2019 though.


The reference to IEEE 754 is made later on, mostly to answer the question posted. I meant regular functions in C alike languages - Math.min/max - java/javascript, fmin/fmax - C++. They do the "right" thing to propagate the NaN


No, the only valid result for NaN is NaN. Saturating range bounds are for real numbers.


No, it breaks the ordering requirements. NaN compares greater than and less than every number.

You can say that a call to max should return NaN if any argument is NaN, but you can't say the same about sorting. (For one thing... sorting an array doesn't return a scalar value.) Sorting is done with comparisons, and what happens if a NaN gets into the list of values will depend on which specific comparisons happen to be done.


The only reason I know what to expect is because Suckerpinch on YouTube made a video in which he managed to define a logic system using NaN and +∞, and does so by abusing min and max, among other expressions:

https://youtu.be/5TFDG-y-EHs


> Does NaN have an "order" in the set of reals or integers or whatever?

By definition, something that is not a number (real, integer, etc.) cannot be compared to something that is a number.


It depends on what space you're working on (e.g. the https://en.wikipedia.org/wiki/Extended_real_number_line define an order on the real field union {-∞, +∞}).


Yes, but in that context, ∞ is a number. We often interpret "NaN" to mean "infinity," but it only means "not a number." Maybe I'm being pedantic, but if we want a token representing infinity as a number, it ought not be called "not a number."


IEEE754 has both infinity and NaN. They are different. NaN is always the result of an invalid operation, such as trying to take the square root of a negative number. Infinity is for when the result would be valid, but is too large in magnitude to represent. There is both positive and negative infinity.


typeof NaN


So I tried a thing in python3.

    >>> max(float('nan'), 0)
    nan
    >>> max(0, float('nan'))
    0
Numpy works though

    >>> np.maximum(float('nan'), 0)
    nan
    >>> np.maximum(0, float('nan'))
    nan
Edit: Fixed numpy example.


The functions minNum and maxNum ([IEEE 754-2008, 5.3.1, p19]) take two arguments and return the min and max, respectively. They have the special, distinguished property that “if exactly one argument is NaN, they return the other. If both are NaN they return NaN.”

Source: http://tom7.org/nand/nand.pdf

Edit: As of 2019, the formerly required minNum, maxNum, minNumMag, and maxNumMag in IEEE 754-2008 are now deleted due to their non-associativity. [https://en.wikipedia.org/wiki/IEEE_754#2019]


And for this particular use case, numpy has .clip(), that takes:

    np.clip(ndarray, lower_bound, upper_bound)
And handles NaNs correctly.


Having a NaN at that point feels like a bug anyway, the solution is probably to check the arguments and throw an exception if NaN is provided (or use an input type that doesn't allow invalid values)


That would depend on what you do with the NaNs. For instance I have been using them extensively in time series data representation to denote a specific entry has no value - think of Saturday and stock/forex markets.


That's not what NaN means. It means there is a value, and that value is... Check this out... "Not a Number".

Recording the lack of a value is what null is for.


Null does not pertain to primitive types in java and in C - it'd be zero when applied to a 'double'. (note: you want double[] as backing storage in java and you absolutely do not indirections). Aside that I have quite a good idea how NaN is represented internally and what it does, e.g. you can have several different NaNs that have different representation bitwise. Baring that - NaNs are pretty decent to represent lack of value as all operations with them result into a NaN. In the end NaN is just a composition of bits that the hardware can optimize for.


ohh this is clever!


Assuming we know (lower_bound <= upper_bound) I'd write:

Math.max(lower_bound, Math.min(num, upper_bound))

Since i read right to left.


It’s amazing what a difference it makes when you just good names and formatting


It’s usually nicer to make a helper function:

  const clamp = (x, low, high) =>
    Math.max(low, Math.min(x, high));
Then it can be easily used later via a descriptive name. e.g.

  color_component = clamp(color_component, 0, 255);


That doesn't really help. "Max" to enforce a "lower bound" is briefly halting.


I think the reason it's weird is that we might intuitively think of the "enforce a lower bound" function as taking two named arguments (lowerBound and inputValue) and the order of those two arguments mattering.

But of course, it turns out that the order of the arguments doesn't matter: applying a lowerBound of 5 to an inputValue of 100 turns out to be the exact same thing as applying a lowerBound of 100 to an inputValue of 5.

We know that the order of arguments doesn't matter for the Math.max function, so I think that's where the moment of incredulity comes from.


I agree.

But "min(-, constant_x)" should be thought of as "at most constant_x" and similarly for max. Maybe there's a way to make it more expressive.


I think your "at most" language is pretty expressive. You could do that as an alias for `min` and `max`

I think `at_most(at_least(num, lower_bound), upper_bound)` is much easier to understand instantly than `min(max(...))`.

I'm tempted to make these aliases myself in some of my development actually. I find a pretty big conceptual difference between "I want to find the minimum point in this data", and "I want to restrict the range of this number" that giving them different names will probably help the readability of my code.

(Of course, for `min(max(...))` I usually write a `clamp()` function to hide that for me, but someones I want to only clamp in one direction)


> (Of course, for `min(max(...))` I usually write a `clamp()` function to hide that for me, but someones I want to only clamp in one direction)

You could make clamp work in only one direction too. clamp(number, None, upper_bound) or the idiomatic equivalent in your language of choice seems pretty readable.


Maybe add an alias to Max called AtLeast and one for Min called AtMost to be used in these situations ;)


I would prefer it if the methods in java.lang.Math had been called "larger" and "smaller", instead of "min" and "max".

I sometimes mix up "min" as "take the minimum" rather than "take the larger given this minimum".


But "min" does take the minimum: `Math.min(1,3) == 1`


I think this was posted purely for the limerick quality.


  There was a young coder whose hacks
  His manager often claimed lacked
  The requisite clarity
  For to clamp vars would he:
  Math.min(Math.max(number, min), max);


    @jaffathecake had a problem of truncation
    and posted to Twitter his calculation.
    The gist of his attack
    was min( max( num, min ), max)
    yet refused to add any annotation.


In a file of utility hacks

near get(), post(), and ajax()

// we alias at import

// to keep all the code short

min(max(number, min), max)


Haiku is nice, too:

    Cryptic sorcerer!
    "Math.min(Math.max(v, min), max);"
    his incantation.


I find the extra words wordy.


I prefer "ceiling" and "floor", but yes, agreed.


Those also happen to be fairly common names for operations (rounding up or down to nearest integer).


I use ceil/floor (and make people use whenever I can) if something is going to happen when something hits the ceiling or drops to the floor. And avoid if it is just for clamping.


Luckily, this is a solved problem for go.

  func helper(a float64, c chan float64){
      time.Sleep(time.Duration(a) * time.Second)
      c <- a
  }
  
  func clamp(a float64, min float64, max float64) float64 {
      c := make(chan  float64, 3)
      go helper(a, c)
      go helper(min, c)
      go helper(max, c)
      _, out, _ := <-c, <-c, <-c
      return out
  }


Passing math.NaN() crashes in a playground.


Isn't this a race condition? https://play.golang.org/p/3Im8FuhyR_S


aka sleep sort


That will give you the median value. What OP wants is the value `a` clamped within `min` and `max`.


Can you give an example where median([a, min, max]) is not equal to clamp(a, min, max), given max >= min?


If a < min or a > max, if I'm reading it right.


You can prove that this algorithm works for both of those cases. Assuming min <= max:

If a < min, then the sorted array is [a, min, max]. The median is min, which is a clamped to min.

If a > max, then the sorted array is [min, max, a]. The median is max, which is a clamped to max.


In languages I use there’s usually no need to write that code.

C++/17 has std::clamp() in <algorithm> header.

Modern C# has Math.Clamp() since .NET Core 2.0; too bad it’s not available in desktop edition of the runtime.

HLSL has clamp() intrinsic function, and a special version saturate() to clamp into [ 0 .. +1 ] interval.


It gets a bit confusing when the order of arguments is different depending on the library. For instance, with std it's std::clamp(val, min, max), but with Qt it's qBound(min, val, max) (for some reason I think the order of arguments in qBound is more logical).


In Haskell, functions often take their arguments in the order that makes the most sense to partially apply. In this case, that would probably be clamp(min, max, val): supplying the first two arguments results in a reusable clamping function.


It would be nice if partial application worked with keyword arguments. Actually it would be great if Haskell natively supported keyword arguments.


The expression Math.min(Math.max(num, min), max) is symmetric in min and num, so it doesn’t matter whether you interchange min and num (or, for that matter, max and num, but that is harder to see from that way to define the ‘clamp’ function)


Good point, thanks.

(Though I've just checked gcc's definition of std::clamp and it looks like

    __glibcxx_assert(!(__hi < __lo));
    return (__val < __lo) ? __lo : (__hi < __val) ? __hi : __val;
so order of arguments does matter there.)


Self reply: Oops. That ‘for that matter’ part isn’t true, as it would mean you could permute the arguments any way you wished.


It depends. (val, min, max) operates on a first argument, which is more logical as well. (min, max, val) allows range constants to be more visible if <val> is a lenghty expression. In more powerful languages like objective-c this has less sense, as you can always specify all arguments explicitly:

  [FZUtilityManager clampIntegerValue:(NSInteger)x
                toRangeWithLowerBound:(NSInteger)min
                           upperBound:(NSInteger)max
                            withError:(out NSError **)error];
Which returns NSIntegerMax and sets the error variable if the range appears to be empty. The chance that max is NSIntegerMax is low, but if your data allows that, you can always put an additional shortcut before clamping.

  if (min > max) {
      error = [NSError errorWithDescription:@"min > max occured"];
      return NO; // or equivalent
  } else {
      x = ...
  }
  // use x


A good text editor solves this problem. I can vouch for emacs, but I would be shocked if there weren't a good way to get vim to do this for you.

The list of hard programming things is long; things that are trivially solved by a tool shouldn't be in the list.


> Modern C# has Math.Clamp() since .NET Core 2.0; too bad it’s not available in desktop edition of the runtime.

Huh, that’s good to know. It’s also in .net standard 2.1. A shame it wasn’t added to Framework 4.8 (which I guess is what you mean by "desktop edition of the runtime"?)


C++17 does have it... but it doesn't compile optimally. It compiles to something based on comparisons instead of the floating-point-specific operations. I tried this on a number of compiler combinations and didn't see anything that would emit min/max instructions for `std::clamp<double>`.

https://www.godbolt.org/z/MKqTvE


Your two functions are not equivalent. std::fmax handles NaN, but std::clamp does not.


Depends on how the comparisons are ordered. Some of the orderings I've seen in here do respect NaN by virtue of `x > upper_bound` comparing false if either x or upper_bound are NaN.


Both functions are defined how they handle NaN by the standard. There is no ordering or implementation ambiguity by design.


I had no idea .NET has Clamp() now. I’ve been writing something like the OP all this time ️


> in desktop edition of the runtime

huh?


MS .NET framework. Unfortunately, for the last couple years it lags behind .NET core. Even 2 years old .NET core 2.1 is better in some regards than the latest desktop version 4.8.


.NET Framework is now in maintenance mode because they're transitioning to .NET Core (soon called as .NET 5). I would call .NET Framework as Legacy edition rather than Desktop edition


.NET 5 will be based on .NET Core. It's already in preview.


Great. But I have been programming since about 1985.


I’ve been programming for living since 2000, but I don’t think that’s relevant. No reason not to use what’s available in standard libraries of whatever language you’re writing.

For example, C++ on AMD64 is very likely to compile std::clamp<double> into 2 instructions, minsd and maxsd. I’m not so sure about nested ternaries mentioned elsewhere in the comments.


https://godbolt.org/z/bq5E8h

It's actually the std::min/std::max version that goes to minsd/maxsd in both clang/gcc.


There are very good reasons to avoid std:: stuff.

And if you don't already know that in your soul, I will appear to be a genuine crackpot, and the reasons not to use std::* will still exist.


I think you’re overgeneralizing here.

I’m aware some parts of C++ standard library are outright horrible, like iostream and I/O in general. Other parts are questionable, like date & time, locales, and futures.

Meanwhile, other parts of the same standard library are actually OK (most collections, threading, synchronization, atomics, smart pointers, initializer lists). And other parts are awesome, like most of the stuff from <algorithm> header.

Apparently, one of the C++ design goals was to not pay for features which aren’t used. Selectively ignoring stuff from the standard library doesn’t have much downsides.


Are you saying a C++ developer shouldn't use the standard library?


Not the person you are replying to, but "a lot of the standard library is horrifying and should never be touched" is pretty standard advice for C++. Mind, I don't think this particular function belongs to that set.


Many performance-critical C++ programmers treat std with suspicion. One thing to keep in mind is that the interface is standard, but the implementations are not, and can foil you on cross-platform development. Another is that you might not need everything that a std container provides, and you can get away with a streamlined data structure that doesn't support those unnecessary operations.

But as a sibling commenter notes... this isn't relevant for min/max.


I'd much rather work on an application that utilized the standard lib than one that brought in dependencies and custom data structures.

If it really is a bottleneck on a hot path then go for it. But not using it because of some ancient anecdotes is going to lead to an unmaintainable mess.


Great. But I have been programming since about Math.min(Math.max(n, 1900), 1985 - 1)


Your database on https://mecoffee.nl seems to be down

> Fout bij het maken van de databaseconnectie


Always good to know the underlying implementation, but for any Ruby readers check out clamp(). It's been available since 2.4.

  25.clamp(5, 10)
  => 10

  6.clamp(5, 10)
  => 6

  1.clamp(5, 10)
  => 5


I often see [a, b, c].sort[1] which I think is very neat.


I'd say the only way this would be better than min/max solution is that you can't accidentally flip it when writing the code - but IMO both suck at expressing intent, clamp reads unambiguously.


Yes but its too many operations for such a simple problem. No need to overwork the system when two conditionals can solve the problem.


If you are writing code that is clamping a lot of numbers, the GC churn from building a new array every time might be a problem.


Ideally the temporary array should optimise away.


Ideally, but so far my experiences with simple benchmarks and with ruby-prof have shown that CRuby doesn't make such optimizations:

https://repl.it/repls/GargantuanThistleLink

    Result from sort: 3 in 0.9785124980007822s
    Allocated 3000001 object(s)
    Result from ternary: 3 in 0.3205206830025418s
    Allocated 1 object(s)
    Result from clamp: 3 in 0.5030354310001712s
    Allocated 2 object(s)
Interestingly the ternary comparison is faster than clamp.


I find myself needing this most frequently in making graphics in R. The scales package has squish() with the same behavior:

squish(25, c(5, 10)) => 10

squish(6, c(5, 10)) => 6

squish(1, c(5, 10)) => 5

If you don't provide the limits it defaults to c(0, 1). That's because this function exists to map to a 0-to-1 range for functions that then map the [0, 1] range to a color ramp.


Stage 1 ECMAScript proposal to add Math.clamp (among others): https://github.com/rwaldron/proposal-math-extensions

But it looks dead: https://github.com/rwaldron/proposal-math-extensions/issues/...

As someone mentioned in the thread, nested ternary is easier to interpret:

(a > max ? max : (a < min ? min : a))


To each his own but I disagree. Nested ternary's are hard to read and understand, and modifying them (by future devs) is tricky and error prone.


True, nested ternaries can be hard to follow. And the excessive parentheses promote this way of looking at it.

OTOH I think chained ternaries can be simple and easy to understand.

Yes, they are the exact same thing in this case, but getting rid of those nested parens really helps, at least for me.

sarah180's example is a good illustration. I would change the order of the tests because it makes more sense to me to check the min before the max. I'd also make one minor formatting change, because I code in a proportional font and can't line things up in columns:

  a < min ? min :
  a > max ? max :
  a
Maybe people think differently, but to me that is super easy to understand, and much better than the confusing Math.min/max stuff.

I would also wrap the whole thing inside a function:

  function clamp( value, min, max ) {
      return(
          value < min ? min :
          value > max ? max :
          value
      );
  }
Now that it's inside a function, you could change the code to use if statements, or Math.min/max, or whatever suits your preferences.


I don't find this a whole lot easier to read to be honest. It seems like doing minification manually, when we have tools to do that for us. An if statement seems a lot clearer, and minifies well https://twitter.com/jaffathecake/status/1296423819238944768


It's just a matter of what you're used to. Both of those are equally clear to me.


At the risk of starting a style debate, this may be easier to read if you write it like an if-else:

    a > max ? max :
    a < min ? min :
              a


Unless, of course, your language "designer" has no idea why every other language uses right-associative ternary operators.


This was a "fun" bug to track down in a PHP codebase.


68K assembly, no branches:

  # d0 = num, d1 = low, d2 = high, d3 = scratch
  sub.l   d1, d0
  subx.l  d3, d3
  or.l    d3, d0
  addx.l  d1, d0  # d0 = max(num,low)
  sub.l   d2, d0
  subx.l  d3, d3
  and.l   d3, d0
  add.l   d2, d0  # d0 = min(max(num,low),high)
Adapted from "Superoptimizer -- A Look at the Smallest Program" by Henry Massalin [1].

[1] https://web.stanford.edu/class/cs343/resources/superoptimize...


Note that with modern branch predictors such optimizations may not actually be beneficial–ARM got rid of condition codes on all its instructions in the 64-bit version. (Plus, I assume they ran out of space to encode them.)


IEEE754 has defined min and max for quite some time. Nowadays almost all FPUs have min and max instructions built-in.


Isn't .l implied if it is omitted?

If I just say "sub d1, d2", of course I mean the whole register width, and not just 16 or 8 bits of it.


OPs implementation requires 2 comparisons. I think this C expression is clearer, and will sometimes use 1 comparison:

    num < min ? min :
    num > max ? max :
    num


Incidentally, gcc and clang both compile this to two cmovs for me.


I don't speak x86. Do you mean that these compilers are evaluating both conditions, all the time? At what optimization level?


x86 has instructions that execute conditionally. Most conditionals in higher level languages get compiled to conditional jumps, but using conditional operations this isn't necessary. The same code path is taken in all cases, and the instructions effect is what is conditional.

In the case of cmov, it is either a nop or a mov dependent on the state of the conditional flags. Using this construct instead of a regular mov guarded by conditional jumps has better performance in some cases.

On my machine gcc is outputting a combination of conditional jumps and conditional movs at all optimization levels


Yes, you can check on godbolt. At -O{1,2,3}. At -Os, clang still generates cmovs, but gcc generates a jump.


Branching is the expensive part, not comparisons.


This is 3 branches. The Math method ends up with 4.

This is one of those cases where I think it is much more readable to just write the code than to puzzle over what Math.min(Math.max(min, num), max); might be doing.

    if (num < min)
      return min;
    if (num > max)
      return max;
    return num;
That's how I'd write it. May not be super terse, but anyone that stumbles on this will know precisely what's happening without needing to take a few seconds to puzzle things out.


What you want is something that compiles to conditional moves. A good compiler should compile your proposed version to the same machine code as the max & min method. If for one reason or another it compiles to branches, it’ll end up being slower.

In Javascript in my browser (Safari) when these methods get called enough to get compiled using the most aggressive stage of the JIT, they end up essentially the same speed. On my laptop either one runs about 145 million times per second on one CPU core.

I wouldn’t be surprised if a C compiler ended up making this function significantly faster, but I haven’t tested it.


To me the danger of this kind of one liners is that if you use it 2 times somewhere, someone, at some point, will do the lazy refactor of “let’s put it into a function”.

  int clamp (int value, int min, int max)
And that one liner inside. And now you have a double evaluation bomb waiting to go out on your codebase.


Why? You wrote a function, not a macro.


Accidentally overwrote the middle step:

Junior developer puts the one liner on google, finds the typical macro definition and does the change.


Maybe they mean that you might have been unintentionally relying on double evaluation, and then you take it away, and something breaks. Because it worked for the wrong reasons.


Kotlin provides pretty nice syntax sugar for that:

   num.coerceIn(min..max)
That's it. This human reader finds it considerably more readable.

It also has

   coerceAtLeast
and

   coerceAtMost


This is what I love about kotlin. The standard library has a good coverage of these kinds of problems and it is done in a simple an clean way. I had the same feeling with Python, the language got you cover with mundane tasks and you don't have to spend time researching libs that do it for you (or spend your time doing a clamp implementation + tests).


Does kotlinc optimize away inline ranges like that, or does this result in a range object being constructed and discarded?


I haven't heard of any instances of Kotlin itself optimizing these things away, but the JVM may be able to do so during its various JIT passes. It's definitely not something you can necessarily rely on, though.

Luckily, these convenience methods are usually implemented as inline extension functions, so the whole thing will get inlined into the calling method, making JIT optimization more likely.


  [min, num, max].sort()[1]


sort() on JavaScript arrays sorts alphabetically (unless you pass a compareFunction)

https://stackoverflow.com/questions/21019902/why-cant-javasc...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Good point, for those who need to implement a slick compareFunction for numbers, Math.sign is great:

  [min, num, max].sort((a, b) => Math.sign(a - b))[1]
An entirely different alternative is poor man's match:

  switch (true) {
  case num > max: return max;
  case num < min: return min;
  default: return num;
  }


Why sign though? I always write my sorts like this.

    .sort((a, b) => a - b)


Is there a better way to grab a reference to the subtraction operator?


I assume you're referring to a point-free reference.

Only by defining a `const subtract = (a, b) => a - b;`, which isn't really point-free.

Or by using a different language.


Wait, really? I just had to double-check my one JS project for bugs and either I used to know this gotcha or I got lucky. My sort()-ing needed to handle NaNs carefully so I was already using custom comparators.


"lexicographically" is a better descriptor than "alphabetically", since it's sorting by UTF-8 code point value.


It sorts by UTF-16 code unit.


Of course, my bad.


Very good point. I thought of it more as pseudocode.


imho a very elegant solution and probably works in most languages, I was surprised by JavaScript's behaviour myself


For a real fun headscratcher consider [ '9', '10', '11', '12', '13' ].map(parseInt)


Thank you for this exercise! I figured it out!

[ROT 13] spoiler: cnefrVag frpbaq nethzrag vf onfr, znc frpbaq nethzrag vf vaqrk va neenl. 10 vf gur frpbaq nethzrag, vaqrk 1. cnefrVag(10, 1) unf vyyrtny onfr

[0] https://rot13.com/


[min, num, max].sort()[1]

I really love this! I’m not sure whether to laugh, cry, or applaud, but I love how it makes me feel all those emotions at the same time.


I also think the fact it doesn't work in JS (due to sort defaulting to alphabetical sort) brings in even more appropriate emotions.


    [10, 2, 100].sort()[1] //returns 100


This is why computers are slow.


I was curious how slow this would be, and here is what JavaScriptCore made of this code:

  function clamp(n, min, max) {
      return [min, n, max].sort((a, b) => a - b)[1];
  }
  
  for (var i = 0; i < 10000000; i++) {
      clamp(Math.random() | 0, Math.random() | 0, Math.random() | 0);
  }
However, I was pretty disappointed when it seemed to be calling sort each time :( Perhaps I profiled it incorrectly? jsc's profiling data shows that it never hit FTL and nothing ever got inlined. The bytecode for DFG and Baseline is identical:

  Compilation clamp#CChm91-1-Baseline:
        arg0: predicting OtherObj
        arg1: predicting BoolInt32
        arg2: predicting BoolInt32
        arg3: predicting BoolInt32
          [   0] enter              
          [   1] get_scope          loc4
          [   3] mov                loc5, loc4
          [   6] check_traps        
          [   7] mov                loc11, arg2
          [  10] mov                loc12, arg1
          [  13] mov                loc13, arg3
          [  16] new_array          loc10, loc11, 3, 3
          [  22] get_by_id          loc7, loc10, 0
          [  27] new_func_exp       loc9, loc4, 0
          [  31] call               loc7, loc7, 2, 16
          [  37] get_by_val         loc6, loc7, Int32: 1(const0)
          [  42] ret                loc6

  Compilation clamp#CChm91-2-DFG:
        arg0: predicting OtherObj
        arg1: predicting BoolInt32
        arg2: predicting BoolInt32
        arg3: predicting BoolInt32
          [   0] enter              
          [   1] get_scope          loc4
          [   3] mov                loc5, loc4
          [   6] check_traps        
          [   7] mov                loc11, arg2
          [  10] mov                loc12, arg1
          [  13] mov                loc13, arg3
          [  16] new_array          loc10, loc11, 3, 3
          [  22] get_by_id          loc7, loc10, 0
          [  27] new_func_exp       loc9, loc4, 0
          [  31] call               loc7, loc7, 2, 16
          [  37] get_by_val         loc6, loc7, Int32: 1(const0)
          [  42] ret                loc6


Dude, you are generating random numbers inside the loop. That is going to dominate runtime.


I'm not testing runtime, though, I'm testing whether clamp gets optimized.


No. Just things written by web devs.

(Shots fired)


This is also nice because it gracefully handles the case where `max < min`.


It's not always graceful to say that 1 is both less than 0 and greater than 2.


Not sure what you mean. If I want to clamp 1 between 2 and 0, the most reasonable answer is 1, which is correctly returned by this code.


He's saying that, if you have explicitly defined a max and min such that max < min, it is not graceful for the computer to produce a result as though those values were swapped. In other words, garbage in should produce garbage out.

The array implementation sidesteps this by not semantically defining a max and min, instead sorting three arbitrary numbers.


In practice “max” and “min” often aren’t conceptually important for a clamp function. It’s more that you want to keep a value within a range, which is defined by two end points in arbitrary order.

If that is the version of clamp you need, the sort based solution reveals something profound and unexpected: it’s not just the two end points of the range that are equivalent, but all three numbers. Keeping value A between B and C is the same as keeping B between A and C or C between A and B. It’s completely arbitrary which pair you consider to be a range.


Why do you prefer to leave undefined behavior?


I'm not sure what you mean. The behavior of the max() and min() functions is perfectly well defined. The terms "maximum" and "minimum" are well defined. If I were using those terms I would likely consider the case where max < min to be an error, or have some other meaning, like an empty range.

If I wanted it to automatically flip the values to ensure a sensible range is defined, I would probably use "a" and "b" or "endpoint1" and "endpoint2" or something, because "max" has now become "max or min," which is not the same.


It's cute but if you used this in an innerloop (like a game, simulation or graphics code where 'clamp' is used often), it'll generate a ton of garbage as well as potential slowdown for no good reason.


This should be completely obvious, but largely irrelevant to the topic at hand. The linked tweet talks about how difficult it is to remember the order of the terms when you implement clamp a certain way; I just wanted to point out that with a different solution, the order surprisingly doesn’t matter at all.


I don't think it's obvious though - many people have never optimised for garbage collection so I thought it was worth pointing out.

I still found your approach interesting and it's rare there's a perfect approach so don't take it to heart.


I love this one.


never thought of clamp as precompiled sort


I think the main hurdle is that we need to read this inside-out, which is something we face regularly trying to read nested function calls. I seriously believe pipe-like operators solve this problem cleanly.

In Elixir which does have the pipe `|>` this could be.

    number = number |> max(lower_bound) |> min(upper_bound)
Which IMO is quite elegant.


Note: I don't really know Elixir, all I know is that this works in repl.it


I use Elixir day to day, and I agree, piping is one of the nicest things about it. And your code is the way I would have written it.


It is simpler if you use the notation [x]_a^b (i.e. with a subscript and a superscript b) to mean x, clipped to the range a to b, and skip writing +/- infinity if you don't intend clipping on one side.

Then you get a bunch of obvious identities like [x]^b = min(x, b) = [b]^x (x capped by b is the same as the smaller of x and b which is the same as b capped by x), [x]_a^b = [b]_a^x, and [x]_a^b = [[x]_a]^b. Putting these together you get [x]_a^b = [[x]_a]^b = min(max(x, a), b). But honestly it's just easier to stick to the notation most of the time.

A better write-up, for everyone who doesn't like reading new math notations inline: https://imgur.com/gallery/593QEow (Imgur link with white background) https://quicklatex.com/cache3/71/ql_46c49ac709b3789482d0736d... (Original link - renders badly in Chrome due to PNG transparency)


Those are all valid C.


Very coincidental I see this post almost immediately after writing the same code:

    new_poll_rate = \
        min(
            max(
                1 / messages_per_second,
                constants.FASTEST_POLL_RATE
            ),
            constants.SLOWEST_POLL_RATE
        )
I agree with the sentiment, I had to re-read this several times to make sure I got it right.


If fastest poll rate > slowest poll rate, I think you've got them the wrong way around (or is that the joke?).


FASTEST_POLL_RATE is 1000hz, SLOWEST_POLL_RATE is 10hz. Thus, FASTEST_POLL_RATE is a smaller number on a per-second basis.


Ah, I see - for clarity I'd rename them FASTEST_POLL_RATE -> SHORTEST_POLL_PERIOD or store them in Hz rather than seconds, so everything was 1/ in that little snippet. Thanks for clearing up my confusion :)


Yeah that’s a very good call, I will probably rename these. Thanks for the critique!


It seems like they are just have bad names, as their unit probably is seconds not 1/seconds.


Not as elegant, but way more readable:

if num > max: num = max

if num < min: num = min


In Factor:

    : clamp ( x min max -- y ) [ max ] dip min ; inline
https://docs.factorcode.org/content/word-clamp,math.order.ht...


For casual readers, `dip` pops the top of the stack, executes a quotation, then pushes the top of the stack back on.

So this pops the max value off the stack, applies the quoted `max` word to the x and min stack values, then pops the max value back on the stack and applies the `min` word to the result and the max.


The basic function is simply defined as:

  function clamp(num, min, max) {
    return Math.max(min, Math.min(num, max));
  }
That is, if you don't try to do anything fancy and make any parameters optional. Lodash does and it makes the implementation much more complex.

  _.clamp(input: number, lower?: number, upper: number): number;
https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb7...


Or to make sure it's crystal clear what's going on:

    function clamp(num, min, max) {
      if (num > max)
        return max;
      if (num < min)
        return min;
      return num;
    }


Seriously. This is about a million times better.


Absolutely. With the above implementation, I can see exactly what's going on, with the others, I'm trying to work out potential edge cases.


I prefer the min/max-based definition for the same reason: Its easier to work out the edge cases around NaN and comparisons against NaN.


NaN always returns false on comparisons. So seems pretty straight forward that this would return NaN if NaN is passed in.

That doesn't seem unreasonable. (In fact, that's what happens with the min/max approach).


Speaking only to JS is there any reason to write it any other way outside of being clever or as a lambda for singular use? I definitely prefer this version. (Assuming any necessary runtime checks are included for a given project)


There are many reasons to forego readability, especially when writing a library: performance, compatibility, requirements, interpreter/compiler optimizations or even cyclomatic complexity.

In lodash's case it might even be all of the above, although I can't speak for the intentions of the authors since there are no comments to guide readers through the process.

Note GP's link points to what looks like the v3 branch. Check out the latest implementation of clamp, with a few less if statements, and what looks like a NaN check using strict equality if you want your mind blown. https://github.com/lodash/lodash/blob/86a852fe763935bb64c125...


Reading that code it looks to me that:

clamp(null) returns 0

clamp(undefined) returns NaN

clamp(1, NaN, NaN) returns 0

clamp(1) returns 0

clamp(1, 5, NaN) returns 5

JavaScript is hard to write safe code for.


those extra newline characters slow down the page load :)


You’re joking right


Now in C# with pattern matching:

    int clamp(num, min, max)  
    {  
      return num switch  
             {  
               _ when num > max => max,  
               _ when num < min => min,  
               _ => num  
             };  
    }
Or as a lambda:

    Func<int, int, int, int> clamp = (num, min, max) => num switch {_ when num > max => max, _ when num < min => min,_ => num};



There’s an obvious solution to this:

    require('clamp')(num, min, max)
1 million monthly downloads


Ideally I'd also want to throw an error when max < min.


Kotlin implementation [1]:

    public fun Int.coerceIn(minimumValue: Int, maximumValue: Int): Int {
        if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum $maximumValue is less than minimum $minimumValue.")
        if (this < minimumValue) return minimumValue
        if (this > maximumValue) return maximumValue
        return this
    }
[1] https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/c...


Most of the ternary implementations here are less correct than the original. You generally want min/max to satisfy the following:

1. min/max of NaN and anything is NaN

2. negative 0 is strictly smaller than positive 0

it's a fun exercise to implement clamp under these constraints.


I'm not sure why the author isn't putting his preferred implementation into a clamp function then forgetting about it?


An amusing anecdote ruined by the fact that everyone who can relate immediately starts thinking of a better way to rewrite it. Yes brain that might be better but that's not the joke.


Fun seeing that pretty much everyone else finds that idiom confusing too. Half-serious, over breakfast:

    (case [(> n min) (< n max)]
      [true true] n
      [true false] max
      [false true] min)
(Side note: Clojure's `>` and `<` are kind of unreadable to begin with. Turning `if (> n min)` into "if n is greater than min" takes some work for me, still, after more than a year.)


However, prefix notation becomes much easier to read and reason about when you have more than two things to compare:

    (> 3 2 1 ...)
vs

    (3 > 2) && (2 > 1) && ...


Alternatively, since min and max are part of clojure.core:

   (-> n
       (max vmin)
       (min vmax))


> (Side note: Clojure's `>` and `<` are kind of unreadable to begin with.

I felt the same for awhile but now I just mentally put the operator between the operands, so (> 3 2) is the same as 3 > 2.


What about the [false false] case where min ≥ n ≥ max?


Do people actually have trouble with this repeatedly, or just when they first learn about it? I started using this implementation of clamp a few years ago, and while it gave me some trouble when I first implemented it, the pattern is very simple, and I got used to it very quickly.

I use Common Lisp:

> (max min (min max n))

Is the difference my choice of language, my personal mental hardware, the amount of familiarity one has with the pattern, or none of the above?


in k:

  xs: 1 2 3 4 5
  max: 4
  min: 2

  max& min| xs
  2 2 3 4 4
works for scalar, vector, matrix


After some of the recent J posts I spent a bit of time refreshing myself on it:

  max <. min >. nums
or equivalently:

  min >. max <. nums


K made me realize that if you use the numbers 0 and 1 for booleans, `min()` is `&&` and `max()` is `||`. love that!


same here, illuminating a-ha moment!


On the one hand, I've done this enough times that I can usually write it on auto-pilot without thinking about it. But on the other hand, whenever I make a mistake, the resulting bug is really hard to track down, because it's tough to just stare at the line and recognize that something wrong.


I find the following easier to visualize. If L is the lower bound and U is the upper bound, and if you visualize L, U as two points on the real number line, then:

left_of_U ( right_of_L (num)) = right_of_L ( left_of_U (num)) = clamped version of num between L and U.

Here, left_of_U = Math.min(num, U) and right_of_L = Math.max(num, L).


In APL:

    lower⌈ upper⌊ numbers
See a stream of random numbers flowing from right to left. See the higher ones being pushed down ⌊ to the upper bound, and the lower ones being pushed up ⌈ to the lower bound, and the middle ones flowing through both guards unchanged.


Excel has no clamp function but a neat alternative is

    =MEDIAN(min, num, max)


I find the following much easier to read. It's certainly easier to maintain, deal with, for successive programmers, and likely has exactly the same compiled/interpreted operations for most common languages. Optionally replace t1 and t2 by overwriting num if needed.

Use full if else if you must.

If you're worried about speed, using built in min and max is not a good idea. There are many, many tricks to remove branches for certain datatypes, etc., as you need.

var t1 = num < min ? min : num; // clamp to min var t2 = max < t1 ? max : t1; // clamp to max return t2; // num clamped to [min,max]


I like doing `Math.max(min, Math.min(num, max))` since it puts them in sort order.


Slightly offtopic, but very practical use of a comparable algorithm is to determine to what extent a date ranges falls between a first and last date (e.g. first date of the year, last date of year). e.g. in Excel it would look like: MAX((MIN(end date range; last date) - MAX(Start date range;first date) + 1);0)/(last date - first date + 1). This results in 0 in case of no match, a fraction when only a part of the date range falls between the first and last date, and 1 in case it matches completely or even exceeds the first and last date.


I personally would find this much more straightforward:

  val - (sign(val - max) + 1) / 2 * (val - max) + (sign(min - val) + 1) / 2 * (min - val)


I always used to get confused by the function names “min” and “max” because they return the minimum and maximum, but typically when you use them you are thinking in terms of applying a minimum or maximum bound. Having a dedicated clamp function significantly helps although imo there is not a single correct order for the parameters to go in.


  This is the TXR Lisp interactive listener of TXR 242.
  Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for  cheatsheet.
  1> (clamp 1 10 -1)
  1
  2> (clamp 1 10 15)
  10
  3> (clamp 1 10 5)
  5
Added on August 13, 2015 by commit f2e197dcd31d737bf23816107343f67e2bf6dd8e


For maximum readability, you could also go for

  (min + ((num + max - Math.abs(num - max)) / 2) + Math.abs(min - ((num + max - Math.abs(num - max)) / 2))) / 2
Since

  min(a, b) = (a + b - |a - b|) / 2
and

  max(a, b) = (a + b + |a - b|) / 2


Python also doesn't have a built-in clamp function (I think there might be one in numpy), I usually use

    sorted((floor, x, ceiling))[1]
Throw it in a function with some asserts if you're worried about x not being a number and giving you weird results.



You could use statistics.median (in python 3.6+)


Talking about obscurity of code, in Forth you would write:

  5 9 7 min max .
This prints 7, as it's between 5 (lower bound) and 9 (upper bound).

Defining a clamp "function" looks like this:

  : clamp ( min max n -- clamped_n )
    min max ;


Here's clamp in idiomatic Elixir (using multi-clause functions and guards):

  def clamp(min, _max, n) when n<min, do: min
  def clamp(_min, max, n) when n>max, do: max
  def clamp(_min, _max, n), do: n


An Elixir convention I've seen is to put the thing you're operating on first, so that you can compose functions using the `|>` operator, which places the previous expression as the first argument of the function to the right.

Maybe something like this?

  defmodule Compare do
    def clamp(number, minimum, maximum) do
      number
      |> max(minimum)
      |> min(maximum)
    end
  end
  
  import Compare
  
  clamp(5, 1, 10) # 5
  clamp(1, 5, 10) # 5
  clamp(10, 1, 5) # 5
  
  
  some_number
  |> clamp(min, max)

As a side note, I think the Elixir |> operator is a stroke of genius that other languages should take a look at. Making the pipe operator append the _first_ argument has the following benefits

1.) It makes the most "important" argument of the function the first thing you read in function signatures

2.) If you need to add more arguments to a function signature later, they tend to be less important the original args, so they tend to make sense at the end

3.) It creates a convention for all libraries to follow so they can leverage the pipe operator. Its really jarring when the thing you want to put in a pipeline isn't the first argument (looking at you `Regex`[0] which puts the regular expression as the first arg and not the string)

[0] https://hexdocs.pm/elixir/Regex.html#replace/4


> It makes the most "important" argument of the function the first thing you read in function signatures

Doesn't that make writing functions that can use partial application harder? e.g. If I was writing clamp i would want the signature to be

     (defn clamp [min max n] ,,,)
Then I can do:

    (map (partial clamp 1 11) [-14 2 5 8 11 15 18])
I know when I use Clojures threading macros I use thread last way more than any of the others. My next most common would be piping it into arbitrary locations, e.g.:

    ; pipe into an arbitrary spot (specified here as o)
    (as-> (range 1 10) o
          (map inc o)
          (filter even? o)
          (reduce + o))

I rarely use thread-first.


Elixir has an anonymous function short hand that makes the "partial application" use case pretty easy for me.

  &Compare.clamp(&1, some_min, some_max)
The above creates an arity one function which puts the arg into the first position in the original function.

I've never felt pain around partial application because I use the anonymous function short hand a good deal


Yes you are right! Puting the number in the first parameter is even more idiomatic Elixir.


  defmodule Math do
    def clamp(num, _min, max) when num > max, do: max
    def clamp(num, min, _max) when num < min, do: min
    def clamp(num, _min, _max), do: num
  end


Following xxs example of looking at NaN behavior, with this code, a bound (say lower) of NaN means that bound is disabled. Which may or may not be what you want.


> it takes me ages to convince myself this implementation is correct.

So never use it in code.



Numpy has `np.clip`.

In pure Python `statistics.median([min, max, num])` also works.


In Python, I usually put the following aliases:

  upperbound = min
  lowerbound = max
Use:

  a = upperbound(a, 10)
  b = lowerbound(-10, b)


> it takes me ages to convince myself this implementation is correct.

> Googler

Then this is another proof that google doesnt only hire on mathematical puzzle tricks


Math.min(a, x) == at_most_a(x)

Math.max(b, x) == at_least_b(x)

at_most_b(at_least_a(x))

A similar kind of thing is really common in measure theory (limsup, liminf) and option payoffs.


When making glsl shaders, there's clamp().


This is a great example of how haskell makes these things obvious ;)

    clamped = num `max` min' `min` max'


no idea what's going on there, I know some of those variables are actually functions but the whole thing is unreadable unless you have experience in haskell imo


The backticks turn regular functions into left-associative operators of the highest priority (by default).

So

    a `foo` b `bar` c
is

    (bar (foo a b) c)


Oohh nice. So kinda like a pipe.

I really think we deserve more syntax that allowed something like this, because otherwise one needs to read `(bar (foo a b) c)` inside-out.


> Oohh nice. So kinda like a pipe.

I never thought of it that way, but it is true that it can be used as a pipe.

Fundamentally it's just the dual of (): Haskell lets you use operators as infix functions by wrapping them in () so

    (+) 1 2
is the same as

    1 + 2
and conversely lets you use prefix (binary) functions as operators by wrapping them in backticks.

You can even combine those through sections, which serve to partially apply infix operators: https://wiki.haskell.org/Section_of_an_infix_operator


I made a simple clamp function based on this const clamp = (num, min=0,max=100) => Math.min(Math.max(num, min), max)


The fact that you need to type 33 characters to define something so simple as clamp() really does not make sense


This is something that upon seeing I would sketch out. It's way easier for me to understand visually


This one doesn't bother me much. What's harder and worse, IMO, is doing saturating math.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: