【Java】意外と忘れていた小数の切り捨て処理

# 0. INDEX
- 概要
- 意外と忘れていた小数の切り捨て処理
- あとがき

# 1. 概要
ふと、そういえば負の値の切り捨てってどうだったっけと思い立って検証してみました。


# 2. 意外と忘れていた小数の切り捨て処理

## 2.1. まずはJIS規格に当たってみる
切り捨ての規格は見つからなかったので、それに近い数値の丸め方（JISZ8401 : 数値の丸め方）によると、整数に丸めた場合（四捨五入とする）はこんな感じになる模様。
まあ、**絶対値で処理をする**と言えば分かりやすいか。

[経済産業省産業標準調査室（JIS検索ツール : 要アカウント）](https://www.jisc.go.jp/app/jis/general/GnrJISSearch.html)
https://www.jisc.go.jp/app/jis/general/GnrJISSearch.html

| 投入値  | 丸め後 | 備考     |
|--------:|-------:|:---------|
| 100     | 100    | - |
| 100.12  | 100    | - |
| 100.56  | 101    | 正の方向に繰り上がる |
| 0       | 0      | - |
| -100    | -100   | - |
| -100.12 | -100   | - |
| -100.56 | -101   | 負の方向に増える |

同じノリで切り捨て処理をするならこうなるだろうか

| 投入値  | 切捨後 | 備考     |
|--------:|-------:|:---------|
| 100     | 100    | - |
| 100.12  | 100    | 0に近づくように減る |
| 100.56  | 100    | 0に近づくように減る |
| 0       | 0      | - |
| -100    | -100   | - |
| -100.12 | -100   | 0に近づくように増える |
| -100.56 | -100   | 0に近づくように増える |

つまりこういう事ですかね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92447/e0d57c05-0aaf-811a-a6bc-10ed6bb9c722.png)



## 2.2. Javaによる切り捨て処理
上記のサンプルをJavaで処理してみる。処理内容としてはこんな感じ。
比較として、よくやりそうなものとして次の方法で書いてみた。

- floor / `(int) Math.floor(value)`
- abs / `(int) Math.abs(value)`
- cast / `(int) value`
- format / `String.format("%.0f", value)`
- 脳筋 / `String.format("%.0f", value - 0.5)`

### 動作サンプル
https://paiza.io/projects/gBaNeXWbSfZyHD0NQguq4Q

### ソース

```Java
public class Main {
    public static void main(String[] args) {

        StringBuilder result = new StringBuilder(String.format("%11s\t%12s %8s %8s %8s %8s %" + calcFormatLength(8, "脳筋") + "s\n", "-", "orignal", "floor", "abs", "cast", "format", "脳筋"));

        // 正の整数
        result.append(getFormatedString("正の整数", 100));

        // 正の値、四捨五入で切り捨てる範囲
        result.append(getFormatedString("正の小数", 100.12));

        // 正の値、四捨五入で繰り上げる範囲
        result.append(getFormatedString("+繰上げあり", 100.56));

        // ゼロ
        result.append(getFormatedString("ゼロ", 0));

        /**
         * 負数の四捨五入の規格について（JIS規格番号 : Z8401）
         * @see https://www.jisc.go.jp/app/jis/general/GnrJISSearch.html
         */
        // 負の整数
        result.append(getFormatedString("負の整数", -100));

        // 負の値、四捨五入で切り捨てる範囲
        result.append(getFormatedString("負の小数", -100.12));

        // 負の値、四捨五入で繰り上げる範囲
        result.append(getFormatedString("+繰上げあり", -100.56));

        // 結果出力
        System.out.println(result);
    }
    
    private static String getFormatedString(String subject, double value) {
        
        String floorValue = String.format("%d", (int) Math.floor(value));
        String absValue = String.format("%d", (int) Math.abs(value) * (value < 0 ? -1 : 1));
        String castValue = String.format("%d", (int) value);
        String formatStr = String.format("%.0f", value);
        String hotHeaded = String.format("%.0f", value - 0.5);
        
        StringBuilder formatString = new StringBuilder()
            .append("%").append(calcFormatLength(12, subject)).append("s\t")
            .append("%12s %8s %8s %8s %8s %8s\n");

        return String.format(formatString.toString(), subject, value, floorValue, absValue, castValue, formatStr, hotHeaded);
    }
    
    /**
     * String.format()で使えるように全角文字の文字数を再計算して返す
     * @param strLength 元々設定したい桁数
     * @return 指定した文字列に合うように調整した桁数（1バイト文字以外は2換算にする）
     */
    private static int calcFormatLength(int strLength, String str) {
        int digitLength = strLength;
        for (int i = 0; i < str.length(); i++) {
            if (String.valueOf(str.charAt(i)).getBytes().length != 1) {
                digitLength--;
            }
        }
        return digitLength;
    }
}
```

### 実行結果
```log
         -     orignal    floor      abs     cast   format     脳筋
   正の整数       100.0      100      100      100      100      100
   正の小数      100.12      100      100      100      100      100
+繰上げあり      100.56      100      100      100      101      100
       ゼロ         0.0        0        0        0        0       -1
   負の整数      -100.0     -100     -100     -100     -100     -101
   負の小数     -100.12     -101     -100     -100     -100     -101
+繰上げあり     -100.56     -101     -100     -100     -101     -101
```

という事で、JIS規格に近い切り捨て方法だと、整数処理に限っては単にキャストを使うのが良さそうと分かった。
JavaDocにもありますが、負数であってもfloorはあくまで丸める方向が負の方向というのが特徴的であり、罠にもなり得ます。（`1.1`以下の最大の整数は`1`ですが、`-1.1`以下の最大の整数は`-2`ですよね！）

[クラスMath - floor](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/lang/Math.html#floor(double))

https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/lang/Math.html#floor(double)


とはいえ、丸め方は本当に案件単位に変わりそうな気配はするので、都度確認しながら進めた方が良いだろうとは思う。
小数第何位で切り捨てみたいな処理の場合は、10の何乗を掛けてから処理をして…的なものが必要だろう。
（何か関数あんのかな？調べてないけど）

# 3. あとがき
今回は、割と当たり前過ぎて自分の思考が脳死状態だった切り捨て処理について改めて検証してみたというもので、掘り下げたら意外と罠がある事を再確認しましたという記事でした。
案件では負数を扱う事があまりなく（あってもさらに演算してどうのこうのってのは稀）、忘れていたので良かったです。

とりあえず、脳筋（Hot-Headedな）コードはネタ以外では二度と書かないぞと思いました。

ではﾉｼ