Batch: count the number of digits in a variable - batch-file

I want to find a way to know the number of digits in variable. For now, I'm trying to use this code. In this example, %var% is the variable that I need to know the number of digits it has.
set x=1
set var=12345
:LOOP
set temp=%var:~0,%x%%
if %temp%==%var% goto END
set x=%x%+1
goto LOOP
:END
Theoretically, at the end of the code %x% would be the number of digits %var% has. However, it doesn't work. I found out the problem is at the 3rd line. I modified the code to diagnose:
set x=1
set var=12345
:LOOP
set temp=%var:~0,%x%%
echo %temp%
pause
if %temp%==%var% goto END
set x=%x%+1
goto LOOP
:END
The result echoed was:
x%%
Can anyone pinpoint my mistake or give an alternative solution to determine the number of digits in a variable?

Here's a short solution, that only works for numeric variables:
set /a Log=1%var:~1%-%var:~1% -0
set /a Len=%Log:0=+1%
The variable %Len% will contain the number of digits in %var%.
Explanation
The basic idea is to convert the first digit to 1, and the rest of them (the 'trailing' digits) to 0's. Then we can use the string replacement function to replace all the 0's with +1 giving us 1+1+1.... and evaluate the string as an arithmetic expression. This will give us the total number of digits.
The 'trailing' digits can be gotten using %var:~1% and we convert them to 0 by subtracting them from the variable itself: 45678 - 5678 gives 40000 etc. However, the above code subtracts them from 1%var:~1% instead, in order to replace the first digit with 1 (i.e. 1 followed by the 'trailing' digits).
The reason for the extra -0 is in case %var% only has one digit, for example 7. In that case, the expression 1%var:~1%-%var:~1% would evaluate to 1- and the shell would complain: Missing operand. The -0 ensures that we always have a valid expression.
Now that we've converted the variable in to the proper form into %Log%, we can replace every occurrence of 0 with +1using %Log:0=+1% and evaluate the resulting expression using set /a, giving us our final result.

The main problem with your code is
set temp=%var:~0,%x%%
This does not work. The parser is not able to properly determine what percent sign belongs to what variable. You can enable delayed expansion and write it as
set "temp=!var:~0,%x%!"
For alternative versions, to handle any length string, any of the posted answers will work.
For a simpler solution, if you are sure the string is under 10 characters, then this is an alternative
set "x=0123456789%var%"
set "x=%x:~-10,1%"

As there is no build in function for string length, you can write your own function.
#echo off
setlocal
set "myString=abcdef!%%^^()^!"
call :strlen result myString
echo %result%
goto :eof
:strlen <resultVar> <stringVar>
(
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~1=%len%"
exit /b
)
This function needs always 13 loops, instead of a simple strlen function which needs strlen-loops.
It handles all characters.
Source: How do you get the string length in a batch file?

Your are trying to do this loop :
#Echo Off
Set /P VrStr=Enter your string :
:Loop
If "%VrStr%" EQU "" Goto EndLoop
Set VrStr=%VrStr:~0,-1%
Set /A VrLgr+=1
Goto Loop
:EndLoop
Echo Number of char: %VrLgr%
Pause
You can use this to :
#echo off
setlocal EnableDelayedExpansion
Set /P $Tstring=Enter your string:
for /l %%a in (0,1,9000) do (set $t=!$Tstring:~%%a,1!&if not defined $t (echo [NB OF CHAR =] %%a&pause&exit /b))
pause

Related

Why does an IF condition in a batch file interpret 3 being greater than 2*2?

