I find the following easier to read :
Math.min(Math.max(num, lower_bound), upper_bound)I find the following easier to read :
Math.min(Math.max(num, lower_bound), upper_bound) 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)
I did exactly this for my PhD!
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.
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 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.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;"
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.
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.
By definition, something that is not a number (real, integer, etc.) cannot be compared to something that is a number.
>>> 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. np.clip(ndarray, lower_bound, upper_bound)
And handles NaNs correctly.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]
Recording the lack of a value is what null is for.
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.
clamped(num, range=[ lower_bound, upper_bound ]);
and then have no interest in how this actually gets implemented. [100, 10, 11].sort()[1] === 100BRB, just checking some code…
Math.max(lower_bound, Math.min(num, upper_bound))
Since i read right to left.
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);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 `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)
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.
I sometimes mix up "min" as "take the minimum" rather than "take the larger given this minimum".
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.
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.near get(), post(), and ajax()
// we alias at import
// to keep all the code short
min(max(number, min), max)
Cryptic sorcerer!
"Math.min(Math.max(v, min), max);"
his incantation.