[HN Gopher] Math.min(Math.max(num, min), max) ___________________________________________________________________ Math.min(Math.max(num, min), max) Author : tosh Score : 177 points Date : 2020-08-20 13:33 UTC (9 hours ago) (HTM) web link (twitter.com) (TXT) w3m dump (twitter.com) | tosh wrote: | 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 | uryga wrote: | K made me realize that if you use the numbers 0 and 1 for | booleans, `min()` is `&&` and `max()` is `||`. love that! | tosh wrote: | same here, illuminating a-ha moment! | Jtsummers wrote: | 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 | nibbapower69 wrote: | The author is one condescending shitcoder lmao | sanbor wrote: | 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... | STRML wrote: | 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... | [deleted] | bonoboTP wrote: | 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; } | 52-6F-62 wrote: | 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) | chasd00 wrote: | those extra newline characters slow down the page load :) | tobyhinloopen wrote: | You're joking right | beirut_bootleg wrote: | 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... | matthewmacleod wrote: | Seriously. This is about a million times better. | mywittyname wrote: | 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. | brandmeyer wrote: | 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. | jaffathecake wrote: | Yep, I came to the same conclusion | https://twitter.com/jaffathecake/status/1296423819238944768 | Akronymus wrote: | 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}; | pachico wrote: | I would make it more confusing to read by using the 100 and 0 | percentiles. | donquichotte wrote: | 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 | } | resist_futility wrote: | Isn't this a race condition? | https://play.golang.org/p/3Im8FuhyR_S | apta wrote: | aka sleep sort | ccmcarey wrote: | That will give you the median value. What OP wants is the value | `a` clamped within `min` and `max`. | donquichotte wrote: | Can you give an example where median([a, min, max]) is not | equal to clamp(a, min, max), given max >= min? | naikrovek wrote: | If a < min or a > max, if I'm reading it right. | mc10 wrote: | 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. | tl wrote: | Passing math.NaN() crashes in a playground. | bigganibba69 wrote: | Damn, this dude is one condescending shitcoder lmao | patresh wrote: | 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) | amichal wrote: | 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 wrote: | It's amazing what a difference it makes when you just good | names and formatting | loopz wrote: | I find the extra words wordy. | boxfire wrote: | I think this was posted purely for the limerick quality. | jkaptur wrote: | 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 wrote: | Haiku is nice, too: Cryptic sorcerer! | "Math.min(Math.max(v, min), max);" his incantation. | hanoz wrote: | 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); | drbacon wrote: | @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. | [deleted] | parliament32 wrote: | I prefer "ceiling" and "floor", but yes, agreed. | KONAir wrote: | 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. | gugagore wrote: | Those also happen to be fairly common names for operations | (rounding up or down to nearest integer). | jacobolus wrote: | 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); | Someone wrote: | 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) | mabbo wrote: | 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. | thaumasiotes wrote: | 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. | chrisseaton wrote: | > 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/ | ford_o wrote: | Wait, how is that not trivial? | saagarjha wrote: | It's not just a hardcoded optimization for that | construct. | chrisseaton wrote: | > 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. | azinman2 wrote: | > 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. | Lerc wrote: | 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. | TimTheTinker wrote: | For those reading, this work went into TruffleRuby, which | implements Ruby on top of Truffle/GraalVM. | [deleted] | gowld wrote: | If you are going to call a library function, just call | median(). | fizixer wrote: | 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 wrote: | this doesn't work for NaN, e.g {0d, Double.NaN, 1d} returns | 1d. | im3w1l wrote: | 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. | dr_zoidberg wrote: | And for this particular use case, numpy has .clip(), that | takes: np.clip(ndarray, lower_bound, | upper_bound) | | And handles NaNs correctly. | rrobukef wrote: | 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] | taberiand wrote: | 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) | jrochkind1 wrote: | 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? | loeg wrote: | No, the only valid result for NaN is NaN. Saturating | range bounds are for real numbers. | xxs wrote: | 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;" | noctune wrote: | > 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. | xxs wrote: | 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 | thaumasiotes wrote: | 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. | monktastic1 wrote: | > 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. | shakow wrote: | 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 {-[?], +[?]}). | [deleted] | Dylan16807 wrote: | That doesn't really help. "Max" to enforce a "lower bound" is | briefly halting. | airstrike wrote: | Maybe add an alias to Max called AtLeast and one for Min | called AtMost to be used in these situations ;) | hyh1048576 wrote: | 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. | ncallaway wrote: | 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) | dmurray wrote: | > (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. | baddox wrote: | 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. | Symbiote wrote: | 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". | t0astbread wrote: | But "min" does take the minimum: `Math.min(1,3) == 1` | w-m wrote: | I personally would find this much more straightforward: | val - (sign(val - max) + 1) / 2 * (val - max) + (sign(min - val) | + 1) / 2 * (min - val) | O_H_E wrote: | I always though pipe operators are under-rated and under-used. | | In elixer that could be | [deleted] | moralestapia wrote: | >[...] 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? | im3w1l wrote: | Took me a few minutes first time I saw it but it's now firmly | in my mental library of idioms. | young_unixer wrote: | 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. | throwaway6734 wrote: | This is something that upon seeing I would sketch out. It's way | easier for me to understand visually | antoineMoPa wrote: | When making glsl shaders, there's clamp(). | polote wrote: | > 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 | da39a3ee wrote: | https://twitter.com/ben_a_adams/status/1296463312780251136 | [deleted] | spapas82 wrote: | 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 | nesarkvechnep wrote: | 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 | im3w1l wrote: | 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. | grantjpowell wrote: | 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 | spapas82 wrote: | Yes you are right! Puting the number in the first parameter | is even more idiomatic Elixir. | css wrote: | 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. | bovine3dom wrote: | If fastest poll rate > slowest poll rate, I think you've got | them the wrong way around (or is that the joke?). | css wrote: | FASTEST_POLL_RATE is 1000hz, SLOWEST_POLL_RATE is 10hz. Thus, | FASTEST_POLL_RATE is a smaller number on a per-second basis. | bovine3dom wrote: | 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 :) | css wrote: | Yeah that's a very good call, I will probably rename | these. Thanks for the critique! | stkdump wrote: | It seems like they are just have bad names, as their unit | probably is seconds not 1/seconds. | lalaithion wrote: | This is a great example of how haskell makes these things obvious | ;) clamped = num `max` min' `min` max' | graham_paul wrote: | 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 | masklinn wrote: | 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) | O_H_E wrote: | 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. | masklinn wrote: | > 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 | jchw wrote: | 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. | z3ncyberpunk wrote: | More often than not no.. I am trying to find the min/max value | in a set. Bit a bound | 9nGQluzmnq3M wrote: | Not as elegant, but way more readable: | | if num > max: num = max | | if num < min: num = min | geophile wrote: | 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 | [deleted] | xondono wrote: | 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 wrote: | Why? You wrote a function, not a macro. | xondono wrote: | Accidentally overwrote the middle step: | | Junior developer puts the one liner on google, finds the | typical macro definition and does the change. | adrianmonk wrote: | 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. | jacobolus wrote: | Branching is the expensive part, not comparisons. | edflsafoiewq wrote: | Incidentally, gcc and clang both compile this to two cmovs for | me. | geophile wrote: | I don't speak x86. Do you mean that these compilers are | evaluating both conditions, all the time? At what | optimization level? | edflsafoiewq wrote: | Yes, you can check on godbolt. At -O{1,2,3}. At -Os, clang | still generates cmovs, but gcc generates a jump. | benchaney wrote: | 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 | [deleted] | wonderlg wrote: | There's an obvious solution to this: | require('clamp')(num, min, max) | | 1 million monthly downloads | aldanor wrote: | Once you've seen this a few dozen times, you instantly parse it | as "clamp(num, min, max)" | hombre_fatal wrote: | Did a bunch of coding bootcamps just begin session or | something? I don't understand the comments here treating it | like it's so hard to write and verify that it needs a | completely different, slower, less direct, cutesy | implementation. | aldanor wrote: | I don't understand the explosion of comments here either. I | was just saying for myself, it's a trivial construct and very | commonly encountered. | tzs wrote: | 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 wrote: | 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 wrote: | IEEE754 has defined min and max for quite some time. Nowadays | almost all FPUs have min and max instructions built-in. | kazinator wrote: | 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. | O_H_E wrote: | 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. | O_H_E wrote: | Note: I don't really know Elixir, all I know is that this works | in repl.it | jakeva wrote: | 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. | parliament32 wrote: | 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. | doukdouk wrote: | There is indeed one in numpy : | https://numpy.org/doc/stable/reference/generated/numpy.clip.... | dagurp wrote: | You could use statistics.median (in python 3.6+) | [deleted] | ridiculous_fish wrote: | 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. | ebg13 wrote: | > _it takes me ages to convince myself this implementation is | correct_ | | It would take them less time to convince themself if they | switched min and num to put the values in proper semantic order: | min < num < max. | | Math.min(Math.max(min, num), max) | jaffathecake wrote: | I don't think so. Whereas an if statement is easy to read | https://twitter.com/jaffathecake/status/1296423819238944768 | bhaak wrote: | That still leaves the order of Math.min and Math.max undecided | and will probably not help much if you get easily confused by | the visuals of this code. | | I never thought about it but of course there must be code | tongue twisters (or more correctly, brain twisters). | | Thinking about it, I would probably go with a less confusing | implementation. Terse code is hard to read and the compiler is | likely clever enough to choose the best implementation anyway. | rewq4321 wrote: | 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 wrote: | 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. | [deleted] | sarah180 wrote: | 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 wrote: | Unless, of course, your language "designer" has no idea why | every other language uses right-associative ternary | operators. | bangonkeyboard wrote: | This was a "fun" bug to track down in a PHP codebase. | jaffathecake wrote: | 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 wrote: | It's just a matter of what you're used to. Both of those are | equally clear to me. | Haga wrote: | inLimits(lowerBound, Val, upperBound) | ChrisLomont wrote: | 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] | kazinator wrote: | 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 | throwawaw wrote: | 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.) | kickopotomus wrote: | Alternatively, since min and max are part of clojure.core: | (-> n (max vmin) (min vmax)) | white-flame wrote: | What about the [false false] case where min >= n >= max? | retzkek wrote: | 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) && ... | beervirus wrote: | Is this a problem that comes up often? What kind of situation | would make you want to calculate this? | mr_toad wrote: | It's pretty common in data science or financial applications to | restrict the range of data in this way. | | There are probably SQL devs reading this and wondering what the | fuss is about. | baddox wrote: | It's common in computer graphics and user interface | programming. You want to move your character around a 2D grid, | but you don't want either of its x and y coordinates to ever | move outside the bounds of the 2D grid. Or you want a web page | with an article section that is 50% of the browser width, but | never narrower than 300px and never wider than 800px. | gre wrote: | In Factor: : clamp ( x min max -- y ) [ max ] | dip min ; inline | | https://docs.factorcode.org/content/word-clamp,math.order.ht... | mauricio wrote: | 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 | madcaptenor wrote: | 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. | chrisseaton wrote: | I often see [a, b, c].sort[1] which I think is very neat. | nitrogen wrote: | 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. | chrisseaton wrote: | Ideally the temporary array should optimise away. | rubber_duck wrote: | 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 wrote: | Yes but its too many operations for such a simple problem. No | need to overwork the system when two conditionals can solve | the problem. | ossopite wrote: | Excel has no clamp function but a neat alternative is | =MEDIAN(min, num, max) | OscarCunningham wrote: | Ideally I'd also want to throw an error when max < min. | rkagerer wrote: | Wrap it in a function, verify the implementation once, then | henceforth use clamp(x, min, max). | Const-me wrote: | 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. | OkGoDoIt wrote: | I had no idea .NET has Clamp() now. I've been writing something | like the OP all this time [?] | brandmeyer wrote: | 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 wrote: | Your two functions are not equivalent. std::fmax handles NaN, | but std::clamp does not. | brandmeyer wrote: | 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. | f00zz wrote: | 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). | Someone wrote: | 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) | f00zz wrote: | 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.) | wruza wrote: | 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 | nemetroid wrote: | 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. | Semaphor wrote: | > 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"?) | gameswithgo wrote: | > in desktop edition of the runtime | | huh? | Const-me wrote: | 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. | wozer wrote: | .NET 5 will be based on .NET Core. It's already in preview. | the-dude wrote: | Great. But I have been programming since about 1985. | Const-me wrote: | 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. | nickysielicki wrote: | https://godbolt.org/z/bq5E8h | | It's actually the std::min/std::max version that goes to | minsd/maxsd in both clang/gcc. | naikrovek wrote: | 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. | Const-me wrote: | 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. | kcb wrote: | Are you saying a C++ developer shouldn't use the standard | library? | klyrs wrote: | 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. | kcb wrote: | 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. | Tuna-Fish wrote: | 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. | HugoDaniel wrote: | Great. But I have been programming since about | Math.min(Math.max(n, 1900), 1985 - 1) | Imnimo wrote: | 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. | fouric wrote: | 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? | ragnese wrote: | This one doesn't bother me much. What's harder and worse, IMO, is | doing saturating _math_. | edparcell wrote: | 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) | secondcoming wrote: | Those are all valid C. | tobr wrote: | [min, num, max].sort()[1] | tosh wrote: | 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... | tobr wrote: | Very good point. I thought of it more as pseudocode. | tosh wrote: | imho a very elegant solution and probably works in most | languages, I was surprised by JavaScript's behaviour myself | 1-more wrote: | For a real fun headscratcher consider [ '9', '10', '11', | '12', '13' ].map(parseInt) | yboris wrote: | 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/ | tgb wrote: | 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. | yesbabyyes wrote: | 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; } | recursive wrote: | Why sign though? I always write my sorts like this. | .sort((a, b) => a - b) | saagarjha wrote: | Is there a better way to grab a reference to the | subtraction operator? | recursive wrote: | 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. | TimTheTinker wrote: | "lexicographically" is a better descriptor than | "alphabetically", since it's sorting by UTF-8 code point | value. | edflsafoiewq wrote: | It sorts by UTF-16 code unit. | TimTheTinker wrote: | Of course, my bad. | seanwilson wrote: | 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. | tobr wrote: | 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. | seanwilson wrote: | 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. | agumonkey wrote: | never thought of clamp as precompiled sort | unoti wrote: | [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. | city41 wrote: | I also think the fact it doesn't work in JS (due to sort | defaulting to alphabetical sort) brings in even more | appropriate emotions. | sanbor wrote: | [10, 2, 100].sort()[1] //returns 100 | vortico wrote: | This is also nice because it gracefully handles the case where | `max < min`. | rags2riches wrote: | It's not always graceful to say that 1 is both less than 0 | and greater than 2. | vortico wrote: | 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. | resu_nimda wrote: | 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. | tobr wrote: | 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. | vortico wrote: | Why do you prefer to leave undefined behavior? | resu_nimda wrote: | 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. | throwawaw wrote: | I love this one. | gridlockd wrote: | This is why computers are slow. | secondcoming wrote: | No. Just things written by web devs. | | (Shots fired) | saagarjha wrote: | 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 | garmaine wrote: | Dude, you are generating random numbers inside the loop. | That is going to dominate runtime. | saagarjha wrote: | I'm not testing runtime, though, I'm testing whether | clamp gets optimized. | [deleted] | V-2 wrote: | 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 wrote: | 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 wrote: | Does kotlinc optimize away inline ranges like that, or does | this result in a range object being constructed and discarded? | occamrazor wrote: | Numpy has `np.clip`. | | In pure Python `statistics.median([min, max, num])` also works. | slifin wrote: | I'm not sure why the author isn't putting his preferred | implementation into a clamp function then forgetting about it? | LifeLiverTransp wrote: | communicate by structure what it does.. clamp(lowerBound, val, | upperBound) | kaiken1987 wrote: | 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. ___________________________________________________________________ (page generated 2020-08-20 23:00 UTC)