09stephenb
09stephenb

Reputation: 9806

Batch rounding a number

I have a script that calculates disk space in bytes. I have found a way to convert it to megabytes. I have just run it and i got a this: 1867.603187561035. Is there a way of rounding it is it would be much clearer if it said 1868. Thanks for any help.

Upvotes: 0

Views: 12016

Answers (6)

Evaldas Jocys
Evaldas Jocys

Reputation: 319

Batch rounding a number with PowerShell:

@ECHO OFF
SET $value=1867.703187561035
FOR /f "usebackq" %%i IN (`PowerShell [Math]::Round^(%$value%^,0^,[MidpointRounding]::AwayFromZero^)`) DO SET $number=%%i
ECHO %$number%
PAUSE

You can specify precision and rounding method:

Upvotes: 0

aschipfl
aschipfl

Reputation: 34929

There is an even easier way to round a number to the nearest integer, supposing it is not negative and less than 100000000:

set NUMBER=1867.603187561035

rem // Enforce fractional part:
set NUMBER=%NUMBER%.
rem // Get integer part:
set /A NROUND=NUMBER+0
rem // Extract fractional part:
set NFRACT=%NUMBER:*.=%0
rem /* Prepend `1` to integer part to avoid trouble with leading `0`
rem    (remember that such numbers are interpreted as octal ones);
rem    append first fractional digit to integer part, then add `5`: */
set /A NROUND=1%NROUND%%NFRACT:~,1%+5
rem // Strip off first and last digits and return remaining integer:
if %NROUND:~,1% GTR 1 (echo 1%NROUND:~1,-1%) else (echo %NROUND:~1,-1%)

To round a signed number in the range between −100000000 and +100000000 to the nearest integer, use this code snippet:

set NUMBER=1867.603187561035

rem // Enforce fractional part:
set NUMBER=%NUMBER%.
rem // Get integer part:
set /A NROUND=NUMBER+0
rem // Cache sign and remove it temporarily:
if %NROUND% LSS 0 (set /A NROUND=-NROUND & set SIGN=-) else set SIGN=
rem // Extract fractional part:
set NFRACT=%NUMBER:*.=%0
rem /* Prepend `1` to integer part to avoid trouble with leading `0`
rem    (remember that such numbers are interpreted as octal ones);
rem    append first fractional digit to integer part, then add `5`: */
set /A NROUND=1%NROUND%%NFRACT:~,1%+5
rem // Strip off first and last digits and return remaining integer:
if %NROUND:~,1% GTR 1 (echo %SIGN%1%NROUND:~1,-1%) else (echo %SIGN%%NROUND:~1,-1%)

Update 31-Oct-2020

The above approaches may have problems with numbers with leading zeros (due to the fact that such are interpreted as octal numbers), and the used method of temporarily preceding and appending digits reduces the available numerical range.

However, here is now a script that does not have these restrictions:

  • leading zeros are properly handled;
  • the rounding range lies between -2147483647.5 and +2147483647.5;
  • it is safe against all odd provided strings;
  • it features a check (using findstr) to check the number format;
    this code block may be removed, invalid numbers then result in zero;
  • it rounds in a mathematically correct manner, so it rounds to the nearest even integer, meaning that an exact fractional part .5 is only rounded up when the integer part is odd (so 1.5 becomes 2, and 2.5 becomes 2 too);
    this can easily be changed to the traditional way (to always round up fractional parts of .5) by removing the condition if "%NFRACT:~,1%"=="5" and just keeping the code in the else clause;

So this is the code:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Provide a (signed) fractional number here:
set "NUMBER=%~2"
if defined NUMBER exit /B 1
set "NUMBER=%~1"
if not defined NUMBER set /P NUMBER=""

