Batch rounding a number

后端 未结 5 657
滥情空心
滥情空心 2021-01-25 21:23

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

5条回答
  •  盖世英雄少女心
    2021-01-25 21:53

    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
    

提交回复
热议问题