I'm trying to make a infinite leveling system for a little personal game I'm making, but it doesn't work. This is what it looks like.
if %exp% gtr %lvl%*%lvl% goto lvlup
:lvlup
set /a exp=%exp%-%lvl%*%lvl%
set /a cmhealth=%cmhealth%+5
set /a cchealth=%cchealth%+5
set /a cattack=%cattack%+3
set /a cDefence=%cDefence%+4
set /a cspeed=%cspeed%+2
set /a lvl=%lvl%+1
set /a SP=%SP%+1
goto decisions
It runs fine until it gets to something like:
if 3 GTR 2*2 goto lvlup
set /a exp=3-2*2
set /a cmhealth=25+5
set /a cchealth=18+5
set /a cattack=8+3
set /a cDefence=15+4
set /a cspeed=8+2
set /a lvl=2+1
set /a SP=1+1
goto decisions
And exp is then set to -1.
Why does it do this and how can I fix it?
The problem is it doesn't read 2*2 as 4 or 3*3 as 9.
The IF command compares primary strings.
IF compares also integer numbers if both strings can be converted successfully internally to 32-bit signed integer numbers and command extensions are enabled as by default and using as comparison operator EQU, or NEQ, or LSS, or LEQ, or GTR, or GEQ.
But IF does not evaluate expressions like command SET with parameter /A.
Run in a command prompt window if /? for help on this command.
As the string 2*2 can't be converted to an integer because of * in string, the command IF runs a string comparison to evaluate if left string is greater than right string.
A string comparison is done by comparing the bytes of the two strings byte by byte until either end of one of the two strings is reached or the currently compared bytes of the two strings are not equal.
The decimal values of the bytes for the two strings are:
3 ... 51
2*2 ... 50 42 50
51 is greater than 50 and therefore the string 3 is greater than the string 2*2 and batch execution continues with jump to label lvlup.
So it is necessary to first evaluate the expression %lvl%*%lvl% with set /A and assign the result to an environment variable for comparing the result next with another environment variable holding also an integer number.
set /A result=lvl * lvl
if %exp% GTR %result% goto lvlup
:lvlup
set /A exp-=result
set /A cmhealth+=5
set /A cchealth+=5
set /A cattack+=3
set /A cDefence+=4
set /A cspeed+=2
set /A lvl+=1
set /A SP+=1
goto decisions
As help of command SET output by running in a command prompt window set /? explains, variable names in expression after set /A usually must not and should not be referenced with using %VariableName% (immediate expansion) or !VariableName! (delayed expansion). Space separated strings which are definitely not integer numbers or operators in arithmetic expression are automatically interpreted as variable names which current string value should be converted internally to a 32-bit signed integer for evaluating the arithmetic expression.

Batch Calcs.. Not the same as usual

