⇧ amazing...
Windowsの.batで小数点の計算ができないという罠
見事に泥沼にハマった...
何か、Javaのjstatで収集されたデータ(拡張子は「.tsv」のファイル。タブ区切りになっている)から、
- 平均値
- 最大値
- 最小値
を計算する処理をWindowsの「.bat」ファイルとかで実現できるんかな?って思って実装を進めていて、いよいよ計算するところまで来たら、まさかの小数点の計算に対応していないということが発覚...
Microsoftさん、やってくれたやないか...
2023年10月23日(月)追記:↓ ここから
ネットの情報をググっていたら、
バッチファイルでは本来整数演算しかできないのですが、IEEE754の単精度をエミュレートすることにより浮動小数点数演算を実現します。Project Euler 25が解ければいいので、厳密にエミュレートはしません。
IEEE754の単精度は、簡単に言うと仮数部を23ビット、指数部を8ビット、符号を1ビットで表します。これをつなげて32ビットの整数とします。例えば、リンク先の0.15625は0x3E200000となります。整数とすることで、exit /bで関数から値を返すことができます。そうするとsetlocalが使えて安全にプログラミングすることができます。
⇧ 小数点を含む計算を実現してる強強エンジニアがおられました。頑張ればできるっぽいです。と言うか、バッチファイルで用意されていないデータ型を、IEEE754の仕様を実現してデータ型を実現させるって天才やん...
2023年10月23日(月)追記:↑ ここまで
というわけで、どうしても、Windows標準の機能しか利用できない環境であるなら、PowerShellとか使っていった方が良いってことなんですかね...
何やら、PowerShellなら
⇧ Excelへの転記とかも自動化できるっぽい。
話を戻すと、Windowsの「.bat」ファイルのスクリプトが計算時に小数点が考慮できない仕様なので使い物にならないけど、一応、ソースコードを晒します。
■C:\Users\Toshinobu\Desktop\soft_work\winodws_work\work_bat\calc_max_and_min_and_avg_per_file.bat
@echo off rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 変数定義 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ setlocal enabledelayedexpansion rem ログファイル名 set log_file=result_calc.txt rem 改行コードを変数として宣言、初期化 rem ※改行コードを変数として宣言、初期化した場合、2行分のスペースが必須 set LF=^ rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 処理開始 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem 処理の開始時間 set start_date=%date% call :getStartTime echo 【処理開始日時】%start_date% %T% >> %log_file% rem メイン処理を実行 echo 【start】main call :main echo 【finish】main call :onError rem 処理の終了時間 set finish_date=%date% call :getEndTime echo 【処理終了日時】%finish_date% %T1% >> %log_file% rem 処理時間を計算 call :calcProcessingTime call :putTime echo 【処理時間】%processing_time% >> %log_file% exit /b rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 処理終了 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ メインの処理 rem ■ 【引数】 rem ■ なし rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :main set /a times_10=10 set /a unit_byte_MB=1024 rem jstatの「-gc オプション」で取得できる項目一覧を出力する処理を実行 call :echoListJstatJava11 >> %log_file% echo 【start】ファイル読み込み for /r %1 %%i in (*.tsv) do ( set /a count=0 set /a sum=0 set max=0 set min=2147483647 set file_path=%%i set /a count+=1 echo 【!count!ファイル目】!file_path! call :processFile "%%i" call :writeResultCalc ) echo 【finish】ファイル読み込み exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ ファイル読み込み rem ■ 最大値、最小値の算出も実施 rem ■ 【引数】【外部】 rem ■ ディレクトリのパス rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ファイル読み込み rem 最大値、最小値の算出も実施 :processFile echo 【start】:processFile echo 【引数】%1 set header_read=false set /a row_index=0 rem 行の数だけ繰り返し for /f "usebackq delims=" %%r in (%1) do ( echo 【start】!row_index!行目 set line[!row_index!]=%%r set /a column_index=0 rem 列の数だけ繰り返し for %%c in (%%r) do ( set col[!row_index!][!column_index!]=%%c set /a column_index=!column_index!+1 ) if !header_read! == false ( echo 【ヘッダー】 set header_read=true set header=【!col[0][2]!+!col[0][3]!+!col[0][5]!+!col[0][7]!】 echo !header! ) else ( echo 【ヘッダー以外】 rem 小数点の対策で、10倍 set /a current_S0U=col[!row_index!][2]*times_10 set /a current_S1U=col[!row_index!][3]*times_10 set /a current_EU=col[!row_index!][5]*times_10 set /a current_0U=col[!row_index!][7]*times_10 call echo %!%col[!row_index!][2]%!%=%%current_S0U%% call echo %!%col[!row_index!][3]%!%=%%current_S1U%% call echo %!%col[!row_index!][5]%!%=%%current_EU%% call echo %!%col[!row_index!][7]%!%=%%current_0U%% rem 1行分の合計 set /a sum_per_row=current_S0U+current_S1U+current_EU+current_0U echo 【SUM_PER_ROW】!sum_per_row! rem すべての行の合計 set /a sum+=sum_per_row echo 【SUM】!sum! call :updateMax !current_S0U! !current_S1U! !current_EU! !current_0U! call :updateMin !current_S0U! !current_S1U! !current_EU! !current_0U! rem 行数をインクリメント rem (ヘッダー行は行数としてカウントしない) set /a row_index=!row_index!+1 ) ) rem 平均値を算出 set /a avg=!sum!/!row_index! echo 【SUM】!sum!/【行数】!row_index! echo 【AVG】!avg! echo 【finish】:processFile exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 計算結果をファイルに書き込む処理 rem ■ 【引数】 rem ■ なし rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :writeResultCalc echo 【start】writeResultCalc echo 【処理開始】最大値、最小値、平均値の算出 if %count% equ 0 ( echo No data found. ) else ( echo 【対象ファイル】!file_path! >> %log_file% echo 【jstat取得項目】!header! >> %log_file% echo 【MAX】!max! echo 【MIN】!min! echo 【AVG】!avg! set /a result_max=!max!/times_10/unit_byte_MB echo 【最大値】Max: !result_max! >> %log_file% set /a result_min=!min!/times_10/unit_byte_MB echo 【最小値】Min: !result_min! >> %log_file% set /a result_avg=!avg!/times_10/unit_byte_MB echo 【平均値】Avg: !result_avg! >> %log_file% ) echo 【finish】writeResultCalc echo 【処理終了】最大値、最小値、平均値の算出 exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 最大値を算出する処理 rem ■ 【引数】 rem ■ @param S0U: Survivor領域0の使用率(KB) rem ■ @param S1U: Survivor領域1の使用率(KB) rem ■ @param EU: Eden領域の使用率(KB) rem ■ @param OU: Old領域の使用率(KB) rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :updateMax echo 【start】:updateMax echo 【引数】%1 %2 %3 %4 set /a value_max=%1^ +%2^ +%3^ +%4 echo 【:updateMax】【MAX】!min! echo 【:updateMax】【S0U+S1U+EU+OU】!value_max! if !value_max! gtr !max! ( set /a max=!value_max! ) echo 【CURRENT】【MAX】!max! echo 【finish】:updateMax exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 最小値を算出する処理 rem ■ 【引数】 rem ■ @param S0U: Survivor領域0の使用率(KB) rem ■ @param S1U: Survivor領域1の使用率(KB) rem ■ @param EU: Eden領域の使用率(KB) rem ■ @param OU: Old領域の使用率(KB) rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :updateMin echo 【start】:updateMin echo 【引数】%1 %2 %3 %4 set /a value_min=%1^ +%2^ +%3^ +%4 echo 【:updateMin】【MIN】!min! echo 【:updateMin】【S0U+S1U+EU+OU】!value_min! if !value_min! lss !min! ( set /a min=!value_min! ) echo 【CURRENT】【MIN】!min! echo 【finish】:updateMin exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ jstatの「-gc オプション」で取得できる項目一覧を出力する処理 rem ■ ※Java 8のjstatの「-gc オプション」で取得できる項目の全量 rem ■ 【引数】 rem ■ なし rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :echoListJstatJava8 echo 【start】:echoListJstatJava8 set list_jstat_gc_option=■jstatの「-gc オプション」で取得できる項目一覧!LF!^ 列 説明!LF!^ S0C Survivor 領域 0 の現在の容量 (KB)!LF!^ S1C Survivor 領域 1 の現在の容量 (KB)!LF!^ S0U Survivor 領域 0 の使用率 (KB)!LF!^ S1U Survivor 領域 1 の使用率 (KB)!LF!^ EC Eden 領域の現在の容量 (KB)!LF!^ EU Eden 領域の使用率 (KB)!LF!^ OC Old 領域の現在の容量 (KB)!LF!^ OU Old 領域の使用率 (KB)!LF!^ PC Permanent 領域の現在の容量 (KB)!LF!^ PU Permanent 領域の使用率 (KB)!LF!^ YGC 若い世代の GC イベント数!LF!^ YGCT 若い世代のガベージコレクション時間!LF!^ FGC フル GC イベント数!LF!^ FGCT フルガベージコレクション時間!LF!^ GCT ガベージコレクション総時間 echo !list_jstat_gc_option! echo 【finish】:echoListJstatJava8 exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ jstatの「-gc オプション」で取得できる項目一覧を出力する処理 rem ■ ※Java 11のjstatの「-gc オプション」で取得できる項目の全量 rem ■ 【引数】 rem ■ なし rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :echoListJstatJava11 echo 【start】:echoListJstatJava11 set list_jstat_gc_option=■jstatの「-gc オプション」で取得できる項目一覧!LF!^ 列 説明!LF!^ S0C: Survivor領域0の現在の容量(KB)。!LF!^ S1C: Survivor領域1の現在の容量(KB)。!LF!^ S0U: Survivor領域0の使用率(KB)。!LF!^ S1U: Survivor領域1の使用率(KB)。!LF!^ EC: Eden領域の現在の容量(KB)。!LF!^ EU: Eden領域の使用率(KB)。!LF!^ OC: Old領域の現在の容量(KB)。!LF!^ OU: Old領域の使用率(KB)。!LF!^ MC: メタスペースのコミット済サイズ(KB)。!LF!^ MU: メタスペースの使用率(KB)。!LF!^ CCSC: 圧縮されたクラスのコミット済サイズ(KB)。!LF!^ CCSU: 使用されている圧縮されたクラス領域(KB)。!LF!^ YGC: Young世代のガベージ・コレクション(GC)イベントの数。!LF!^ YGCT: Young世代のガベージ・コレクション時間。!LF!^ FGC: フルGCイベントの数。!LF!^ FGCT: フル・ガベージ・コレクションの時間。!LF!^ GCT: ガベージ・コレクションの総時間。 echo !list_jstat_gc_option! echo 【finish】:echoListJstatJava11 exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ エラーチェック rem ■ 【引数】 rem ■ @param %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■ 【戻り値】 rem ■ @return %errorlevel% rem ■ 0: 正常終了 rem ■ 0以外:異常終了(何かしらエラー) rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :onError echo 【start】:onError if not %errorlevel% == 0 ( echo 【ErrorCode】[%errorlevel%] echo エラーがありました。[%errorlevel%] >> %log_file% ) else ( echo 【ErrorCode】[%errorlevel%] echo エラーはありませんでした。[%errorlevel%] >> %log_file% ) echo 【finish】:onError exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 処理開始時刻 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :getStartTime rem 開始時刻の取得 set T=%TIME: =0% set H=%T:~0,2% set M=%T:~3,2% set S=%T:~6,2% set C=%T:~9,2% rem 先頭が0の数値が8進数として扱われないようにするための処理 set /a H=1%H%-100,M=1%M%-100,S=1%S%-100,C=1%C%-100 exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 処理終了時刻 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :getEndTime rem 終了時刻の取得 set T1=%TIME: =0% set H1=%T1:~0,2% set M1=%T1:~3,2% set S1=%T1:~6,2% set C1=%T1:~9,2% rem 先頭が0の数値が8進数として扱われないようにするための処理 set /a H1=1%H1%-100,M1=1%M1%-100,S1=1%S1%-100,C1=1%C1%-100 exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 処理時間の計算 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :calcProcessingTime rem 処理時間の計算 set /a H2=H1-H,M2=M1-M if %M2% LSS 0 set /a H2=H2-1,M2=M2+60 set /a S2=S1-S if %S2% LSS 0 set /a M2=M2-1,S2=S2+60 set /a C2=C1-C if %C2% LSS 0 set /a S2=S2-1,C2=C2+100 if %C2% LSS 10 set C2=0%C2% exit /b 0 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ rem ■ 処理時間の表示 rem ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ :putTime rem 開始・終了時刻と処理時間の表示 rem Hが時、Mが分、Sが秒、Cがコンマ以下2桁秒 rem 処理時間表示は適宜必要な単位のみ残して削除可 echo 開始時刻:%T% echo 終了時刻:%T1% echo 処理時間:%H2%h %M2%m %S2%.%C2%s set processing_time=%H2%h %M2%m %S2%.%C2%s exit /b 0
⇧ ちなみに、JavaのJavadocを真似て、@paramとか@returnとかコメントアウトで記載しているけど、Windowsの「.bat」ファイルのスクリプトの本来の作法が全く分からんです...
そもそも、Windowsの「.bat」ファイルのスクリプトの処理では「戻り値」は、数値しか返すことができないっぽい...
あとは、関数の結果を関数の外に反映させたい場合は、グローバル変数を変更する感じになるっぽい...
で、インプットは、以下に配置していて、
中身は、
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 2560.0 2560.0 0.0 2540.1 15872.0 9292.4 42496.0 14760.7 8192.0 7699.9 1024.0 857.7 3 0.055 0 0.000 0.055 2560.0 2560.0 2556.1 0.0 31744.0 3724.5 42496.0 16236.7 8192.0 7758.2 1024.0 857.7 4 0.064 0 0.000 0.064 2560.0 2560.0 2556.1 0.0 31744.0 12415.1 42496.0 16236.7 8192.0 7758.2 1024.0 857.7 4 0.064 0 0.000 0.064 2560.0 2560.0 2556.1 0.0 31744.0 23285.4 42496.0 16236.7 8192.0 7758.2 1024.0 857.7 4 0.064 0 0.000 0.064
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 2560.0 2560.0 0.0 2540.1 15872.0 9292.4 42496.0 14760.7 8192.0 7699.9 1024.0 857.7 3 0.055 0 0.000 0.055 2560.0 2560.0 2556.1 0.0 31744.0 3724.5 42496.0 16236.7 8192.0 7758.2 1024.0 857.7 4 0.064 0 0.000 0.064 2560.0 2560.0 2556.1 0.0 31744.0 12415.1 42496.0 16236.7 8192.0 7758.2 1024.0 857.7 4 0.064 0 0.000 0.064 2560.0 2560.0 2556.1 0.0 31744.0 23285.4 42496.0 16236.7 8192.0 7758.2 1024.0 857.7 4 0.064 0 0.000 0.064 13824.0 2560.0 0.0 2540.1 31744.0 1870.4 42496.0 16284.7 8448.0 7761.5 1024.0 857.7 5 0.070 0 0.000 0.070 13824.0 2560.0 0.0 2540.1 31744.0 8323.1 42496.0 16284.7 8448.0 7761.5 1024.0 857.7 5 0.070 0 0.000 0.070 13824.0 2560.0 0.0 2540.1 31744.0 8323.1 42496.0 16284.7 8448.0 7761.5 1024.0 857.7 5 0.070 0 0.000 0.070 13824.0 2560.0 0.0 2540.1 31744.0 8323.1 42496.0 16284.7 8448.0 7761.5 1024.0 857.7 5 0.070 0 0.000 0.070 13824.0 2560.0 0.0 2540.1 31744.0 8323.1 42496.0 16284.7 8448.0 7761.5 1024.0 857.7 5 0.070 0 0.000 0.070 13824.0 2560.0 0.0 2540.1 31744.0 8323.1 42496.0 16284.7 8448.0 7761.5 1024.0 857.7 5 0.070 0 0.000 0.070
⇧ という感じ。で、引数ありで「.bat」ファイルを実行する。
calc_max_and_min_and_avg_per_file.bat "[対象のディレクトリ]"
⇧ ファイルが出力され、中身を確認。
【処理開始日時】2023/10/22 16:54:52.66 【start】:echoListJstatJava11 ■jstatの「-gc オプション」で取得できる項目一覧 列 説明 S0C: Survivor領域0の現在の容量(KB)。 S1C: Survivor領域1の現在の容量(KB)。 S0U: Survivor領域0の使用率(KB)。 S1U: Survivor領域1の使用率(KB)。 EC: Eden領域の現在の容量(KB)。 EU: Eden領域の使用率(KB)。 OC: Old領域の現在の容量(KB)。 OU: Old領域の使用率(KB)。 MC: メタスペースのコミット済サイズ(KB)。 MU: メタスペースの使用率(KB)。 CCSC: 圧縮されたクラスのコミット済サイズ(KB)。 CCSU: 使用されている圧縮されたクラス領域(KB)。 YGC: Young世代のガベージ・コレクション(GC)イベントの数。 YGCT: Young世代のガベージ・コレクション時間。 FGC: フルGCイベントの数。 FGCT: フル・ガベージ・コレクションの時間。 GCT: ガベージ・コレクションの総時間。 【finish】:echoListJstatJava11 【対象ファイル】C:\Users\Toshinobu\Desktop\soft_work\winodws_work\work_bat\【テスト結果】2023-10-21-jstat\test_01\jstat_Test.tsv 【jstat取得項目】【S0U+S1U+EU+OU】 【最大値】Max: 41 【最小値】Min: 21 【平均値】Avg: 29 【対象ファイル】C:\Users\Toshinobu\Desktop\soft_work\winodws_work\work_bat\【テスト結果】2023-10-21-jstat\test_01\jstat_TestJvmOption.tsv 【jstat取得項目】【S0U+S1U+EU+OU】 【最大値】Max: 41 【最小値】Min: 20 【平均値】Avg: 27 エラーはありませんでした。[0] 【処理終了日時】2023/10/22 16:54:53.68 【処理時間】0h 0m 1.02s
⇧ 一応、平均値、最大値、最小値の計算はできていそう、ただ、小数点を計算できないので意味が無いのだけど...
他にも、参考にさせていただいたサイトが山ほどありましたが、以下、主な参考サイト様。
■遅延環境変数について
■時間の計算について
■call echoについて
■配列について
■二次元配列について
■関数について
■exitとexit /bの違いについて
■関数の戻り値について
■Microsoftの公式のドキュメント
とりあえず、Microsoftの公式のドキュメントに載っていない情報が多過ぎるんよな...
結論としては、小数点などを考慮した計算に対応していないので、Windowsの標準の環境しか利用できなくて、小数点を含む計算が必要な場合は、PowerShellを使わざるを得ないかと。(つまり、今回のように「.bat」ファイルでのスクリプトは利用してはならない)
まぁ、Windowsの「.batファイル」のわけ分からな過ぎる記述の仕方も相まって、.batを使う気になれないってのが一番大きいんだが、Windowsの「.batファイル」は極力使いたくないですかね...
ちょっとしたスクリプトが必要だとしても、何か他のプログラミング言語をインストールして、スクリプトを組みたいところですな...
あと、Windowsの「.bat」ファイルのスクリプトに関係ないんだけど、Javaのjstatで取得できる項目が、
で異なるとかも、jstatが試験的な機能だからなんかね?何か、Javaのバージョンで「GC(Garbage Collection)」が変わってるってのも影響してるんかね?
そもそも、Javaって、C言語みたいに煩わしいメモリの管理を気にしなくて済むように「GC(Garbage Collection)」とか導入されてるんじゃないのか?
情報科学を専門に勉強したことないから知らんけど...
また不毛な調査に時間を浪費してしまったではないか...
何て言うか、貴重な時間を返して欲しい...
毎度モヤモヤ感が半端ない...
今回はこのへんで。