Module: Seimi::Kanji

Defined in:
lib/seimi/kanji.rb

Constant Summary collapse

DIGITS =
%w[         ].freeze
PARSE_DIGITS =
DIGITS.each_with_index.to_h.merge("" => 0).freeze
SMALL_UNITS =
{ "" => 10, "" => 100, "" => 1000 }.freeze
LARGE_UNITS =
[["", 1_000_000_000_000], ["", 100_000_000], ["", 10_000]].freeze

Class Method Summary collapse

Class Method Details

.decimal(value, digits = 3) ⇒ Object



58
59
60
61
62
# File 'lib/seimi/kanji.rb', line 58

def decimal(value, digits = 3)
  formatted = format("%.#{digits}f", value)
  integer, fraction = formatted.split(".", 2)
  "#{from_i(integer.to_i)}#{fraction.chars.map { |char| DIGITS[char.to_i] }.join}"
end

.from_i(number) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/seimi/kanji.rb', line 12

def from_i(number)
  integer = Integer(number)
  return "" if integer.zero?
  return "#{from_i(-integer)}" if integer.negative?

  groups = []
  while integer.positive?
    groups << integer % 10_000
    integer /= 10_000
  end

  groups.each_with_index.filter_map do |group, index|
    next if group.zero?

    "#{under_10000(group)}#{large_unit_at(index)}"
  end.reverse.join
end

.large_unit_at(index) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# File 'lib/seimi/kanji.rb', line 80

def large_unit_at(index)
  case index
  when 0 then ""
  when 1 then ""
  when 2 then ""
  when 3 then ""
  else
    raise ArgumentError, "number is too large"
  end
end

.rational(value) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/seimi/kanji.rb', line 49

def rational(value)
  rational = Rational(value)
  return from_i(rational.numerator) if rational.denominator == 1

  numerator = rational.numerator
  sign = numerator.negative? ? "" : ""
  "#{sign}#{from_i(rational.denominator)}分の#{from_i(numerator.abs)}"
end

.to_i(text) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/seimi/kanji.rb', line 30

def to_i(text)
  source = String(text)
  return 0 if source == "" || source == ""

  negative = source.start_with?("")
  source = source.delete_prefix("")
  total = 0

  LARGE_UNITS.each do |unit, scale|
    next unless source.include?(unit)

    head, source = source.split(unit, 2)
    total += under_10000_to_i(head) * scale
  end

  total += under_10000_to_i(source)
  negative ? -total : total
end

.under_10000(number) ⇒ Object

Raises:

  • (ArgumentError)


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/seimi/kanji.rb', line 64

def under_10000(number)
  integer = Integer(number)
  raise ArgumentError, "out of range" if integer.negative? || integer >= 10_000

  thousands, rem = integer.divmod(1000)
  hundreds, rem = rem.divmod(100)
  tens, ones = rem.divmod(10)

  [
    unit_part(thousands, ""),
    unit_part(hundreds, ""),
    unit_part(tens, ""),
    ones.positive? ? DIGITS[ones] : nil
  ].compact.join
end

.under_10000_to_i(text) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/seimi/kanji.rb', line 98

def under_10000_to_i(text)
  return 0 if text.empty?

  total = 0
  current = nil

  text.each_char do |char|
    if PARSE_DIGITS.key?(char)
      current = PARSE_DIGITS.fetch(char)
    elsif SMALL_UNITS.key?(char)
      total += (current || 1) * SMALL_UNITS.fetch(char)
      current = nil
    else
      raise ArgumentError, "unknown kanji digit: #{char}"
    end
  end

  total + (current || 0)
end

.unit_part(value, unit) ⇒ Object



91
92
93
94
95
96
# File 'lib/seimi/kanji.rb', line 91

def unit_part(value, unit)
  return nil if value.zero?
  return unit if value == 1

  "#{DIGITS[value]}#{unit}"
end