Live data from Hacker News

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

twitter.com

181–190 of 291 posts

Re: Math.min(Math.max(num, min), max)

#181

>[...] it takes me ages to convince myself this implementation is correct. Is that supposed to be difficult? Not trying to be snarky, I'm honestly surprised, do people actually struggle with this? Googlers in particular?

x2

While doing competitive programming I learned that I'm really stupid, but even as stupid as I am, I still understand that expression easily.

Re: Math.min(Math.max(num, min), max)

#182
post #45

In 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.

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.

Re: Math.min(Math.max(num, min), max)

#183

Earlier quoted context omitted.

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.

Re: Math.min(Math.max(num, min), max)

#184
post #175

Earlier quoted context omitted.

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.

Re: Math.min(Math.max(num, min), max)

#185
post #121

Earlier quoted context omitted.

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].sor…

> 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.

Re: Math.min(Math.max(num, min), max)

#186
post #75
post #28

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)

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

Re: Math.min(Math.max(num, min), max)

#187
post #15

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

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

Re: Math.min(Math.max(num, min), max)

#188
post #30
post #28

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)

I think this was posted purely for the limerick quality.

Haiku is nice, too:

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

Re: Math.min(Math.max(num, min), max)

#189

Earlier quoted context omitted.

He's saying that, if you have explicitly defined a max and min such that max The array implementation sidesteps this by not semantically defining a max and min, instead sorting three arbitrary numbers.

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 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.

Re: Math.min(Math.max(num, min), max)

#190
post #158

Earlier quoted context omitted.

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

Post reply on HN