You can compare just two numbers with dc
like:
dc -e "[$1]sM $2d $1<Mp"
... where "$1"
is your max value and "$2"
is the number you would print if it is lesser than "$1"
. That also requires GNU dc
- but you can do the same thing portably like:
dc <<MAX
[$1]sM $2d $1<Mp
MAX
In both of the above cases you can set the precision to something other than 0 (the default) like ${desired_precision}k
. For both it is also imperative that you verify that both values are definitely numbers because dc
can make system()
calls w/ the !
operator.
With the following little script (and the next) you should verify the input as well - like grep -v \!|dc
or something to robustly handle arbitrary input. You should also know that dc
interprets negative numbers with a _
prefix rather than a -
prefix - because the latter is the subtraction operator.
Aside from that, with this script dc
will read in as many sequential \n
ewline separated numbers as you would care to provide it, and print for each either your $max
value or the input, depending on which is the lesser of the wo:
dc -e "${max}sm
[ z 0=? d lm<M p s0 lTx ]ST
[ ? z 0!=T q ]S?
[ s0 lm ]SM lTx"
So... each of those [
square bracketed ]
expanses is a dc
string object that is S
aved each to its respective array - any one of T
, ?
, or M
. Besides some few other things dc
might do with a string, it can also ex
ecute one as a macro. If you arrange it right a fully functioning little dc
script is assembled simply enough.
dc
works on a stack. All input objects are stacked each upon the last - each new input object pushing the last top object and all objects below it down on the stack by one as it is added. Most references to an object are to the top stack value, and most references pop that top of stack (which pulls all objects below it up by one).
Besides the main stack, there are also (at least) 256 arrays and each array element comes with a stack all its own. I don't use much of that here. I just store the strings as mentioned so I can l
oad them when wanted and ex
ecute them conditionally, and I s
tore $max
's value in the top of the m
array.
Anyway, this little bit of dc
does, largely, what your shell-script does. It does use the GNU-ism -e
option - as dc
generally takes its parameters from standard-in - but you could do the same like:
echo "$script" | cat - /dev/tty | dc
...if $script
looked like the above bit.
It works like:
lTx
- This l
oads and ex
ecutes the macro stored in the top of T
(for test, I guess - I usually pick those names arbitrarily).
z 0=?
- T
est then tests the stack depth w/ z
and, if the stack is empty (read: holds 0 objects) it calls the ?
macro.
? z0!=T q
- The ?
macro is named for the ?
dc
builtin command which reads a line of input from stdin, but I also added another z
stack depth test to it, so that it can q
uit the whole little program if it pulls in a blank line or hits EOF. But if it does !
not and instead successfully populates the stack, it calls T
est again.
d lm<M
- T
est will then d
uplicate the top of stack and compare it to $max
(as stored in m
). If m
is the lesser value, dc
calls the M
macro.
s0 lm
- M
just pops the top of stack and dumps it to the dummy scalar 0
- just a cheap way of popping the stack. It also l
oads m
again before returning to T
est.
p
- This means that if m
is less than the current top of stack, then m
replaces it (the d
uplicate of it, anyway) and is here p
rinted, else it does not and whatever the input was is p
rinted instead.
s0
- Afterward (because p
doesn't pop the stack) we dump the top of stack into 0
again, and then...
lTx
- recursively l
oad T
est once more then ex
ecute it again.
So you could run this little snippet and interactively type numbers at your terminal and dc
would print back at you either the number you entered or the value of $max
if the number you typed was larger. It would also accept any file (such as a pipe) as standard input. It will continue the read/compare/print loop until it encounters a blank line or EOF.
Some notes about this though - I wrote this just to emulate the behavior in your shell function, so it only robustly handles the one number per line. dc
can, however, handle as many space separated numbers per line as you would care to throw at it. However, because of its stack the last number on a line winds up being the first it operates on, and so, as written, dc
would print its output in reverse if you printed/typed more than one number per line at it.The proper way to handle that is to store up a line in an array, then to work it.
Like this:
dc -e "${max}sm
[ d lm<M la 1+ d sa :a z0!=A ]SA
[ la d ;ap s0 1- d sa 0!=P ]SP
[ ? z 0=q lAx lPx l?x ]S?
[q]Sq [ s0 lm ]SM 0sa l?x"
But... I don't know if I want to explain that in quite as much depth. Suffice it to say that as dc
reads in each value on the stack it stores either its value or $max
's value in an indexed array, and, once it detects the stack is once again empty, it then prints each indexed object before attempting to read another line of input.
And so, while the first script does...
10 15 20 25 30 ##my input line
20
20
20
15
10 ##see what I mean?
The second does:
10 15 20 25 30 ##my input line
10 ##that's better
15
20
20 ##$max is 20 for both examples
20
You can handle floats of arbitrary precision if you first set it with the k
command. And you can alter the i
nput or o
utput radices independently - which can sometimes be useful for reasons you might not expect. For example:
echo 100000o 10p|dc
00010
...which first sets dc
's output radix to 100000 then prints 10.
NAN
is not comparable. See floating-point-gui.de for more