Skip to content

Commit

Permalink
Merge branch 'master' into by_language_added
Browse files Browse the repository at this point in the history
  • Loading branch information
SkiBY authored Apr 12, 2023
2 parents 99b21a5 + be6b2df commit 0cdbc87
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 64 deletions.
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ In code there's only one function to use::
>>> num2words(42, lang='fr')
quarante-deux

Besides the numerical argument, there are two main optional arguments.
Besides the numerical argument, there are two main optional arguments, ``to:`` and ``lang:``

**to:** The converter to use. Supported values are:

Expand All @@ -85,6 +85,7 @@ Besides the numerical argument, there are two main optional arguments.
* ``dk`` (Danish)
* ``en_GB`` (English - Great Britain)
* ``en_IN`` (English - India)
* ``en_NG`` (English - Nigeria)
* ``es`` (Spanish)
* ``es_CO`` (Spanish - Colombia)
* ``es_VE`` (Spanish - Venezuela)
Expand Down
17 changes: 9 additions & 8 deletions num2words/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@

from __future__ import unicode_literals

from . import (lang_AM, lang_AR, lang_AZ, lang_BY, lang_CZ, lang_DE, lang_DK, lang_EN,
lang_EN_IN, lang_EO, lang_ES, lang_ES_CO, lang_ES_NI,
lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE, lang_FR_CH,
lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, lang_IT,
lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, lang_NL,
lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, lang_RU,
lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, lang_TR,
lang_UK, lang_VI)
from . import (lang_AM, lang_AR, lang_AZ, lang_BY, lang_CZ, lang_DE, lang_DK,
lang_EN, lang_EN_IN, lang_EN_NG, lang_EO, lang_ES, lang_ES_CO,
lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE,
lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS,
lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV,
lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO,
lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH,
lang_TR, lang_UK, lang_VI)

CONVERTER_CLASSES = {
'am': lang_AM.Num2Word_AM(),
Expand All @@ -34,6 +34,7 @@
'cz': lang_CZ.Num2Word_CZ(),
'en': lang_EN.Num2Word_EN(),
'en_IN': lang_EN_IN.Num2Word_EN_IN(),
'en_NG': lang_EN_NG.Num2Word_EN_NG(),
'fa': lang_FA.Num2Word_FA(),
'fr': lang_FR.Num2Word_FR(),
'fr_CH': lang_FR_CH.Num2Word_FR_CH(),
Expand Down
114 changes: 84 additions & 30 deletions num2words/lang_AR.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA

import decimal
import math
import re
from decimal import Decimal
from math import floor

from .base import Num2Word_Base

CURRENCY_SR = [("ريال", "ريالان", "ريالات", "ريالاً"),
("هللة", "هللتان", "هللات", "هللة")]
CURRENCY_EGP = [("جنيه", "جنيهان", "جنيهات", "جنيهاً"),
Expand All @@ -37,11 +41,13 @@
]


class Num2Word_AR(object):
errmsg_too_big = "Too large"
max_num = 10 ** 36
class Num2Word_AR(Num2Word_Base):
errmsg_toobig = "abs(%s) must be less than %s."
MAXVAL = 10**51

def __init__(self):
super().__init__()

self.number = 0
self.arabicPrefixText = ""
self.arabicSuffixText = ""
Expand Down Expand Up @@ -75,34 +81,47 @@ def __init__(self):
"", "مائة", "مئتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة",
"سبعمائة", "ثمانمائة", "تسعمائة"
]

self.arabicAppendedTwos = [
"مئتا", "ألفا", "مليونا", "مليارا", "تريليونا", "كوادريليونا",
"كوينتليونا", "سكستيليونا"
"كوينتليونا", "سكستيليونا", "سبتيليونا", "أوكتيليونا ",
"نونيليونا", "ديسيليونا", "أندسيليونا", "دوديسيليونا",
"تريديسيليونا", "كوادريسيليونا", "كوينتينيليونا"
]
self.arabicTwos = [
"مئتان", "ألفان", "مليونان", "ملياران", "تريليونان",
"كوادريليونان", "كوينتليونان", "سكستيليونان"
"كوادريليونان", "كوينتليونان", "سكستيليونان", "سبتيليونان",
"أوكتيليونان ", "نونيليونان ", "ديسيليونان", "أندسيليونان",
"دوديسيليونان", "تريديسيليونان", "كوادريسيليونان", "كوينتينيليونان"
]
self.arabicGroup = [
"مائة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون",
"كوينتليون", "سكستيليون"
"كوينتليون", "سكستيليون", "سبتيليون", "أوكتيليون", "نونيليون",
"ديسيليون", "أندسيليون", "دوديسيليون", "تريديسيليون",
"كوادريسيليون", "كوينتينيليون"
]
self.arabicAppendedGroup = [
"", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً",
"كوينتليوناً", "سكستيليوناً"
"كوينتليوناً", "سكستيليوناً", "سبتيليوناً", "أوكتيليوناً",
"نونيليوناً", "ديسيليوناً", "أندسيليوناً", "دوديسيليوناً",
"تريديسيليوناً", "كوادريسيليوناً", "كوينتينيليوناً"
]
self.arabicPluralGroups = [
"", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات",
"كوينتليونات", "سكستيليونات"
"كوينتليونات", "سكستيليونات", "سبتيليونات", "أوكتيليونات",
"نونيليونات", "ديسيليونات", "أندسيليونات", "دوديسيليونات",
"تريديسيليونات", "كوادريسيليونات", "كوينتينيليونات"
]
assert len(self.arabicAppendedGroup) == len(self.arabicGroup)
assert len(self.arabicPluralGroups) == len(self.arabicGroup)
assert len(self.arabicAppendedTwos) == len(self.arabicTwos)