I'm trying to make a desktop binary calculator and can't seem to get values to calculate properly.
http://pastebin.com/embed_js.php?i=mtsJRys8
:: functions for 128
if userValue GEQ 128 (
set bin128 = 1
goto 64
)
if not userValue GEQ 128 (
set bin128 = 0
)
Trying to get the Variable 'bin128' to end with a value 1 or 0
Decimal to binary converter?
I see in your paste you're computing the binary result by comparing the entered number with powers of two. There's a more efficient algorithm for converting dec to bin, and it'll handle numbers from 0 to 2147483647.
#echo off
setlocal
if "%~1"=="" (
echo usage: %~nx0 integer
goto :EOF
)
set /a dec = %~1
:/2
set /a mod = dec %% 2, dec /= 2
set "bin=%mod%%bin%"
if %dec% gtr 0 goto :/2
echo %bin%
I also see paulsm4's comment recommending another (suspicious quote) "real" language. Truthfully, though, not counting languages that already have a direct int-to-bin conversion method, the batch scripting language can actually save you a few steps because all math is integer math, with decimals inherently truncated. This saves you from having to Math.floor or similar on every iteration. This is one situation where batch is better-suited for the task than many other languages.
If you prefer to do the power-of-two comparison method, here's another solution. It loops 31 times regardless of the length of the numeral you supply, but for /L is very efficient. This may actually be faster than the method above for larger numbers for many iterations. Not sure.
#echo off
setlocal enabledelayedexpansion
if "%~1"=="" (
echo usage: %~nx0 integer
goto :EOF
)
set /a dec = %~1, pow = 1073741824
for /L %%I in (1,1,31) do (
if !dec! geq !pow! (
set "bin=!bin!1"
set /a dec -= pow
) else (
if defined bin set "bin=!bin!0"
)
set /a pow /= 2
)
if not defined bin set bin=0
echo %bin%
And just because I felt like it, here's the same thing but using bitwise operations. This is the most efficient method. (Edit: or at least it was, until Aacini posted his solution.)
#echo off
setlocal enabledelayedexpansion
if "%~1"=="" (
echo usage: %~nx0 integer
goto :EOF
)
for /L %%I in (0,1,30) do (
set /a "mask = 1 << %%I, bit = ^!^!(%~1 & mask)"
if !mask! gtr %~1 goto break
set "bin=!bit!!bin!"
)
:break
if not defined bin set bin=0
echo %bin%
Efficiency:
I ran a series of tests to determine which method is most efficient. For each method, I sent the output to NUL, looped the script for 1000 iterations, then took an average of the run times over 3 runnings. I did this first with a small value of 5, then a large value of 2147483646. Results:
goto method, input=small: 7.37 seconds
goto method, input=large: 33.77 seconds
powers-of-two method, input=small: 8.38 seconds
powers-of-two method, input=large: 12.44 seconds
bitwise method, input=small: 6.35 seconds
bitwise method, input=large: 11.42 seconds
These results are not surprising, as for /L is generally faster than a goto loop, and bitwise operations occur faster than integer math.
Just to complete rojo's answer, I think that this is the most efficient method to convert a decimal number to binary:
#echo off
setlocal EnableDelayedExpansion
if "%~1"=="" (
echo usage: %~nx0 integer
goto :EOF
)
set "decimal=%~1"
set "binary="
for /L %%i in (1,1,32) do (
set /A "bit=decimal&1, decimal>>=1"
set "binary=!bit!!binary!"
if !decimal! equ 0 goto break
)
:break
echo %binary%
Note that previous method correctly convert negative numbers!
At this post there is a Batch file that can convert very large decimal numbers to binary. The result is stored in a Batch variable, so the binary number may have a maximum of 8 K digits. This means that the maximum decimal number that can be converted is 2^8192 - 1.

Batch file decides that 4 > 39, How do I fix it?

