| | | @@ 変更前 1行目〜 / 変更後 1行目〜 @@ |
| 1 | 1 | | package main |
| 2 | 2 | | |
| 3 | 3 | | import ( |
| 4 | | − | "math" |
| 5 | | − | "strconv" |
| 4 | + | "fmt" |
| 5 | + | "math/big" |
| 6 | 6 | | ) |
| 7 | 7 | | |
| 8 | 8 | | // Output は請求書の税抜・消費税・税込(円)。 |
| | | @@ 変更前 12行目〜 / 変更後 12行目〜 @@ |
| 12 | 12 | | Inclusive int64 `json:"inclusive"` |
| 13 | 13 | | } |
| 14 | 14 | | |
| 15 | | − | // v1: 素直に書いた版(float64 と math.Floor / math.Round / math.Ceil)。 |
| 16 | | − | func applyRounding(v float64, mode string) int64 { |
| 17 | | − | switch mode { |
| 18 | | − | case "round": |
| 19 | | − | return int64(math.Round(v)) |
| 20 | | − | case "ceil": |
| 21 | | − | return int64(math.Ceil(v)) |
| 22 | | − | default: |
| 23 | | − | return int64(math.Floor(v)) |
| 15 | + | // v2: math/big.Rat で正確に計算し、端数処理は絶対値基準で自前で行う。 |
| 16 | + | // Go の math/big には「0方向・0から遠い方へ・四捨五入」を名指しする丸めが無いため、 |
| 17 | + | // 商と余り(QuoRem は 0 方向に切り捨てる)から決める。 |
| 18 | + | func toYen(v *big.Rat, mode string) (int64, error) { |
| 19 | + | q, r := new(big.Int).QuoRem(v.Num(), v.Denom(), new(big.Int)) // Denom は常に正 |
| 20 | + | if r.Sign() != 0 { |
| 21 | + | away := false |
| 22 | + | switch mode { |
| 23 | + | case "floor": // 切り捨て(0 方向) |
| 24 | + | case "ceil": // 切り上げ(0 から遠い方へ) |
| 25 | + | away = true |
| 26 | + | case "round": // 四捨五入(|余り|×2 ≥ 分母 なら 0 から遠い方へ) |
| 27 | + | away = new(big.Int).Lsh(new(big.Int).Abs(r), 1).Cmp(v.Denom()) >= 0 |
| 28 | + | default: |
| 29 | + | return 0, fmt.Errorf("unknown rounding: %s", mode) |
| 30 | + | } |
| 31 | + | if away { |
| 32 | + | q.Add(q, big.NewInt(int64(r.Sign()))) |
| 33 | + | } |
| 24 | 34 | | } |
| 35 | + | if !q.IsInt64() { |
| 36 | + | return 0, fmt.Errorf("out of int64 range: %s", q) |
| 37 | + | } |
| 38 | + | return q.Int64(), nil |
| 25 | 39 | | } |
| 26 | 40 | | |
| 27 | 41 | | // CalcInvoiceTax は端数処理をして税額を求める。 |
| 28 | 42 | | func CalcInvoiceTax(basis string, amount int64, rate string, rounding string) (Output, error) { |
| 29 | | − | r, err := strconv.ParseFloat(rate, 64) |
| 43 | + | r, ok := new(big.Rat).SetString(rate) |
| 44 | + | if !ok { |
| 45 | + | return Output{}, fmt.Errorf("invalid rate: %q", rate) |
| 46 | + | } |
| 47 | + | a := new(big.Rat).SetInt64(amount) |
| 48 | + | if basis == "exclusive" { |
| 49 | + | tax, err := toYen(new(big.Rat).Mul(a, r), rounding) |
| 50 | + | if err != nil { |
| 51 | + | return Output{}, err |
| 52 | + | } |
| 53 | + | return Output{amount, tax, amount + tax}, nil |
| 54 | + | } |
| 55 | + | onePlusRate := new(big.Rat).Add(big.NewRat(1, 1), r) |
| 56 | + | exclusive, err := toYen(new(big.Rat).Quo(a, onePlusRate), rounding) |
| 30 | 57 | | if err != nil { |
| 31 | 58 | | return Output{}, err |
| 32 | 59 | | } |
| 33 | | − | if basis == "exclusive" { |
| 34 | | − | tax := applyRounding(float64(amount)*r, rounding) |
| 35 | | − | return Output{amount, tax, amount + tax}, nil |
| 36 | | − | } |
| 37 | | − | exclusive := applyRounding(float64(amount)/(1+r), rounding) |
| 38 | 60 | | return Output{exclusive, amount - exclusive, amount}, nil |
| 39 | 61 | | } |