def number_to_arabic(self, arabic_prefix_text, arabic_suffix_text):
self.arabicPrefixText = arabic_prefix_text
self.arabicSuffixText = arabic_suffix_text
self.extract_integer_and_decimal_parts()

def extract_integer_and_decimal_parts(self):
re.split('\\.', str(self.number))
splits = re.split('\\.', str(self.number))

self.integer_value = int(splits[0])
Expand All @@ -129,22 +148,23 @@ def decimal_value(self, decimal_part):
else:
result = decimal_part

for i in range(len(result), self.partPrecision):
result += '0'
# The following is useless (never happens)
# for i in range(len(result), self.partPrecision):
# result += '0'
return result

def digit_feminine_status(self, digit, group_level):
if group_level == -1:
if self.isCurrencyPartNameFeminine:
return self.arabicFeminineOnes[int(digit)]
else:
# Note: this never happens
return self.arabicOnes[int(digit)]
elif group_level == 0:
if self.isCurrencyNameFeminine:
return self.arabicFeminineOnes[int(digit)]
else:
return self.arabicOnes[int(digit)]

else:
return self.arabicOnes[int(digit)]

Expand All @@ -159,38 +179,44 @@ def process_arabic_group(self, group_number, group_level,
ret_val = "{}".format(self.arabicAppendedTwos[0])
else:
ret_val = "{}".format(self.arabicHundreds[int(hundreds)])
if ret_val != "" and tens != 0:
ret_val += " و "

if tens > 0:
if tens < 20:
# if int(group_level) >= len(self.arabicTwos):
# raise OverflowError(self.errmsg_toobig %
# (self.number, self.MAXVAL))
assert int(group_level) < len(self.arabicTwos)
if tens == 2 and int(hundreds) == 0 and group_level > 0:
if self.integer_value in [2000, 2000000, 2000000000,
2000000000000, 2000000000000000,
2000000000000000000]:
pow = int(math.log10(self.integer_value))
if self.integer_value > 10 and pow % 3 == 0 and \
self.integer_value == 2 * (10 ** pow):
ret_val = "{}".format(
self.arabicAppendedTwos[int(group_level)])
else:
ret_val = "{}".format(
self.arabicTwos[int(group_level)])
else:
if ret_val != "":
ret_val += " و "

if tens == 1 and group_level > 0 and hundreds == 0:
# Note: this never happens
# (hundreds == 0 only if group_number is 0)
ret_val += ""
elif (tens == 1 or tens == 2) and (
group_level == 0 or group_level == -1) and \
hundreds == 0 and remaining_number == 0:
# Note: this never happens (idem)
ret_val += ""
elif tens == 1 and group_level > 0:
ret_val += self.arabicGroup[int(group_level)]
else:
ret_val += self.digit_feminine_status(int(tens),
group_level)
else:
ones = tens % 10
tens = (tens / 10) - 2
if ones > 0:
if ret_val != "" and tens < 4:
ret_val += " و "

ret_val += self.digit_feminine_status(ones, group_level)
if ret_val != "" and ones != 0:
ret_val += " و "
Expand All @@ -199,8 +225,23 @@ def process_arabic_group(self, group_number, group_level,

return ret_val

# We use this instead of built-in `abs` function,
# because `abs` suffers from loss of precision for big numbers
def abs(self, number):
return number if number >= 0 else -number

# We use this instead of `"{:09d}".format(number)`,
# because the string conversion suffers from loss of
# precision for big numbers
def to_str(self, number):
integer = int(number)
if integer == number:
return str(integer)
decimal = round((number - integer) * 10**9)
return str(integer) + "." + "{:09d}".format(decimal).rstrip("0")

def convert(self, value):
self.number = "{:.9f}".format(value)
self.number = self.to_str(value)
self.number_to_arabic(self.arabicPrefixText, self.arabicSuffixText)
return self.convert_to_arabic()

Expand All @@ -218,9 +259,16 @@ def convert_to_arabic(self):

while temp_number > Decimal(0):

number_to_process = int(
Decimal(str(temp_number)) % Decimal(str(1000)))
temp_number = int(Decimal(temp_number) / Decimal(1000))
temp_number_dec = Decimal(str(temp_number))
try:
number_to_process = int(temp_number_dec % Decimal(str(1000)))
except decimal.InvalidOperation:
decimal.getcontext().prec = len(
temp_number_dec.as_tuple().digits
)
number_to_process = int(temp_number_dec % Decimal(str(1000)))

temp_number = int(temp_number_dec / Decimal(1000))

group_description = \
self.process_arabic_group(number_to_process,
Expand All @@ -229,8 +277,13 @@ def convert_to_arabic(self):
if group_description != '':
if group > 0:
if ret_val != "":
ret_val = "{} و {}".format("", ret_val)
if number_to_process != 2:
ret_val = "{}و {}".format("", ret_val)
if number_to_process != 2 and number_to_process != 1:
# if group >= len(self.arabicGroup):
# raise OverflowError(self.errmsg_toobig %
# (self.number, self.MAXVAL)
# )
assert group < len(self.arabicGroup)
if number_to_process % 100 != 1:
if 3 <= number_to_process <= 10:
ret_val = "{} {}".format(
Expand Down Expand Up @@ -294,8 +347,8 @@ def convert_to_arabic(self):
return formatted_number

def validate_number(self, number):
if number >= self.max_num:
raise OverflowError(self.errmsg_too_big)
if number >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (number, self.MAXVAL))
return number

def set_currency_prefer(self, currency):
Expand Down Expand Up @@ -329,7 +382,7 @@ def to_ordinal(self, number, prefix=''):
self.currency_unit = ('', '', '', '')
self.arabicPrefixText = prefix
self.arabicSuffixText = ""
return "{}".format(self.convert(abs(number)).strip())
return "{}".format(self.convert(self.abs(number)).strip())

def to_year(self, value):
value = self.validate_number(value)
Expand All @@ -339,6 +392,7 @@ def to_ordinal_num(self, value):
return self.to_ordinal(value).strip()

def to_cardinal(self, number):
self.isCurrencyNameFeminine = False
number = self.validate_number(number)
minus = ''
if number < 0:
Expand All @@ -349,4 +403,4 @@ def to_cardinal(self, number):
self.arabicPrefixText = ""
self.arabicSuffixText = ""
self.arabicOnes = ARABIC_ONES
return minus + self.convert(value=abs(number)).strip()
return minus + self.convert(value=self.abs(number)).strip()
37 changes: 37 additions & 0 deletions num2words/lang_EN_NG.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA

from __future__ import unicode_literals

from . import lang_EN


class Num2Word_EN_NG(lang_EN.Num2Word_EN):

CURRENCY_FORMS = {'NGN': (('naira', 'naira'), ('kobo', 'kobo'))}

CURRENCY_ADJECTIVES = {'NGN': 'Nigerian'}

def to_currency(
self, val, currency='NGN',
kobo=True, separator=',',
adjective=False
):
result = super(Num2Word_EN_NG, self).to_currency(
val, currency=currency, cents=kobo, separator=separator,
adjective=adjective)
return result
2 changes: 1 addition & 1 deletion num2words/lang_EO.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def setup(self):
self.pointword = "komo"
self.errmsg_nonnum = u"Sole nombroj povas esti konvertita en vortojn."
self.errmsg_toobig = (
u"Tro granda nombro por esti konvertita en vortojn."
u"Tro granda nombro por esti konvertita en vortojn (abs(%s) > %s)."
)
self.exclude_title = ["kaj", "komo", "minus"]
self.mid_numwords = [(1000, "mil"), (100, "cent"), (90, "naŭdek"),
Expand Down
5 changes: 3 additions & 2 deletions num2words/lang_FA.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@


class Num2Word_FA(object):
errmsg_too_big = "Too large"
max_num = 10 ** 36
# Those are unused
errmsg_toobig = "Too large"
MAXNUM = 10 ** 36

def __init__(self):
self.number = 0
Expand Down
4 changes: 3 additions & 1 deletion num2words/lang_FR.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def setup(self):
self.errmsg_nonnum = (
u"Seulement des nombres peuvent être convertis en mots."
)
self.errmsg_toobig = u"Nombre trop grand pour être converti en mots."
self.errmsg_toobig = (
u"Nombre trop grand pour être converti en mots (abs(%s) > %s)."
)
self.exclude_title = ["et", "virgule", "moins"]
self.mid_numwords = [(1000, "mille"), (100, "cent"),
(80, "quatre-vingts"), (60, "soixante"),
Expand Down
Loading

0 comments on commit 0cdbc87

Please sign in to comment.