Note: The file Default.txt contains one line with these three characters:1:X
#echo off
set r=0
:: For loop retrieves lines of text from the file Default.txt
FOR /F "tokens=1,2 delims=:" %%a IN (Default.txt) DO (
:: Each line is saved to a different variable.
set _%%a=%%b
set n=%%a
)
set ln=1
setlocal enabledelayedexpansion
:process
:: This loop processes all the lines in the text file.
set r=0
if "%n%" GTR "0" (
:len
:: This loop determines the length of each string.
if not "!_%ln%:~%r%,1!"=="" (
set /a r=%r%+1
goto len
)
:space
:: This loop adds spaces to each string so they will all be 39 characters in length.
if "%r%" LEQ "39" (
:: Note that there is a mandatory space at the end of the following line.
set _%ln%=!_%ln%!
set /a r=%r%+1
goto space
)
set /a n-=1
set /a ln+=1
goto process
) else (
endlocal
set _1=%_1%
)
echo %_1%]
pause >nul
When the script is run however, instead of adding 38 spaces, it only adds 3.
By turning echo back on, I found the exact point where it exits the :space loop.
C:\>if "1" LEQ "39" (
set _1=!_1!
set /a r=1+1
goto space
)
C:\>if "2" LEQ "39" (
set _1=!_1!
set /a r=2+1
goto space
)
C:\>if "3" LEQ "39" (
set _1=!_1!
set /a r=3+1
goto space
)
Up to this point, everything is working as it should.
Suddenly:
C:\>if "4" LEQ "39" (
set _1=!_1!
set /a r=4+1
goto space
)
For some reason, 4 is suddenly greater than 39, and it moves on to the next section instead of incrementing the variable and looping again like it should.
C:\>set /a n-=1
C:\>set /a ln+=1
C:\>goto process
And the program moves on and only 3 spaces are ever added to the variable.
I have no idea what the problem is and would be grateful for any insight.
C:\>if 4 LEQ 39 (
set _1=!_1!
set /a r=4+1
goto space
)
Remove quotations and try that should work , quotes are normally used with strings.
When comparing numerics, don't enclose them in quotation marks.
command: if "4" leq "39" echo hi
output: (empty line)
command: if 4 leq 39 echo hi
output: hi
The reason for that is that "4" is alphabetically after "39", so "4 is greater than "3. When comparing using quotation marks, the comparison is alphabetic, not numeric as you intended.
You've got a few other problems with your script. Don't put labels within parenthetical code blocks. You need to find some other place to put :len and :space outside of the if statement where they currently live. Strictly speaking, :: is also label named :, not a substitute for rem. When using :: as a comment, avoid using it within parenthetical code blocks as well. Use rem instead. Also, indent your code to make it easier to ensure you've got the same number of ( as ). Let me ask you: which is more readable?
option 1:
...
:space
:: This loop adds spaces to each string so they will all be 39 characters in length.
if "%r%" LEQ "39" (
:: Note that there is a mandatory space at the end of the following line.
set _%ln%=!_%ln%!
set /a r=%r%+1
goto space
)
set /a n-=1
set /a ln+=1
goto process
) else (
endlocal
set _1=%_1%
)
echo %_1%]
pause >nul
The else is a continuation of the if statement above it, right? Wrong! Properly indented, you'd see that it's part of an if statement much higher in the script.
option 2:
rem This loop adds spaces to each string so they will all be 39 characters in length.
if "%r%" LEQ "39" (
rem Note that there is a mandatory space at the end of the following line.
set _%ln%=!_%ln%!
set /a r=%r%+1
goto space
)
set /a n-=1
set /a ln+=1
goto process
) else (
endlocal
set _1=%_1%
)
echo %_1%]
pause >nul
I'll never understand why some people insist on left-justifying every line of code they write. It only makes things much more difficult to troubleshoot.
You know, there are more efficient ways to repeat characters. Rather than looping, you could do variable substring extraction.
set "spaces= "
set "39spaces=%spaces:~-39%"
If you want to get the length of a string, the fastest way I've found to do that is based on jeb's answer here:
:length <return_var> <string>
setlocal enabledelayedexpansion
if "%~2"=="" (set ret=0) else set ret=1
set "tmpstr=%~2"
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!tmpstr:~%%I,1!"=="" (
set /a ret += %%I
set "tmpstr=!tmpstr:~%%I!"
)
)
endlocal & set "%~1=%ret%"
goto :EOF
Example usage:
command: call :length len "The quick brown fox"
command: echo %len%
output: 19
Even if you're getting the length of a 2000 character line, that loop still counts the length in only 13 iterations, rather than potentially thousands.
Prudviraj's answer answers why your original code doesn't work, but for an easier way of padding a string to 39 characters, you could try:
Make39.bat
#echo off
setlocal
set "R=%1"
set "R=%R% " REM append 39 spaces
set "R=%R:~,39%" REM take first 39 characters
echo :123456789012345678901234567890123456789:
echo :%R%:
which works as follows:
S:\>make39 abc
:123456789012345678901234567890123456789:
:abc :
S:\>make39 "Quite a long string"
:123456789012345678901234567890123456789:
:Quite a long string :
Your comments seem to imply no string will initially be longer than 39 characters; you would have to get more inventive if this were possible (I would probably take the first 39 characters of the original R and see if that and R differ: if they did, the original would have been longer, so there would be no need to add padding).

Append Text with batch file

