Readit News logoReadit News
patresh · 5 years ago
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)

Someone · 5 years ago
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)

chrisseaton · 5 years ago
> 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/

mabbo · 5 years ago
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.

dfabulich · 5 years ago
Beware that in JavaScript, the language that this tweet is about, the sort function sorts alphabetically by default.

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

gowld · 5 years ago
If you are going to call a library function, just call median().
fizixer · 5 years ago
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.

xxs · 5 years ago
this doesn't work for NaN, e.g {0d, Double.NaN, 1d} returns 1d.
shepherdjerred · 5 years ago
ohh this is clever!
amichal · 5 years ago
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.

biddlesby · 5 years ago
It’s amazing what a difference it makes when you just good names and formatting
jacobolus · 5 years ago
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);

Deleted Comment

Dylan16807 · 5 years ago
That doesn't really help. "Max" to enforce a "lower bound" is briefly halting.
baddox · 5 years ago
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.

hyh1048576 · 5 years ago
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.

airstrike · 5 years ago
Maybe add an alias to Max called AtLeast and one for Min called AtMost to be used in these situations ;)
Symbiote · 5 years ago
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".

boxfire · 5 years ago
I think this was posted purely for the limerick quality.
hanoz · 5 years ago

  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);

jkaptur · 5 years ago
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)

gmfawcett · 5 years ago
Haiku is nice, too:

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

_y5hn · 5 years ago
I find the extra words wordy.

Deleted Comment

parliament32 · 5 years ago
I prefer "ceiling" and "floor", but yes, agreed.
gugagore · 5 years ago
Those also happen to be fairly common names for operations (rounding up or down to nearest integer).
KONAir · 5 years ago
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.
donquichotte · 5 years ago
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
  }

tl · 5 years ago
Passing math.NaN() crashes in a playground.
resist_futility · 5 years ago
Isn't this a race condition? https://play.golang.org/p/3Im8FuhyR_S
apta · 5 years ago
aka sleep sort
ccmcarey · 5 years ago
That will give you the median value. What OP wants is the value `a` clamped within `min` and `max`.
donquichotte · 5 years ago
Can you give an example where median([a, min, max]) is not equal to clamp(a, min, max), given max >= min?
Const-me · 5 years ago
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.

f00zz · 5 years ago
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).
nemetroid · 5 years ago
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.
Someone · 5 years ago
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)
wruza · 5 years ago
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

nwallin · 5 years ago
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.

Semaphor · 5 years ago
> 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"?)

brandmeyer · 5 years ago
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

ChrisLomont · 5 years ago
Your two functions are not equivalent. std::fmax handles NaN, but std::clamp does not.
OkGoDoIt · 5 years ago
I had no idea .NET has Clamp() now. I’ve been writing something like the OP all this time ️
gameswithgo · 5 years ago
> in desktop edition of the runtime

huh?

Const-me · 5 years ago
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.
the-dude · 5 years ago
Great. But I have been programming since about 1985.
Const-me · 5 years ago
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.

HugoDaniel · 5 years ago
Great. But I have been programming since about Math.min(Math.max(n, 1900), 1985 - 1)
zhte415 · 5 years ago
Your database on https://mecoffee.nl seems to be down

> Fout bij het maken van de databaseconnectie

mauricio · 5 years ago
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

chrisseaton · 5 years ago
I often see [a, b, c].sort[1] which I think is very neat.
rubber_duck · 5 years ago
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.
stickyricky · 5 years ago
Yes but its too many operations for such a simple problem. No need to overwork the system when two conditionals can solve the problem.
nitrogen · 5 years ago
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.
madcaptenor · 5 years ago
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.

rewq4321 · 5 years ago
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))

macspoofing · 5 years ago
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.
Stratoscope · 5 years ago
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.

Deleted Comment

jaffathecake · 5 years ago
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
balfirevic · 5 years ago
It's just a matter of what you're used to. Both of those are equally clear to me.
sarah180 · 5 years ago
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

KMag · 5 years ago
Unless, of course, your language "designer" has no idea why every other language uses right-associative ternary operators.
tzs · 5 years ago
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...

saagarjha · 5 years ago
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.)
brandmeyer · 5 years ago
IEEE754 has defined min and max for quite some time. Nowadays almost all FPUs have min and max instructions built-in.
kazinator · 5 years ago
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.

geophile · 5 years ago
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

edflsafoiewq · 5 years ago
Incidentally, gcc and clang both compile this to two cmovs for me.
geophile · 5 years ago
I don't speak x86. Do you mean that these compilers are evaluating both conditions, all the time? At what optimization level?
jacobolus · 5 years ago
Branching is the expensive part, not comparisons.
cogman10 · 5 years ago
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.

Deleted Comment

xondono · 5 years ago
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.

geophile · 5 years ago
Why? You wrote a function, not a macro.
V-2 · 5 years ago
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

sanbor · 5 years ago
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).
nitrogen · 5 years ago
Does kotlinc optimize away inline ranges like that, or does this result in a range object being constructed and discarded?
RandomBK · 5 years ago
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.