| | | @@ 変更前 2行目〜 / 変更後 2行目〜 @@ |
| 2 | 2 | | |
| 3 | 3 | | import ( |
| 4 | 4 | | "fmt" |
| 5 | | − | "math" |
| 6 | | − | "strconv" |
| 5 | + | "math/big" |
| 7 | 6 | | "strings" |
| 8 | 7 | | ) |
| 9 | 8 | | |
| 10 | 9 | | var jaUnits = []string{"", "万", "億", "兆", "京", "垓", "秭", "穣", "溝"} |
| 11 | 10 | | |
| 12 | 11 | | var enUnits = []struct { |
| 13 | | − | e int |
| 12 | + | e int64 |
| 14 | 13 | | word string |
| 15 | 14 | | }{{18, "quintillion"}, {15, "quadrillion"}, {12, "trillion"}, {9, "billion"}, {6, "million"}, {3, "thousand"}} |
| 16 | 15 | | |
| | | @@ 変更前 25行目〜 / 変更後 24行目〜 @@ |
| 25 | 24 | | return b.String() |
| 26 | 25 | | } |
| 27 | 26 | | |
| 27 | + | func pow10(e int64) *big.Int { return new(big.Int).Exp(big.NewInt(10), big.NewInt(e), nil) } |
| 28 | + | |
| 29 | + | func enScale(a *big.Int) string { |
| 30 | + | if a.Cmp(big.NewInt(1000)) < 0 { |
| 31 | + | return commafy(a.String()) |
| 32 | + | } |
| 33 | + | for _, u := range enUnits { |
| 34 | + | d := pow10(u.e) |
| 35 | + | if a.Cmp(d) < 0 { |
| 36 | + | continue |
| 37 | + | } |
| 38 | + | q, r := new(big.Int).QuoRem(new(big.Int).Mul(a, pow10(6)), d, new(big.Int)) |
| 39 | + | if new(big.Int).Lsh(r, 1).Cmp(d) >= 0 { // 四捨五入(0.5 ちょうどは繰り上げ) |
| 40 | + | q.Add(q, big.NewInt(1)) |
| 41 | + | } |
| 42 | + | ip, fp := new(big.Int).QuoRem(q, pow10(6), new(big.Int)) |
| 43 | + | frac := strings.TrimRight(fmt.Sprintf("%06s", fp.String()), "0") |
| 44 | + | if frac != "" { |
| 45 | + | frac = "." + frac |
| 46 | + | } |
| 47 | + | return ip.String() + frac + " " + u.word |
| 48 | + | } |
| 49 | + | return commafy(a.String()) |
| 50 | + | } |
| 51 | + | |
| 28 | 52 | | // Convert は大きな数を3つの表記にする。 |
| 29 | | − | // v1: よくある書き方(strconv.ParseInt で int64 に読み、英語表記は float64 で割って FormatFloat(x, 'f', 6))。 |
| 53 | + | // v2: math/big.Int で読む(strconv.ParseInt は 64ビットを超えるとエラー)。英語表記の丸めも整数で行う。 |
| 30 | 54 | | func Convert(value string) (map[string]string, error) { |
| 31 | | − | n, err := strconv.ParseInt(value, 10, 64) |
| 32 | | − | if err != nil { |
| 33 | | − | return nil, err |
| 55 | + | n, ok := new(big.Int).SetString(value, 10) |
| 56 | + | if !ok { |
| 57 | + | return nil, fmt.Errorf("invalid integer: %q", value) |
| 34 | 58 | | } |
| 35 | 59 | | sign := "" |
| 36 | | − | a := n |
| 37 | | − | if n < 0 { |
| 38 | | − | sign, a = "-", -n |
| 60 | + | if n.Sign() < 0 { |
| 61 | + | sign = "-" |
| 39 | 62 | | } |
| 63 | + | a := new(big.Int).Abs(n) |
| 40 | 64 | | var parts []string |
| 41 | | − | for i, rest := 0, a; rest > 0; i++ { |
| 42 | | − | if g := rest % 10000; g != 0 { |
| 43 | | − | parts = append([]string{fmt.Sprintf("%d%s", g, jaUnits[i])}, parts...) |
| 65 | + | rest, g, tenK := new(big.Int).Set(a), new(big.Int), big.NewInt(10000) |
| 66 | + | for i := 0; rest.Sign() > 0; i++ { |
| 67 | + | rest.QuoRem(rest, tenK, g) |
| 68 | + | if g.Sign() != 0 { |
| 69 | + | parts = append([]string{g.String() + jaUnits[i]}, parts...) |
| 44 | 70 | | } |
| 45 | | − | rest /= 10000 |
| 46 | 71 | | } |
| 47 | 72 | | ja := strings.Join(parts, "") |
| 48 | 73 | | if ja == "" { |
| 49 | 74 | | ja = "0" |
| 50 | 75 | | } |
| 51 | | − | plainAbs := commafy(strconv.FormatInt(a, 10)) |
| 52 | | − | en := plainAbs |
| 53 | | − | if a >= 1000 { |
| 54 | | − | for _, u := range enUnits { |
| 55 | | − | if float64(a) >= math.Pow10(u.e) { |
| 56 | | − | x := strconv.FormatFloat(float64(a)/math.Pow10(u.e), 'f', 6, 64) |
| 57 | | − | en = strings.TrimRight(strings.TrimRight(x, "0"), ".") + " " + u.word |
| 58 | | − | break |
| 59 | | − | } |
| 60 | | − | } |
| 61 | | − | } |
| 62 | | − | return map[string]string{"plain": sign + plainAbs, "ja": sign + ja, "en": sign + en}, nil |
| 76 | + | return map[string]string{"plain": sign + commafy(a.String()), "ja": sign + ja, "en": sign + enScale(a)}, nil |
| 63 | 77 | | } |