ya_kansuji 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog +10 -0
- data/README.md +26 -5
- data/lib/ya_kansuji/core_ext/numeric.rb +10 -0
- data/lib/ya_kansuji/core_ext.rb +1 -0
- data/lib/ya_kansuji/core_refine.rb +6 -0
- data/lib/ya_kansuji/formatter/gov.rb +8 -4
- data/lib/ya_kansuji/formatter/judic_h.rb +5 -3
- data/lib/ya_kansuji/formatter/judic_v.rb +5 -3
- data/lib/ya_kansuji/formatter/lawyer.rb +6 -3
- data/lib/ya_kansuji/formatter/simple.rb +10 -2
- data/lib/ya_kansuji/formatter.rb +17 -0
- data/lib/ya_kansuji/version.rb +1 -1
- data/lib/ya_kansuji.rb +43 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ebbcd000d7dd15c3c1438fffa073ee979cda4ae3f74cc898fbe6bab3d0f2308
|
|
4
|
+
data.tar.gz: 1674ffc3d50bc2748f7700e1faa0d579ef1d4d98fdd6830a0b1f7486adc7c169
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29b3931d46062d1874d25db90d1dc62ae9f633d0328e23f704e661f85ea81816d348afed00d6bc27be90eb9e5b0d7c57c8e9a288bdd5d8727900982354fe5cd3
|
|
7
|
+
data.tar.gz: d3b04eeff5270d76b509ad9ecd0742bc26767066c488259a666650fb1fd11d45bac4604761fa99420614c17ff5313550787d6ddd1281243555501337c8781e61
|
data/ChangeLog
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
2026-07-14 Tatsuki Sugiura <sugi@nemui.org>
|
|
2
|
+
|
|
3
|
+
* Version: 1.3.0
|
|
4
|
+
* Support fractional numbers (Float / Rational / BigDecimal) on
|
|
5
|
+
to_kan, using Japanese small number units (分 厘 毛 ... 清浄)
|
|
6
|
+
* Fractions are rounded at 10^-21; floats are interpreted by their
|
|
7
|
+
shortest decimal representation (to_s)
|
|
8
|
+
* All builtin formatters render fractions in their own style
|
|
9
|
+
* Add Numeric#to_kan to core extensions and refinements
|
|
10
|
+
|
|
1
11
|
2026-07-14 Tatsuki Sugiura <sugi@nemui.org>
|
|
2
12
|
|
|
3
13
|
* Version: 1.2.0
|
data/README.md
CHANGED
|
@@ -23,9 +23,10 @@ YaKansuji は ruby 用の日本語の漢数字ライブラリです。 和暦ラ
|
|
|
23
23
|
* カンマ表現 (12,345 / 5億3,456万 / 二万、五十)
|
|
24
24
|
* 負数 (マイナス五十)
|
|
25
25
|
* 数値から漢数字, 漢字混じり数値文字列フォーマット
|
|
26
|
+
* 小数 (分厘毛〜清浄)
|
|
26
27
|
* 複数のビルトインフォーマッタ
|
|
27
28
|
* フォーマッタプラグイン機構
|
|
28
|
-
* (オプション) 標準の String,
|
|
29
|
+
* (オプション) 標準の String, Numeric クラスの拡張
|
|
29
30
|
|
|
30
31
|
## インストール
|
|
31
32
|
|
|
@@ -62,6 +63,23 @@ YaKansuji.to_kan(-12345) # => マイナス一万二千三百四十五
|
|
|
62
63
|
対応する値域は `-(10**72 - 1)` から `10**72 - 1` です。
|
|
63
64
|
数値として整数化した値がこの範囲を超える場合、`RangeError` が発生します。
|
|
64
65
|
|
|
66
|
+
#### 小数
|
|
67
|
+
|
|
68
|
+
Float・Rational・BigDecimal を渡すと、小数の命数法 (分 = 10⁻¹ 〜 清浄 = 10⁻²¹) で小数部を出力します。
|
|
69
|
+
10⁻²¹ より小さい部分は四捨五入されます。
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
YaKansuji.to_kan(0.5) # => "五分"
|
|
73
|
+
YaKansuji.to_kan(123.456) # => "百二十三・四分五厘六毛"
|
|
74
|
+
YaKansuji.to_kan(3.14159, :judic_v) # => "三・一四一五九"
|
|
75
|
+
YaKansuji.to_kan(123.456, :gov) # => "123.456"
|
|
76
|
+
YaKansuji.to_kan(1.05, :judic_h) # => "1.05"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Float は `to_s` の最短表現として解釈されるため、`0.1` は正確に「一分」になります。
|
|
80
|
+
逆に浮動小数点演算の誤差はそのまま出力されます (`0.1 + 0.2` → `0.30000000000000004` → 「三分四弾指」)。
|
|
81
|
+
誤差を避けたい場合は Rational か BigDecimal を渡してください。
|
|
82
|
+
|
|
65
83
|
このさい、2つめの引数にシンボルを渡すことにより、フォーマッタを切り替えることができます。
|
|
66
84
|
|
|
67
85
|
```ruby
|
|
@@ -84,7 +102,9 @@ YaKansuji.to_kan(12340005, :judic_h) # => "1234万0005"
|
|
|
84
102
|
|
|
85
103
|
`YaKansuji#register_formatter` を使うと独自のフォーマッタを登録できます。
|
|
86
104
|
`#call` を持つ任意のオブジェクトもしくはブロックを渡します。第1引数に数字、第2引数にオプションが渡ります。
|
|
87
|
-
負数を渡した場合も、フォーマッタには絶対値 (
|
|
105
|
+
負数を渡した場合も、フォーマッタには絶対値 (非負の値) が渡され、符号 (「マイナス」) は `to_kan` 側で前置されます。
|
|
106
|
+
小数を含む値を渡した場合、フォーマッタには正規化済みの値
|
|
107
|
+
(整数なら Integer、小数を含むなら Rational) がそのまま渡ります。
|
|
88
108
|
|
|
89
109
|
```ruby
|
|
90
110
|
YaKansuji.register_formatter :hoge, -> (num, opts = {}) {
|
|
@@ -100,14 +120,14 @@ YaKansuji.to_kan(123, :hoge) # => "たくさん"
|
|
|
100
120
|
|
|
101
121
|
### 標準クラス拡張
|
|
102
122
|
|
|
103
|
-
標準では YaKansuji の特異メソッドを直接呼ぶことで使用できますが、短く書く場合、必要応じてビルトインクラス (String,
|
|
123
|
+
標準では YaKansuji の特異メソッドを直接呼ぶことで使用できますが、短く書く場合、必要応じてビルトインクラス (String, Numeric) の拡張を使うことができます。
|
|
104
124
|
|
|
105
|
-
クラス拡張では `String#to_i`
|
|
125
|
+
クラス拡張では `String#to_i` を置き換え、`Numeric#to_kan` を追加します。
|
|
106
126
|
拡張の方法は、Refinements と直接拡張の好きな方を選択できます。
|
|
107
127
|
|
|
108
128
|
#### [Refinements](https://docs.ruby-lang.org/en/2.6.0/syntax/refinements_rdoc.html) による拡張
|
|
109
129
|
|
|
110
|
-
`YaKansuji::CoreRefine` を using すると、 `String#to_i` を置き換え、 `
|
|
130
|
+
`YaKansuji::CoreRefine` を using すると、 `String#to_i` を置き換え、 `Numeric#to_kan` を追加します。
|
|
111
131
|
|
|
112
132
|
```ruby
|
|
113
133
|
puts "二万".to_i # => 0
|
|
@@ -117,6 +137,7 @@ using YaKansuji::CoreRefine
|
|
|
117
137
|
puts "二万".to_i # => 20000
|
|
118
138
|
puts 20000.to_kan #= > "二万"
|
|
119
139
|
puts 20000.to_kan(:gov) # => "2万"
|
|
140
|
+
puts 0.5.to_kan # => "五分"
|
|
120
141
|
```
|
|
121
142
|
|
|
122
143
|
ちなみに、 `to_i` に base が渡された場合は、漢数字が解釈されずにビルトインクラスのものが呼ばれます。
|
data/lib/ya_kansuji/core_ext.rb
CHANGED
|
@@ -7,13 +7,15 @@ module YaKansuji
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def call(num, _options = {})
|
|
10
|
-
|
|
10
|
+
int, frac = Formatter.split_fraction(num)
|
|
11
|
+
frac.empty? ? frac_part = nil : frac_part = ".#{frac.join}"
|
|
12
|
+
return frac_part ? "0#{frac_part}" : '0' if int.zero?
|
|
11
13
|
|
|
12
14
|
parts = []
|
|
13
|
-
chunks = Formatter.split_by_unit4(
|
|
15
|
+
chunks = Formatter.split_by_unit4(int)
|
|
14
16
|
(chunks.size - 1).downto(0) do |idx4|
|
|
15
17
|
i4 = chunks[idx4]
|
|
16
|
-
next if i4.zero?
|
|
18
|
+
next if i4.zero? && !(idx4.zero? && frac_part)
|
|
17
19
|
|
|
18
20
|
unit4 = Formatter::UNIT4_UNITS[idx4]
|
|
19
21
|
if i4 == 1
|
|
@@ -22,7 +24,9 @@ module YaKansuji
|
|
|
22
24
|
end
|
|
23
25
|
parts << (i4.to_s + unit4)
|
|
24
26
|
end
|
|
25
|
-
parts.join(', ')
|
|
27
|
+
ret = parts.join(', ')
|
|
28
|
+
ret << frac_part if frac_part
|
|
29
|
+
ret
|
|
26
30
|
end
|
|
27
31
|
end
|
|
28
32
|
YaKansuji.register_formatter :gov, Formatter::Gov
|
|
@@ -8,14 +8,15 @@ module YaKansuji
|
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
10
|
def call(num, _options = {})
|
|
11
|
-
|
|
11
|
+
int, frac = Formatter.split_fraction(num)
|
|
12
|
+
return '0' if int.zero? && frac.empty?
|
|
12
13
|
|
|
13
14
|
ret = +''
|
|
14
15
|
head = true
|
|
15
|
-
chunks = Formatter.split_by_unit4(
|
|
16
|
+
chunks = Formatter.split_by_unit4(int)
|
|
16
17
|
(chunks.size - 1).downto(0) do |idx4|
|
|
17
18
|
i4 = chunks[idx4]
|
|
18
|
-
next if i4.zero?
|
|
19
|
+
next if i4.zero? && !(idx4.zero? && !frac.empty?)
|
|
19
20
|
|
|
20
21
|
unit4 = Formatter::UNIT4_UNITS[idx4]
|
|
21
22
|
if head
|
|
@@ -25,6 +26,7 @@ module YaKansuji
|
|
|
25
26
|
end
|
|
26
27
|
head = false
|
|
27
28
|
end
|
|
29
|
+
ret << '.' << frac.join unless frac.empty?
|
|
28
30
|
ret.tr('0-9', '0-9')
|
|
29
31
|
end
|
|
30
32
|
YaKansuji.register_formatter :judic_h, self
|
|
@@ -8,14 +8,15 @@ module YaKansuji
|
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
10
|
def call(num, _options = {})
|
|
11
|
-
|
|
11
|
+
int, frac = Formatter.split_fraction(num)
|
|
12
|
+
return '〇' if int.zero? && frac.empty?
|
|
12
13
|
|
|
13
14
|
ret = +''
|
|
14
15
|
head = true
|
|
15
|
-
chunks = Formatter.split_by_unit4(
|
|
16
|
+
chunks = Formatter.split_by_unit4(int)
|
|
16
17
|
(chunks.size - 1).downto(0) do |idx4|
|
|
17
18
|
i4 = chunks[idx4]
|
|
18
|
-
next if i4.zero?
|
|
19
|
+
next if i4.zero? && !(idx4.zero? && !frac.empty?)
|
|
19
20
|
|
|
20
21
|
unit4 = Formatter::UNIT4_UNITS[idx4]
|
|
21
22
|
if head
|
|
@@ -25,6 +26,7 @@ module YaKansuji
|
|
|
25
26
|
end
|
|
26
27
|
head = false
|
|
27
28
|
end
|
|
29
|
+
ret << '・' << frac.join unless frac.empty?
|
|
28
30
|
ret.tr('0-9', '〇一二三四五六七八九')
|
|
29
31
|
end
|
|
30
32
|
YaKansuji.register_formatter :judic_v, self
|
|
@@ -8,13 +8,15 @@ module YaKansuji
|
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
10
|
def call(num, _options = {})
|
|
11
|
-
|
|
11
|
+
int, frac = Formatter.split_fraction(num)
|
|
12
|
+
frac.empty? ? frac_part = nil : frac_part = ".#{frac.join}"
|
|
13
|
+
return frac_part ? "0#{frac_part}" : '0' if int.zero?
|
|
12
14
|
|
|
13
15
|
ret = +''
|
|
14
|
-
chunks = Formatter.split_by_unit4(
|
|
16
|
+
chunks = Formatter.split_by_unit4(int)
|
|
15
17
|
(chunks.size - 1).downto(0) do |idx4|
|
|
16
18
|
i4 = chunks[idx4]
|
|
17
|
-
next if i4.zero?
|
|
19
|
+
next if i4.zero? && !(idx4.zero? && frac_part)
|
|
18
20
|
|
|
19
21
|
unit4 = Formatter::UNIT4_UNITS[idx4]
|
|
20
22
|
if i4 == 1
|
|
@@ -24,6 +26,7 @@ module YaKansuji
|
|
|
24
26
|
|
|
25
27
|
ret << ((i4 >= 1000 ? "#{i4.to_s[0]},#{i4.to_s[1..-1]}" : i4.to_s) + unit4)
|
|
26
28
|
end
|
|
29
|
+
ret << frac_part if frac_part
|
|
27
30
|
ret
|
|
28
31
|
end
|
|
29
32
|
YaKansuji.register_formatter :lawyer, self
|
|
@@ -10,10 +10,11 @@ module YaKansuji
|
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
12
|
def call(num, _options = {})
|
|
13
|
-
|
|
13
|
+
int, frac = Formatter.split_fraction(num)
|
|
14
|
+
return '零' if int.zero? && frac.empty?
|
|
14
15
|
|
|
15
16
|
ret = nil
|
|
16
|
-
chunks = Formatter.split_by_unit4(
|
|
17
|
+
chunks = Formatter.split_by_unit4(int)
|
|
17
18
|
(chunks.size - 1).downto(0) do |idx4|
|
|
18
19
|
i4 = chunks[idx4]
|
|
19
20
|
next if i4.zero?
|
|
@@ -40,6 +41,13 @@ module YaKansuji
|
|
|
40
41
|
end
|
|
41
42
|
ret << unit4
|
|
42
43
|
end
|
|
44
|
+
unless frac.empty?
|
|
45
|
+
ret << '・' if ret
|
|
46
|
+
ret ||= +''
|
|
47
|
+
frac.each_with_index do |d, idx|
|
|
48
|
+
ret << DIGITS[d - 1] << UNIT_FRAC[idx] unless d.zero?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
43
51
|
ret || ''
|
|
44
52
|
end
|
|
45
53
|
YaKansuji.register_formatter :simple, self
|
data/lib/ya_kansuji/formatter.rb
CHANGED
|
@@ -6,6 +6,7 @@ module YaKansuji
|
|
|
6
6
|
UNIT4_BASE = 10_000
|
|
7
7
|
UNIT4_CHUNK_LIMIT = UNIT_EXP4.size + 1
|
|
8
8
|
UNIT4_UNITS = ([''] + UNIT_EXP4).freeze
|
|
9
|
+
EMPTY_FRACTION = [].freeze
|
|
9
10
|
|
|
10
11
|
module_function
|
|
11
12
|
|
|
@@ -18,5 +19,21 @@ module YaKansuji
|
|
|
18
19
|
end
|
|
19
20
|
chunks
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
def split_fraction(num)
|
|
24
|
+
num = YaKansuji.normalize_value(num)
|
|
25
|
+
raise ArgumentError, 'Value must be non-negative' if num.negative?
|
|
26
|
+
|
|
27
|
+
return [num, EMPTY_FRACTION] if num.is_a?(Integer)
|
|
28
|
+
|
|
29
|
+
int = num.truncate
|
|
30
|
+
scaled = ((num - int) * FRAC_BASE).to_i
|
|
31
|
+
digits = []
|
|
32
|
+
UNIT_FRAC.size.times do
|
|
33
|
+
scaled, d = scaled.divmod(10)
|
|
34
|
+
digits.unshift(d) unless digits.empty? && d.zero?
|
|
35
|
+
end
|
|
36
|
+
[int, digits]
|
|
37
|
+
end
|
|
21
38
|
end
|
|
22
39
|
end
|
data/lib/ya_kansuji/version.rb
CHANGED
data/lib/ya_kansuji.rb
CHANGED
|
@@ -7,6 +7,9 @@ module YaKansuji
|
|
|
7
7
|
UNIT_EXP3 = %w(十 百 千).freeze
|
|
8
8
|
UNIT_EXP4 = %w(万 億 兆 京 垓 𥝱 穣 溝 澗 正 載 極 恒河沙 阿僧祇 那由他 不可思議 無量大数).freeze
|
|
9
9
|
MAX_VALUE = (10_000**(UNIT_EXP4.size + 1)) - 1
|
|
10
|
+
UNIT_FRAC = %w(分 厘 毛 糸 忽 微 繊 沙 塵 埃 渺 漠
|
|
11
|
+
模糊 逡巡 須臾 瞬息 弾指 刹那 六徳 虚空 清浄).freeze
|
|
12
|
+
FRAC_BASE = 10**UNIT_FRAC.size
|
|
10
13
|
NUM_ALT_CHARS = '〇一二三四五六七八九0123456789零壱壹弌弐貳貮参參弎肆伍陸漆質柒捌玖拾什陌佰阡仟萬秭'
|
|
11
14
|
NUM_NORMALIZED_CHARS = '01234567890123456789011122233345677789十十百百千千万𥝱'
|
|
12
15
|
# rubocop:disable Lint/DuplicateRegexpCharacterClassElement
|
|
@@ -81,6 +84,44 @@ module YaKansuji
|
|
|
81
84
|
matched[0].start_with?('マイナス') ? -ret : ret
|
|
82
85
|
end
|
|
83
86
|
|
|
87
|
+
def normalize_value(num)
|
|
88
|
+
return num if num.is_a?(Integer)
|
|
89
|
+
|
|
90
|
+
unless num.is_a?(Numeric)
|
|
91
|
+
return num._to_i_ya_kansuji_orig if num.respond_to?(:_to_i_ya_kansuji_orig)
|
|
92
|
+
|
|
93
|
+
return num.to_i
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
return num.to_i unless num.respond_to?(:truncate)
|
|
97
|
+
|
|
98
|
+
num = rational_from_float(num) if num.is_a?(Float)
|
|
99
|
+
num = num.to_r if num.respond_to?(:to_r)
|
|
100
|
+
int = num.truncate
|
|
101
|
+
scaled = ((num - int) * FRAC_BASE).round
|
|
102
|
+
if scaled.abs >= FRAC_BASE
|
|
103
|
+
if scaled.negative?
|
|
104
|
+
int -= 1
|
|
105
|
+
else
|
|
106
|
+
int += 1
|
|
107
|
+
end
|
|
108
|
+
scaled = 0
|
|
109
|
+
end
|
|
110
|
+
scaled.zero? ? int : Rational((int * FRAC_BASE) + scaled, FRAC_BASE)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def rational_from_float(num)
|
|
114
|
+
raise FloatDomainError, num.to_s if num.nan? || num.infinite?
|
|
115
|
+
|
|
116
|
+
m = /\A(-?)(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?\z/.match(num.to_s)
|
|
117
|
+
raise FloatDomainError, num.to_s unless m
|
|
118
|
+
|
|
119
|
+
ret = Rational(Integer(m[2] + (m[3] || ''), 10))
|
|
120
|
+
ret = -ret unless m[1].empty?
|
|
121
|
+
exp = (m[4] ? Integer(m[4], 10) : 0) - (m[3] ? m[3].size : 0)
|
|
122
|
+
exp >= 0 ? ret * (10**exp) : ret / (10**-exp)
|
|
123
|
+
end
|
|
124
|
+
|
|
84
125
|
def register_formatter(sym, proc = nil, &block)
|
|
85
126
|
if block_given?
|
|
86
127
|
@@formatters[sym] = block
|
|
@@ -100,8 +141,8 @@ module YaKansuji
|
|
|
100
141
|
end
|
|
101
142
|
|
|
102
143
|
def to_kan(num, formatter = :simple, options = {})
|
|
103
|
-
num
|
|
104
|
-
if num.abs > MAX_VALUE
|
|
144
|
+
num = normalize_value(num)
|
|
145
|
+
if num.truncate.abs > MAX_VALUE
|
|
105
146
|
raise RangeError, "Value must be between #{-MAX_VALUE} and #{MAX_VALUE}"
|
|
106
147
|
end
|
|
107
148
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ya_kansuji
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tatsuki Sugiura
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Pure ruby library of Japanese kansuji.
|
|
@@ -25,6 +25,7 @@ files:
|
|
|
25
25
|
- lib/ya_kansuji.rb
|
|
26
26
|
- lib/ya_kansuji/core_ext.rb
|
|
27
27
|
- lib/ya_kansuji/core_ext/integer.rb
|
|
28
|
+
- lib/ya_kansuji/core_ext/numeric.rb
|
|
28
29
|
- lib/ya_kansuji/core_ext/string.rb
|
|
29
30
|
- lib/ya_kansuji/core_refine.rb
|
|
30
31
|
- lib/ya_kansuji/formatter.rb
|