I use the following code to write a 5 times to hi.txt using batch file.
The problem is tht it automatically appends newline to the end.
Output:
a
a
a
a
a
I want:
aaaaa
The method below is probably the fastest one to create a file with a given number of the same character. If the number is just 10,000 the file is created in an instant.
#echo off
setlocal EnableDelayedExpansion
set times=10000
rem Create the initial file with one "a"
set /P "=a" < NUL > bitNumberOfChars.txt
rem Identify individual bits in the number of times
rem and append the same number of "a"'s to output file
rem Test 31 bits, from 0 to 30
(for /L %%i in (0,1,30) do if !times! neq 0 (
set /A "bit=times & (1<<%%i), times-=bit"
if !bit! neq 0 type bitNumberOfChars.txt
type bitNumberOfChars.txt >> bitNumberOfChars.txt
)) > output.txt
del bitNumberOfChars.txt
EDIT: Optimized method added
As user dbenham indicated in his comment, this method is not optimized because it uses an auxiliary disk file. The new version below is an optimized one that does not store the data in a file, but in a memory variable as dbenham suggested in his answer. The procedure is the same than in the first method: in each step the string length is doubled and one bit of the given number is tested; if the bit is not zero, the current string is output.
#echo off
setlocal EnableDelayedExpansion
for /F "delims==" %%a in ('set') do set "%%a="
set times=%1
rem Create the initial string with one "a"
set "s=a"
rem Identify individual bits in the number of times
rem and append the same number of "a"'s to output file
< NUL (
rem Test the first 12 bits, from 0 to 11 (string up to 4 KB)
for /L %%i in (0,1,11) do (
set /A "bit=times & (1<<%%i), times-=bit"
if !bit! neq 0 set /P "=!s!"
if !times! equ 0 goto break
set "s=!s!!s!"
)
rem Test the bit 12 (string of 8 KB - 8)
set /A "bit=times & (1<<12), times-=bit"
if !bit! neq 0 set /P "=!s!"
if !times! equ 0 goto break
set "s=!s:~4!"
set "s=!s!!s!"
rem Test the rest of bits, from 13 to 30 (repeating string of 8 KB)
set t2=1, t3=0
for /L %%i in (13,1,30) do if !times! neq 0 (
set /A "bit=times & (1<<%%i), times-=bit"
if !bit! neq 0 (
for /L %%t in (1,1,!t2!) do set /P "=!s!"
set /A "t3+=t2*8"
)
set /A "t2<<=1"
)
rem Add missing bytes (8 bytes per each 8 KB string)
set /A div=t3/8184, mod=t3%%8184
for /L %%t in (1,1,!div!) do set /P "=!s!"
for %%t in (!mod!) do set /P "=!s:~0,%%t!"
) > output.txt
:break
This method have practically the same performance than dbenham's one; however, because this method uses a slightly larger maximum string (8184 vs. 8000 chars.), it will be marginally faster with very large files of certain specific sizes. After completed severals tests and getting the average time, this method ran about 1.5% faster with a file of 10,000,000 characters, and it ran 3.5% faster with a file of 66,000,000 characters.
#echo off
break|set /p=a>file
break|set /p=a>>file
break|set /p=a>>file
break|set /p=a>>file
type file
try this...
<nul set /p"=string"
This is the usual batch construct to output a string without an ending CR/LF.
If you need to "instantly" generate a 10000 a characters to a file, you can use something like
#echo off
setlocal enableextensions disabledelayedexpansion
<nul >"file.txt" (for /l %%a in (1 1 625) do #set /p"=aaaaaaaaaaaaaaaa" )
That is, 16 a * 625 iterations = 10000 a
Unless you are working with a high performance solid state drive, performance will probably be limited by disk write speed. The Aacini solution is not optimized because not only does it write the desired string to disk, it also writes at least that much to a temporary file as well, so the IO cost is roughly doubled.
An optimized solution should minimize disk write operations, both in terms of total length, as well as number of write operations.
The SET /P hack used to write data without carriage return or linefeed (\r\n) is relatively slow. On my machine, ECHO is about 2 times faster than <NUL SET /P. So we also want to minimize the number of times SET /P is executed.
Manipulating strings in memory via variables is comparatively fast.
My optimized solution prepares a variable with a nearly maximum length (8000 out of max possible 8191). The entire big string is written enough times to get close to the desired length, and then a substring operation is used to get the remainder.
I've written two optimized routines to assist with the task.
:MakeString8k replicates any string to a length of exactly 8000 bytes, all within memory, overwriting the original variable. Once defined, this 8k string can be used for multiple write operations.
:WriteBigString uses SET /P to write a string multiple times in a loop, plus a substring, to achieve the desired length. The output is written to stdout, so the CALL can be redirected to the desired output file. This would normally be called with the output of :MakeString8k as the input string, along with the known input length of 8000. If the length of the input string is not passed, then it uses a :strlen function to compute the length.
I've included demo code that shows how to write "a" 10000 times to test.txt. But it is easy to change the parameters to write nearly any string to nearly any given length.
#echo off
setlocal enableDelayedExpansion
set "str=a"
call :makeString8k str
call :writeBigString 10000 str 8000 >>test.txt
exit /b
:MakeString8k StrVar
::
:: Replicate the string within variable StrVar to length 8000.
:: The pattern will be discontinuous at the midway point if 8000
:: is not an even multiple of the original string length.
::
:: If delayed expansion is enabled when called, then the initial
:: string can contain any valid byte code except 0x00. If delayed
:: expansion is disabled when called, then the string cannot contain
:: carriage return (0x0D) or linefeed (0x0A).
::
if "!!" neq "" (
setlocal enableDelayedExpansion
set make8k.setlocal=1
)
for /l %%N in (1 1 13) do set "%~1=!%~1:~0,4000!!%~1:~0,4000!"
if defined make8k.setlocal for /f delims^=^ eol^= %%A in ("!%~1!") do (
endlocal
set "%~1=%%A"
)
exit /b
:WriteBigString Len SeedVar [SeedLen]
::
:: Repeatedly write to stdout the string in seedVar until length Len
:: is reached. Performance is improved slightly if the length of SeedVar
:: is also passed as SeedLen.
::
:: This function may not work properly if the string begins with =, ",
:: or white space.
::
setlocal enableDelayedExpansion
set "seedLen=%~3"
if not defined seedLen call :strlen %2 seedLen
set /a "wholeCnt=%~1/seedLen, subLen=%~1%%seedLen"
<nul (for /l %%N in (1 1 %wholeCnt%) do set /p "=!%~2!")
for %%N in (%subLen%) do <nul set /p "=!%~2:~0,%%N!"
exit /b
:strlen StrVar RtnVar
::
:: Compute the length of the string within StrVar and return
:: the result in variable RtnVar, or write the result to stdout
:: if RtnVar is not specified.
::
setlocal EnableDelayedExpansion
set "s=!%~1!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
(
endlocal
if "%~2" neq "" (set "%~2=%len%") else echo %len%
)
exit /b
Note: There are limitations to the SET /P hack - it may not work properly if the string begins with =, ", or a white space character like space, tab, or new line. The exact limitations depend on the version of Windows that you are using.
You could use techniques posted at https://stackoverflow.com/a/19468559/1012053 to adapt this solution to support writing any string other than null (0x00) characters.
I've tested my solution vs. Aacini's solution, and this is about 2 times faster. Both solutions write 10,000 bytes in the blink of an eye. But the difference becomes apparent with large files. It takes Aacini's code ~13 seconds to write 100 million bytes on my machine, whereas my code takes only ~6 seconds.

Floating point division in a batch file

I need to do a floating-point division in a dos batch.
I didn't find a way to do it. Something like this :
SET /A Res=10/3
returns a integer number.
Is it possible to do it ?
I know this is a very old topic, but I can't found a simple Batch method in all previous answers, so I post here a pure Batch solution that is very simple to use.
Perform operations using fixed point arithmetic in Batch is simple. "Fixed point" means that you must set a number of decimals in advance and keep it throughout the operations. Add and subtract operations between two Fixed Point numbers are performed directly. Multiply and division operations requires an auxiliary variable, that we can call "one", with the value of 1 with the right number of decimals (as "0" digits). After multiply, divide the product by "one"; before division, multiply the dividend by "one". Here it is:
#echo off
setlocal EnableDelayedExpansion
set decimals=2
set /A one=1, decimalsP1=decimals+1
for /L %%i in (1,1,%decimals%) do set "one=!one!0"
:getNumber
set /P "numA=Enter a number with %decimals% decimals: "
if "!numA:~-%decimalsP1%,1!" equ "." goto numOK
echo The number must have a point and %decimals% decimals
goto getNumber
:numOK
set numB=2.54
set "fpA=%numA:.=%"
set "fpB=%numB:.=%"
set /A add=fpA+fpB, sub=fpA-fpB, mul=fpA*fpB/one, div=fpA*one/fpB
echo %numA% + %numB% = !add:~0,-%decimals%!.!add:~-%decimals%!
echo %numA% - %numB% = !sub:~0,-%decimals%!.!sub:~-%decimals%!
echo %numA% * %numB% = !mul:~0,-%decimals%!.!mul:~-%decimals%!
echo %numA% / %numB% = !div:~0,-%decimals%!.!div:~-%decimals%!
For example:
Enter a number with 2 decimals: 3.76
3.76 + 2.54 = 6.30
3.76 - 2.54 = 1.22
3.76 * 2.54 = 9.55
3.76 / 2.54 = 1.48
Batch files as such do not support the floating point arithmetic. However, this article suggests a workaround that uses an external script file to do calculations. The script file should use some sort of eval function to evaluate the expression passed as an argument and return the result. Here's a sample VBScript file (eval.vbs) that does this:
WScript.Echo Eval(WScript.Arguments(0))
You can call this external script from your batch file, specify the expression to be evaluated and get the result back. For example:
#echo off
for /f %%n in ('cscript //nologo eval.vbs "10/3"') do (
set res=%%n
)
echo %res%
Of course, you'll get the result as a string, but it's better than nothing anyway, and you can pass the obtained result to the eval script as part of another expression.
According to this reference, there is no floating point type in DOS batch language:
Although variables do exist in the DOS batch programming language, they are extremely limited. There are no integer, pointer or floating point variable types, only strings.
I think what you are trying to do will be impossible without implementing your own division scheme to calculate the remainder explicitly.
I recently came across this batch file to compute an approximation of Pi.
There is a DivideByInteger label that might be useful to you: Stupid-Coding-Tricks-A-Batch-of-Pi
It uses a set of MaxQuadIndex variables, each containing a four-digit number (quadruple), in order to store the entire result. The code allows division by an integer between 1 and 10000, inclusive.
:DivideByInteger
if defined PiDebug echo.DivideByInteger %1 %2
set /a DBI_Carry = 0
for /L %%i in (!MaxQuadIndex!, -1, 0) do (
set /a DBI_Digit = DBI_Carry*10000 + %1_%%i
set /a DBI_Carry = DBI_Digit %% %2
set /a %1_%%i = DBI_Digit / %2
)
goto :EOF
A Print label is also available…
try this
SETLOCAL EnableExtensions EnableDelayedExpansion
call :calc_ 1 (99-(100*5/100^)^)
echo !calc_v!
goto :EOF
:calc_
set scale_=1
set calc_v=
for /l %%i in (1,1,%1) do set /a scale_*=10
set /a "calc_v=!scale_!*%2"
set /a calc_v1=!calc_v!/!scale_!
set /a calc_v2=!calc_v!-!calc_v1!*!scale_!
set calc_v=!calc_v1!.!calc_v2!
goto :EOF
just change
call :calc_ decimalpoint equataion
in the example
decimalpoint is 1
equataion is (99-(100*5/100^)^) ;make sure if you use () that you insert ^ before ) as in ^)
the answer is 94.0
if decimalpoint is 2
and equataion is 22/7 ;π pi
the answer is 3.14
I wrote a pure batch file specifically to do division. It takes the first number you input, and then divides it by the second one, and displays the result with as many decimal points as you specify.
Echo off
cls
if NOT "%3" == "" (
set n1=%1
set n2=%2
set max=%3
goto :begin
)
set counter=2
set n1=1
set n2=1
set ans=
:start
Echo.
Echo. 1 / 2
Echo.
Set /p N1= 1?
set /p N2= 2?
Set /p Max= Out how many Decimal Points?
:begin
set /a TmpAns=%N1%/%N2%
set ans=%TmpAns%.
:: Echo.%ans%.>Answer.txt
<nul set /p "=%Tmpans%."
set /a TmpSub=%N2%*%TmpAns%
set /a N1=%N1%-%TmpSub%
set N1=%N1%0
If NOT "%n1%" == "00" (
if %n1% LSS %N2% (
set N1=%N1%0
set ans=%ans%0
)
) else (
Goto :Finished
)
set count=0
:loop
If "%count%" == "%max%" (
Goto :Finished
)
set /a TmpAns=%N1%/%N2%
set ans=%ans%%TmpAns%
<nul set /p "=%Tmpans%"
set /a TmpSub=%N2%*%TmpAns%
set /a N1=%N1%-%TmpSub%
set N1=%N1%0
If NOT "%n1%" == "00" (
if %n1% LSS %N2% (
set N1=%N1%0
set ans=%ans%0
)
) else (
Goto :Finished
)
set /a count=%count%+1
goto :loop
:finished
cls
Echo.
Echo.
Echo.The Number
Echo.%ans%
Echo.
Echo.
set n1=1
set n2=1
pause
goto :eof
:eof
The answer put into the variable %Ans%. It can also be called with parameters. ("Divide.bat 50 27 5" would give you 50/27 out 5 decimal points.)
Since nowadays PowerShell is present on almost all machines, I would let PowerShell do the math and return the result to the batch.
Example:
set divident=10
set divisor=3
for /f "delims=" %%a in ('powershell -Command %divident%/%divisor%') do set result=%%a
#echo %result%
Explanation:
Input variables: Use set variables to define divident and divisor.
Calling powershell and assign result to a batch variable: for /f "delims=" %%a in ('powershell -Command ...) do set result=%%a (you may also check here: How to put a single PowerShell output string into a cmd variable?)
Note the above code will only work with integer input variables.
To support floating point input variables, we need to send the variables as strings inside quotations ("%variable%") and convert the strings within PowerShell back to Double, otherwise batch would interpret the commas as delimiters and PowerShell could not interpret the numbers.
Example:
set divident=10,5
set divisor=3,4
for /f "delims=" %%a in ('powershell -Command [convert]::ToDouble^(\"%divident%\"^)
/[convert]::ToDouble^(\"%divisor%\"^)') do set result=%%a
#echo %result%
Explanation:
Note in PowerShell you would do this like [convert]::ToDouble("10,5")/[convert]::ToDouble("3,5"). However in batch we need to escape the quotes using backslash, and we also need to add a "^" sign before and after the quoted parts: [convert]::ToDouble^("%divident%"^)/[convert]::ToDouble^("%divisor%"^)
If you're running in a command shell on Windows (rather than DOS), you can use VBScript to evaluate complex expressions including floating point math for you.
I have written a small helper library you can call to do this.
EvalBat Library on GitHub

Resources