rem // Check whether or not the provided fractional number is actually such:
cmd /V /C echo(!NUMBER!| > nul findstr /R ^
    /C:"^ *[+-][0123456789]* *$" /C:"^ *[+-][0123456789]*\.[0123456789]* *$" ^
    /C:"^ *[0123456789]* *$" /C:"^ *[0123456789]*\.[0123456789]* *$" || exit /B

rem // Remove quotation marks, remove leading white-spaces, append dot:
if defined NUMBER set "NUMBER=%NUMBER:"=%
for /F "tokens=* eol= " %%S in (" %NUMBER%.") do set "NUMBER=%%S"
rem // Determine sign:
set "SIGN=" & if "%NUMBER:~,1%"=="-" (
    set "NUMBER=%NUMBER:~1%" & set "SIGN=-"
) else if "%NUMBER:~,1%"=="+" (
    set "NUMBER=%NUMBER:~1%" & rem set "SIGN=+"
)
rem // Remove leading zeros to avoid interpretation as octal number:
for /F "tokens=* eol=0 delims=0" %%Z in ("%NUMBER%") do set "NUMBER=%%Z"
rem // Split number into integer and fractional parts:
for /F "tokens=1 eol=. delims=." %%I in ("1%NUMBER%") do set "NROUND=%%I"
set "NROUND=%NROUND:~1%" & set "NFRACT=%NUMBER:*.=%0"
for /F "delims=0123456789 eol=0" %%J in ("%NROUND:~1%") do set "NFRACT=0"
rem // Actually round:
if "%NFRACT:~,1%"=="5" (
    rem /* Particularly handle the exact fractional portion `.5` in order
    rem    to round mathematically correct, thus to the nearest even: */
    for /F "tokens=* eol=0 delims=0" %%T in ("0%NFRACT:~1%") do (
        set "NFRACT=%%T" & set /A "NROUND+=!!(NFRACT+NROUND%%2)"
    )
) else (
    set "NFRACT=%NFRACT:~,1%" & set /A "NROUND+=NFRACT/5"
)
rem // Correct wrong sign when (2^31 - 1) needed to be rounded up:
if %NROUND% lss 0 set /A "NROUND-=1"
rem // Return resulting rounded number:
if %NROUND% equ 0 (echo %NROUND%) else echo %SIGN%%NROUND%

endlocal
exit /B

Upvotes: 1

foxidrive
foxidrive

Reputation: 41244

Here is a native batch/jscript hybrid posted recently that will
return a MB figure, for a byte figure given as %1 (and not limited to 2GB figures as batch math is)

It does not round up or round down the partial megabyte after the decimal point, it just removes it.

@if (@CodeSection == @Batch) @then

@echo off
set JScall=Cscript //nologo //E:JScript "%~F0"
for /f "delims=." %%a in ('%JScall% "%~1/1024/1024"') do set "size=%%a"
echo %size%
goto :EOF

@end
WScript.Echo(eval(WScript.Arguments.Unnamed.Item(0)));

Upvotes: 1

David Ruhmann
David Ruhmann

Reputation: 11367

It would be better if you showed the script in question, but yes, rounding can be performed. We just have to do the process manually. Here is a routine that will take in the number and a variable for the result.

:Round <Input> <Output>
setlocal
for /f "tokens=1,2 delims=." %%A in ("%~1") do set "X=%%~A" & set "Y=%%~B0"
if %Y:~0,1% geq 5 set /a "X+=1"
endlocal & set "%~2=%X%"
exit /b 0

Usage:

@echo off
setlocal
call :Round 12345.6789 Out
echo %Out%
endlocal
exit /b 0

Upvotes: 1

SachaDee
SachaDee

Reputation: 9545

If you just want get the number before the . :

@echo off

set $value=1867.703187561035

for /f "tokens=1 delims=." %%a in ('echo %$Value%') do set %$number%=%%a

echo %$Number%

And if you really want to test the second part of the number :

@echo off

setlocal EnableDelayedExpansion
set $value=1867.703187561035

for /f "tokens=1,2 delims=." %%a in ('echo %$Value%') do (
set $Number=%%a
set $Vtest=%%b
if "!$Vtest:~0,1!" geq "5" set /a $Number+=1
)

echo !$Number!

Upvotes: 1

npocmaka
npocmaka

Reputation: 57262

@echo off
set "number_to_round=1867.603187561035"

for /f "tokens=1,2 delims=." %%a  in ("%number_to_round%") do (
  set first_part=%%a
  set second_part=%%b
)

set second_part=%second_part:~0,1%
echo %second_part%
if defined second_part if %second_part% GEQ 5 ( 

    set /a rounded=%first_part%+1
) else ( 
    set /a rounded=%first_part%
)

echo %rounded%

OR (with the javascript call you can get more precise results and work with long number (while batch is limited to integers))

@echo off
set number_to_round=1867.603187561035

set "beginJS=mshta "javascript:close(new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1).Write(Math.round(%number_to_round%)"
set "endJS=));""

for /f %%N in (
  '%beginJS%%endJS%'
) do set rounded=%%N

echo %rounded%

Upvotes: 3

Related Questions