Would you like to make this site your homepage? It's fast and easy...
Yes, Please make this my home page!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Division by 2: in 13 lines of batch code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The principle algorithm for this routine is to work from left
to right finding the next digit of the result by dividing by
2 and adding 5 if the previous digit had a remainder when it
was divided by 2.
Addition/Subtraction
~~~~~~~~~~~~~~~~~~~~
The code uses a trick with overflowing the ERRORLEVEL in order
to (i) add/subtract (ii) divide by 2.
IF ERRORLEVEL x ....
The value x is always taken modulo 256 in DOS, so
IF ERRORLEVEL 770 ....
Actually is the same as
IF ERRORLEVEL 2 ....
This is very useful in a loop;
FOR %%_ IN (0 1 2 3 4 5 6 7 8 9) IF ERRORLEVEL 77%%_ SET C=%%_
Which will set C=0 if ERRORLEVEL 2, C=1 if ERRORLEVEL 3, C=2
if ERRORLEVEL 4, and so on. You can see the result is like
taking ERRORLEVEL-2.
In the routine this particular function is used to get the
next digit (%_% here is the value);
ECHO %_%|CHOICE /C!0123456789>NUL
This sets ERRORLEVEL=2+valueofdigit, consequently the FOR
loop shown above is used to get that digit into a variable.
Division by 2
~~~~~~~~~~~~~
Notice that;
11010 mod 256 =2
So;
FOR %%_ IN (0 1 2 3 4) DO IF ERRORLEVEL %%_%%_0%%_0 ......
Is checking for;
%%_ ERRORLEVEL check
0 0
1 2
2 4
3 6
4 8
This is giving us ERRORLEVEL div 2.
Of course what we really want is something like this;
%%_ ERRORLEVEL check Digit ERRORLEVEL=
0 2 0,1 2,3
1 4 2,3 4,5
2 6 4,5 6,7
3 8 6,7 8,9
4 10 8,9 10,11
Because the digit in ERRORLEVEL has 2 added to it.
This is trivial to accomplish, we can add 2 to the
check directly;
FOR %%_ IN (0 1 2 3 4) DO IF ERRORLEVEL %%_%%_0%%_2 SET RESULT=%%_
If there was a carry from the previous digit
the procedure is similar but we want;
%%_ ERRORLEVEL check
5 2
6 4
7 6
8 8
9 10
Notice that the first ERRORLEVEL check here;
IF ERRORLEVEL 55050 ....
Is 5*2 = 10
Somehow 8 needs to be subtracted. How can you subtract
by changing the 2 zeros?
504 = 512 - 8 = -8 mod 256
So, (if there was a carry from the previous digit) the loop becomes;
FOR %%_ IN (5 6 7 8 9) DO IF ERRORLEVEL %%_%%_5%%_4 SET RESULT=%%_
Remainder
~~~~~~~~~
The carry from each successive digit is simply going to be
either 1 if the digit was odd, or 0 if it was even (DIGIT mod 2).
This could be calculated obviously with something like;
(C=digit)
FOR %%_ IN (3 5 7 9) DO IF %C%==%%_ SET C=1
IF NOT %C%==1 SET C=0
(C=remainder)
However there is an even better way using ERRORLEVEL
to multiply the digit by 128 (080h).
10000000 mod 256 = 128
(BTW: Notice here also that 10^8 mod 256 =0, any digits from the 8th
digit on will be zeroised because of this so there will never be
any point in using more than 8 digits in an ERRORLEVEL argument.)
So multiplying by the digit gives;
%C% 10000000*%C%
0 0
1 128
2 0
3 128
4 0
5 128
6 0
7 128
8 0
9 128
ERRORLEVEL 0 is always TRUE, no matter what the circumstances
are. ERRORLEVEL 128 will always be false in the loop because
within the loop (after the CHOICE command) the ERRORLEVEL is
always 2+valueofdigit.
So the remainder can be calculated with great ease;
IF ERRORLEVEL %C%0000000 SET C=0
IF NOT %C%==0 SET C=1
Program
~~~~~~~
After you've understood what's going on with the ERRORLEVEL bits
the rest of the routine is pretty straightforward. The digits
are one by one processed to generate the result, on exit a final remainder
is also returned. As it stands it is pretty highly optimised, you
will notice for example that %C% is used both for the carry and
for each extracted digit....
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The complete subroutine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::***********************************************************
::* DIV2A - DIVIDE VALUE BY 2 *
::* Entry: %_%=Value *
::* Exit: %_%=Value/2 %C%=Value mod 2 *
::* Last Modified: 04/11/00 *
::* Compatibility: DOS 7 *
::* Notes: *
::* + Invalid values cause unspecified results *
::* + Size optimised version *
::***********************************************************
:DIV2
FOR %%_ IN ("C=0" "N=") DO SET %%_
:DIVLOP
ECHO %_%|CHOICE /C!0123456789>NUL
IF %C%==0 FOR %%_ IN (0 1 2 3 4) DO IF ERRORLEVEL %%_%%_0%%_2 SET N=%N%%%_
IF %C%==1 FOR %%_ IN (5 6 7 8 9) DO IF ERRORLEVEL %%_%%_5%%_4 SET N=%N%%%_
FOR %%_ IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL 77%%_ SET C=%%_
FOR %%%C% IN ("SET _=") DO %%%_%
IF ERRORLEVEL %C%0000000 SET C=0
IF NOT %C%==0 SET C=1
IF NOT !%_%==! GOTO DIVLOP
FOR %%_ IN ("_=%N%" "N=") DO SET %%_
%RET%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A working example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Invoke this program as;
PROGRAM value
It will output the result of dividing the value by 2.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ECHO OFF
SET RET=GOTO:K0
SET _=%1
GOTO DIV2
:K0
ECHO %1/2=%_% remainder %C%
FOR %%_ IN (_ C RET) DO SET %%_=
GOTO OUT
::***********************************************************
::* DIV2A - DIVIDE VALUE BY 2 *
::* Entry: %_%=Value *
::* Exit: %_%=Value/2 %C%=Value mod 2 *
::* Last Modified: 04/11/00 *
::* Compatibility: DOS 7 *
::* Notes: *
::* + Invalid values cause unspecified results *
::* + Size optimised version *
::***********************************************************
:DIV2
FOR %%_ IN ("C=0" "N=") DO SET %%_
:DIVLOP
ECHO %_%|CHOICE /C!0123456789>NUL
IF %C%==0 FOR %%_ IN (0 1 2 3 4) DO IF ERRORLEVEL %%_%%_0%%_2 SET N=%N%%%_
IF %C%==1 FOR %%_ IN (5 6 7 8 9) DO IF ERRORLEVEL %%_%%_5%%_4 SET N=%N%%%_
FOR %%_ IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL 77%%_ SET C=%%_
FOR %%%C% IN ("SET _=") DO %%%_%
IF ERRORLEVEL %C%0000000 SET C=0
IF NOT %C%==0 SET C=1
IF NOT !%_%==! GOTO DIVLOP
FOR %%_ IN ("_=%N%" "N=") DO SET %%_
%RET%
::***********************************************************
::* EXIT POINT *
::***********************************************************
:OUT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DOS 6 version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The only real problem for the code to run in DOS 6 is the line
that strips the first character from the value string;
FOR %%%C% IN ("SET _=") DO %%%_%
Use of quotes in FOR loops was not introduced until DOS 7
so this method is not possible. In fact there is quite
a general problem here because the "=" sign cannot be used
in the FOR parameter list (it acts as a delimitor in DOS6).
The usual workaround I have developed for doing this in
a DOS 6 compatible way is to use an auxillary batch;
FOR %%%C% IN (%%1) DO ECHO SET _=%%%_%>$.BAT
CALL $.BAT
Which gets the string;
SET _=%1mid(_,2)
(Where mid(_,2) is the string %_% from the 2nd character on.)
...into a batch file, the batch file is executed and of course
%1 disappears.
While reasonably neat and generic this method is not completely
satisfying;
(i) It now takes 2 lines
(ii) It is slower because of the external file
(iii) It holds back DOS 7 in order to gain DOS 6 compatibility
Because of these bothers I spent some deal of time working
for other methods;
FOR %%%C% IN (%%) DO SET _=%_%
For example;
---------------------------------------------------------------------------
%_% value Action
---------------------------------------------------------------------------
123456 initialise by prepending a '%' to %_%
%123456 FOR %%%C% IN (%%) DO SET _=%_%
FOR %1 IN (%) DO SET _=%123456 after variable substitution
SET _=%23456 result of FOR loop
%23456 FOR %%%C% IN (%%) DO SET _=%_%
FOR %2 IN (%) DO SET _=%23456 after variable substitution
SET _=%3456 result of FOR loop
%3456
---------------------------------------------------------------------------
You can see this is similar in effect to trimming the 1st character
from the left but it has the caveat that a '%' sign needs to be
kept at the start of the string.
This is perfect for DIV2A since the method used to get the first
digit will not see the '%' sign.
After;
ECHO %%1234 |CHOICE /C!0123456789
You can see that the '%' will be ignored by choice as an invalid
entry, instead the '1' will be picked up.
Aside from the obvious unrolling of the FOR loops the
rest now should be obvious.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The complete subroutine (DOS 6 compatibility)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::***********************************************************
::* DIV2A6 - DIVIDE VALUE BY 2 *
::* Entry: %_%=Value *
::* Exit: %_%=Value/2 %C%=Value mod 2 *
::* Last Modified: 08/11/00 *
::* Compatibility: DOS 6+ (Tested DOS7) *
::* Notes: *
::* + Invalid values cause unspecified results *
::* + Size optimised version *
::***********************************************************
:DIV2
SET C=0
SET N=
SET _=%%%_%
:DIVLOP
ECHO %_%|CHOICE /C!0123456789>NUL
IF %C%==0 FOR %%_ IN (0 1 2 3 4) DO IF ERRORLEVEL %%_%%_0%%_2 SET N=%N%%%_
IF %C%==1 FOR %%_ IN (5 6 7 8 9) DO IF ERRORLEVEL %%_%%_5%%_4 SET N=%N%%%_
FOR %%_ IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL 77%%_ SET C=%%_
FOR %%%C% IN (%%) DO SET _=%_%
IF ERRORLEVEL %C%0000000 SET C=0
IF NOT %C%==0 SET C=1
IF NOT %_%==%% GOTO DIVLOP
SET _=%N%
SET N=
%RET%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A working example (DOS 6 compatibility)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Invoke this program as;
PROGRAM value
It will output the result of dividing the value by 2.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ECHO OFF
SET RET=GOTO:K0
SET _=%1
GOTO DIV2
:K0
ECHO %1/2=%_% remainder %C%
FOR %%_ IN (_ C RET) DO SET %%_=
GOTO OUT
::***********************************************************
::* DIV2A6 - DIVIDE VALUE BY 2 *
::* Entry: %_%=Value *
::* Exit: %_%=Value/2 %C%=Value mod 2 *
::* Last Modified: 08/11/00 *
::* Compatibility: DOS 6+ (Tested DOS7) *
::* Notes: *
::* + Invalid values cause unspecified results *
::* + Size optimised version *
::***********************************************************
:DIV2
SET C=0
SET N=
SET _=%%%_%
:DIVLOP
ECHO %_%|CHOICE /C!0123456789>NUL
IF %C%==0 FOR %%_ IN (0 1 2 3 4) DO IF ERRORLEVEL %%_%%_0%%_2 SET N=%N%%%_
IF %C%==1 FOR %%_ IN (5 6 7 8 9) DO IF ERRORLEVEL %%_%%_5%%_4 SET N=%N%%%_
FOR %%_ IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL 77%%_ SET C=%%_
FOR %%%C% IN (%%) DO SET _=%_%
IF ERRORLEVEL %C%0000000 SET C=0
IF NOT %C%==0 SET C=1
IF NOT %_%==%% GOTO DIVLOP
SET _=%N%
SET N=
%RET%
::***********************************************************
::* EXIT POINT *
::***********************************************************
:OUT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~