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.
Math.min(Math.max(num, min), max)
151–160 of 291 posts
Re: Math.min(Math.max(num, min), max)
#152Earlier quoted context omitted.
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
Unless, of course, your language "designer" has no idea why every other language uses right-associative ternary operators.
Re: Math.min(Math.max(num, min), max)
#15368K 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.)
Re: Math.min(Math.max(num, min), max)
#154Earlier quoted context omitted.
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?
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.
Re: Math.min(Math.max(num, min), max)
#155Earlier quoted context omitted.
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?
By definition, something that is not a number (real, integer, etc.) cannot be compared to something that is a number.
Re: Math.min(Math.max(num, min), max)
#156I 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)
Re: Math.min(Math.max(num, min), max)
#157In languages I use there’s usually no need to write that code. C++/17 has std::clamp() in 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.
Re: Math.min(Math.max(num, min), max)
#158Earlier quoted context omitted.
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)
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.
Re: Math.min(Math.max(num, min), max)
#159I 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)
clamped(num, range=[ lower_bound, upper_bound ]);
and then have no interest in how this actually gets implemented.