Open kwagdy opened 3 years ago
Well done, I wonder if you will support sub currency as well (قروش) Also would you happen to have a .Net version of this?
Please visit this page: https://github.com/MohsenAlyafei/arqamAR
for a work in progress on a new fully featured library. Please see the readme file. Currently testing the javascript code.
Currently testing the javascript code.
Thank you! One really long page! how will it be different than the current "Tafqit" code? By the way, I have copied your code to .NET in order to integrate it with my application. Not sure if that would be helpful, but I can send it your way if it will benefit others. Thanks a lot for the wonderful effort, jAk 👍
Currently testing the javascript code.
Thank you! One really long page! how will it be different than the current "Tafqit" code? By the way, I have copied your code to .NET in order to integrate it with my application. Not sure if that would be helpful, but I can send it your way if it will benefit others. Thanks a lot for the wonderful effort, jAk 👍
arqamAR is a large code (40k minified) and completely comprehensive as it provides lots of features and options for fraction numbers and for 173 currencies plus abilities to add more currencies and subjects, but 60% of the size are the tables for currencies information and very large numbers handling.
With regards to .NET, what is the core programming language you used (I never tried .NET :-))
If you translated it to .NET in that case, I do not recommend that you add that part for "نصب جر", i.e. the option {AG} as this will require additional code once you use fractions or currencies. It is easy to remove it (only 5 lines need modification).
It would be good to have a look at your .NET code, I might learn something new.
Regards
I am using C#.NET. I don't think you should find trouble learning it, you already know the programming concepts and there are tons of tutorials out there.
Frankly I didn't apply all the options, most of the non-required options in my particular case was set to a default values in the function call as they are sent as parameters. So it is probably uglier that it should be. But it's a good start -thanks to you!- if someone needs to follow your steps on a .NET application :).
I also had to handle the piasters/fractions case by splitting the number by the '.' and calling the function twice, then handling the case for fractions when it is a single digit, 0.2 for example, since it will be sent to the function as 2, to multiple by 10 in that case, so it would result in 20 'عشرون قرشا' instead of just '2'.
So in brief:
Main Tafqit Function...
/*********************************************************************
* @function : tafqit(Number, _params..._)
* @purpose : Converts Numbers to Arabic Words with Grammar Rules
* @version : 1.60
* @author : Mohsen Alyafei
* @date : 04 August 2020
* @modified date: 13 November 2021 (_ported to .NET_)
* @Licence : MIT
* @param : {Number} [Integer in Numeric or String form]
* Number may be in Arabic-Indic format (as a string)
* @params : 9 Options passed as True/False value as follows:
*
* {Feminine} : True : Generate string for a Feminine subject (أرقام بصيغة المؤنث).
* The default is the Masculine form (_false_).
* {Miah} : True: Use Mi'ah for Hundreds (مئة بدل مائة). Default is Ma'ah "مائة".
* {Comma} : True: Insert comma between triplet words.
* {SplitHund} : True: Split number from hundred words (فصل الرقم عن المئة).
i.e. ثلاث مائة. Default "No Split" i.e. (ثلاثمائة).
* {Billions} : True: Use Billions (بليون) instead of Miliard (مليار).
* {TextToFollow} : True: Indicates that there will be text to follow the resulting text.
* This permits proper subject name to be added after the resulting text.
* {AG} : True: Text is produced in Accusative/Genitive (جر/نصب) case.
* The default is the Nominative case (رفع).
*
* {CurrPart} : "main" denotes main part of the number (default). "frac" denotes fraction part of the number.
* {Subject} : An optional array holding the Subject Name to be counted in 4 forms as follows:
* [0] = Deafult Name Singular (e.g. "كتاب/تفاحة/دينار").
* [1] = Name for 2's (double) (e.g. "كتابان/تفاحتان/ديناران").
* [2] = Name for plural (e.g. "كتب/تفاحات/دنانير").
* [3] = Name Singular with Tanween (e.g. "كتابًا/تفاحةً/دينارًا").
* The subject name will be added to the resulting string in accordance
* with the number grammar rules.
* {Legal} : True: Uses the lagal form of output text.
*
* @returns : {string} The wordified number string in Arabic.
**********************************************************************/
public static string Tafqit(int NumIn = 0, bool Feminine = false, bool Comma = false, bool SplitHund = false, bool Miah = true,
bool Billions = false, bool TextToFollow = false, bool AG = false, bool Legal = false, string CurrPart = "main")
{
//{Feminine,Comma,SplitHund,Miah,Billions,TextToFollow,AG,Subject,Legal}={}
string[] TableScales = { "", "ألف", "مليون", "مليار", "ترليون", "كوادرليون", "كوينتليون", "سكستليون" }, // Add here only
TableScalesP = { "", "آلاف", "ملايين", "مليارات" }, // Do not change this table
TableMale = { "", "واحد", "اثنان", "ثلاثة", "أربعة", "خمسة", "ستة", "سبعة", "ثمانية", "تسعة", "عشرة" },
TableFemale = { "", "واحدة", "اثنتان", "ثلاث", "أربع", "خمس", "ست", "سبع", "ثمان", "تسع", "عشر" };
string[] Subject;
if (CurrPart == "frac")
{
Subject = new[] { "قرشا", "قرشان", "قروش", "قرشا" };
}
else
{
Subject = new[] { "جنيه", "جنيهان", "جنيهات", "جنيهاً" };
}
if (NumIn == 0) return "صفر"; // if 0 or "0" then "zero"
int Triplet=0, ScalePos;
string Scale, ScalePlural, NumberInWords = "";
bool IsLastEffTriplet = false;
bool IsAG = (AG == true); // Option Accusative or Genitive case Grammar?
string SpWa = " و"; // AND word
string Ahad = "أحد"; // Masculine
//string Ehda = "إحدى"; //Feminine 11
// ---- Setup constants for the AG Option (Accusative/Genitive or Nominative case Grammar)
string Taa = IsAG ? "تي" : "تا", Taan = IsAG ? "تين" : "تان"; // Hundred 2's مئتا/مائتا مئتان/مائتان
string Aa = IsAG ? "ي" : "ا", Aan = IsAG ? "ين" : "ان"; // Scale 2's الفا/مليونا الفان/مليونان
string Ethna = IsAG ? "اثني" : "اثنا", Ethnata = IsAG ? "اثنتي" : "اثنتا"; // Masculine/Feminine 12 starting word
string Ethnan = IsAG ? "اثنين" : "اثنان", Ethnatan = IsAG ? "اثنتين" : "اثنتان"; // Masculine/Feminine 2
string Woon = IsAG ? "ين" : "ون"; // Second part of 20's to 90's
//string IsSubject = Array.isArray(Subject) && Subject.length === 4; // Check for Subject Array Names
//if (IsSubject) TextToFollow = false;
// Convert Arabic-Indic Numbers to Arabic if any
//+ NumIn.ToString().Replace("/[٠-٩] /g", d => "٠١٢٣٤٥٦٧٨٩".IndexOf(d));
string newNumIn; // Make numeric string
//string wordMiah = (Miah == true) ? "مئة" : "مائة"; // Select chosen Miah (Hundred) Option
var TableUnits = new string[TableMale.Length];
TableMale.CopyTo(TableUnits, 0); // Create copies of Masculine Table for manipulation
var Table11_19 = new string[TableMale.Length];
TableMale.CopyTo(Table11_19, 0); // Create copies of Masculine Table for manipulation
Table11_19[0] = TableFemale[10]; // Borrow word "عشرة" from Feminine's Table for use in 11-19
Table11_19[1] = Ahad; // Masculine starting words for 11
Table11_19[2] = Ethna; // Masculine starting words for 12
TableUnits[2] = Ethnan; // Masculine word for 2
//NumIn = "0".repeat(NumIn.length * 2 % 3) + NumIn; // Convert Number to a Triplets String
newNumIn = NumIn.ToString();
if (! (NumIn.ToString().Length * 2 % 3 == 0))
{
newNumIn = 0.ToString("D" + (NumIn.ToString().Length * 2 % 3)) + NumIn.ToString();
}
var NumLen = newNumIn.Length;
int next_triplet_idx;
for (int digits = NumLen; digits > 0; digits -= 3) // Loop and convert each Triplet
{
//Triplet = + newNumIn.Substring(NumLen - digits, 3); // Get a Triplet Number
Triplet = Convert.ToInt32(newNumIn.Substring(NumLen - digits, 3)); // Get a Triplet Number
// Determine if Last Effective Triplet
next_triplet_idx = NumLen - digits + 3;
IsLastEffTriplet = true;
if (next_triplet_idx < newNumIn.Length)
{
IsLastEffTriplet = Convert.ToInt32(newNumIn.Substring(next_triplet_idx)) == 0;
}
if (Triplet > 0)
{ // If not Empty: Convert Triplet Number to Words
ScalePos = digits / 3 - 1; // Position of Scale Name in Scale Table
Scale = TableScales[ScalePos]; // Get Scale Name
ScalePlural = (ScalePos < 4 ? TableScalesP[ScalePos] : TableScales[ScalePos] + "ات"); // Make Scale Plural
if (Billions && ScalePos == 3)
{
Scale = "بليون";
ScalePlural = "بلايين"; // If Billions Option
}
NumberInWords += oneTripletToWords(Triplet, TableFemale, SplitHund, Miah, Scale, TextToFollow, Taa, Taan, Legal, IsLastEffTriplet,
TableUnits, Subject, Table11_19, ScalePlural, Woon, Aa, Aan); // Convert 1 Triplet to Words
if (!IsLastEffTriplet) NumberInWords += (Comma ? "،" : "") + SpWa; // Add "و " and Option Comma
}
} // All done with conversion,
//Process Subject Name if any
string SubjectName = "";
int Num_99 = Triplet % 100; // 00 to 99
if (Subject.Length > 0)
{ // Process Subject Name
string space = !IsLastEffTriplet ? "" : " "; // Position correct spacing
//Triplet = +(Triplet + "").slice(-2); // Get last 2 digits of last Triplet
Triplet = Triplet % 100;
SubjectName = space + Subject[0]; // Default Subject Name is at Pos 0
if (Triplet > 10) SubjectName = space + Subject[3]; // Subject name with Tanween for 11-99
else if (Triplet > 2) SubjectName = space + Subject[2]; // Subject name Plural for 3-10
else if (Triplet > 0) SubjectName = Subject[Triplet - 1] + " " + TableUnits[Num_99]; // Reverse names for 1 or 2
}
return NumberInWords + SubjectName; // All done
}
//------------------------------------------------------------------ // Core Function Converts 1 Triplet (1 to 999) to Arabic Words //------------------------------------------------------------------
public static string oneTripletToWords(int Triplet, string[] TableFemale, bool SplitHund, bool Miah, string Scale, bool TextToFollow,
string Taa, string Taan, bool Legal, bool IsLastEffTriplet, string[] TableUnits, string[] Subject, string[] Table11_19, string ScalePlural,
string Woon, string Aa, string Aan)
{
int Num_99 = Triplet % 100, // 00 to 99
Num_100 = ~~(Triplet / 100), // Hundreds (1 digit)
Num_Unit = Num_99 % 10, // 0 to 9 (1 digit)
Num_Tens = ~~(Num_99 / 10); // Tens (1 digit)
string Word_100 = "",
Word_99 = ""; // Holds words for Hundreds & 0-99
string wordMiah = (Miah == true) ? "مئة" : "مائة";
string SpWa = " و";
string TanweenLetter = "ًا"; // Tanween Fatih for Scale Names above 10
//if (Feminine && IsLastEffTriplet)
//{ // If Feminine, use the Feminine table if Last Effective Triplet
// TableUnits = [...TableFemale]; Table11_19 = [...TableFemale];// Create copies of Feminine Table for manipulation
// Table11_19[0] = TableMale[10]; // Borrow word "عشر" from Masculine's Table for use in 11-19
// Table11_19[1] = Ehda; // Feminine starting words for 11
// Table11_19[2] = Ethnata; // Feminine starting words for 12
// TableUnits[2] = Ethnatan; // Feminine word for 2
// if (Num_99 > 19) TableUnits[1] = Ehda; // Feminine word for 1 used in 20's to 90's
//}
if (Num_100 > 0)
{ // ---- Do Hundreds (100 to 900)
if (Num_100 > 2)
{
Word_100 = TableFemale[Num_100] + (SplitHund == true ? " " : "") + wordMiah; // 300-900
}
else if (Num_100 == 1)
{
Word_100 = wordMiah; // 100
}
else
{
Word_100 = wordMiah.Substring(0, wordMiah.Length - 1) + ((Scale != "" && Num_99 == 0) || TextToFollow ? Taa : Taan); // 200 Use either مئتا or مئتان
}
}
if (Num_99 > 19) Word_99 = TableUnits[Num_Unit] + (Num_Unit > 0 ? SpWa : "") + // 20-99 Units و and
(Num_Tens == 2 ? "عشر" : TableFemale[Num_Tens]) + Woon; // Add Woon for 20's or 30's to 90's
else if (Num_99 > 10) Word_99 = Table11_19[Num_99 - 10] + " " + Table11_19[0]; // 11-19
else if (Num_99 > 2 || Num_99 == 0 || Subject.Length==0) Word_99 = TableUnits[Num_99].ToString(); // 0 or 3-10 (else keep void for 1 &2)
string Words999 = Word_100 + (Num_100 > 0 && Num_99 > 0 ? SpWa : "") + Word_99; // Join Hund, Tens, and Units
if (Scale != "")
{ // Add Scale Name if applicable
string legalTxt = (Legal && Num_99 < 3) ? " " + Scale : ""; // if Legal Option add Extra Word
string Word_100Wa = (Num_100 > 0 ? Word_100 + legalTxt + SpWa : "") + Scale; // Default Scale Name
if (Num_99 > 2)
{
Words999 += " " + // Scale for for 3 to 99
(Num_99 > 10 ? Scale + (IsLastEffTriplet && TextToFollow ? "" : TanweenLetter) // Scale for 11 to 99 (Tanween)
: ScalePlural); // Scale for 3 to 10 (Plural)
}
else
{
if (Num_99 == 0) Words999 += " " + Scale; // Scale for 0
else if (Num_99 == 1) Words999 = Word_100Wa; // Scale for 1
else Words999 = Word_100Wa + (IsLastEffTriplet && TextToFollow ? Aa : Aan);// Scale for 2 ألفا or ألفان
}
}
return Words999; //Return the Triple in Words
}
Usage
int main_value = (int)passedNumber;
string mainText = "فقط " + Tafqit(main_value);
string fracText;
decimal q = Math.Floor(passedNumber);
if (!(q == passedNumber)) //check if there is a fraction part
{
string tempfrac = Decimal.Round(passedNumber, 2).ToString().Split(".")[1];
int frac_value = int.Parse(tempfrac);
if (tempfrac.Length == 1 || tempfrac.Length == 2 && tempfrac[1] == '0') //Check the case examples of 0.2 or 0.20
{
frac_value *= 10;
}
fracText = Tafqit(frac_value, CurrPart: "frac");
mainText += " و" + fracText + " لا غير.";
}
Thanks for the code.
I tried to run it here https://dotnetfiddle.net/ but I get errors. Maybe you add some examples to write to the console.
In your case, because you use Egyptian Pound and Qirsh which are both male units, then almost 40% of the code is not needed which deals with female units 😉
You are right, as I said it is not a pretty conversion, I am in a hurry, it should be done properly. But it works for now. Sorry for my poor usage documentation. I have prepared a fiddle here https://dotnetfiddle.net/R7o3bG
Thanks a lot. Good job. I will study the code and it will be my first exercise in learning C#.
You are right, as I said it is not a pretty conversion, I am in a hurry, it should be done properly. But it works for now. Sorry for my poor usage documentation. I have prepared a fiddle here https://dotnetfiddle.net/R7o3bG
Can you please direct massage me on twitter at my twitter account: @maalyafei Thanks
In the C# code, I have noticed the following errors in the output:
200 gives "مئتان جنيه". It should be "مئتا جنيه". 2000 gives "الفان جنيه". It should be "الفا جنيه". same for 2 million, etc.
You missed enabling the "TextToFollow" flag, which is needed always because there is always text after the number.
The code breaks if you enter a number from trillion and above as you are using int32 in the manipulation of the numbers.
Last, if you give a number like 20.001 it will give "فقط عشرون جنيهاً وصفر لا غير". I think this is because of the way you do the rounding.
See here a new version in C# https://github.com/MohsenAlyafei/arqamAR/blob/master/perCurrency/C_Sharp/arqamARC_EGP.cs
I have customized it to specifically deal with only EGP. But can also be used for any Male/Male currency by a few text changes.
Male/Male Currency is like Egyptian Pound as it has both Male Major Unit "جنيه" and also Male Minor Unit "قرش".
I have simplified lots of the code and used strings for handling the number so that there is no limit on the size of the number.
It is my first attempt at C#.
Note that I used local functions to simplify the function calls. This needs version 7 or later of C#.
I will update and improve it when I learn more of C#.
Please note that I have updated the C# code and removed the arrays and the code that copies arrays across for better performance. Now it is a shorter code.
The female table was needed to handle the Hundreds because the word "مئة" in Arabic is a female word.
The new single array now serves the purpose of all cases with a modified triplet conversion code.
I don't use twitter much, it's unfortunate that GitHub doesn't have messaging! Thank you very much for optimizing the code, I see you got around C# quite fast :+1: .
Last, if you give a number like 20.001 it will give "فقط عشرون جنيهاً وصفر لا غير". I think this is because of the way you do the rounding.
That part actually is somewhat correct, Egypt uses 2 decimal places for "قروش" anything that comes after that should be "مليم" which is a currency we no longer use really. That's why I round the value to the nearest "قرش".
Great.
I have posted in the same folder a few other currencies. In particular, the Kuwaiti Dinar in which 1 Dinar is 1000 fils. This, as you can see, does not require additional code.
The concept I believe is that the function code should not try to attempt to do rounding. Rounding should be done before it is passed to the function. The idea is that the code is a 'translator' of numbers to words and must not attempt to do any math.
For example: if you pass the number Egyptian Pound: 3.99999 . It should only pick 3.99 and convert that to text and not change it to 4 pounds because the developer should have passed 3.99 in the first place.
Converting currencies to words is easier than doing normal numbers which have fractions because it then becomes complex and people have different tastes of how they want the output to be written; currency output text on the other hand pretty standard.
With currencies, there are other considerations (options) you may need to look into:
Example:
0.23 could be written (like now) صفر جنيه وثلاثة وعشرون قرشاً or some like to write it without zero like: ثلاثة وعشرون قرشاً i.e. without the zero. Also, some others prefer to also have the currency demonym (جنيه مصري) because it could mean other currency.
Yes C# is interesting and the only thing I don't like about it is that it is a Typed Language, reminds me of VBA.
Best regards
I agree 100%, I used your updated code for C#, did the rounding before and passed it to the function. This makes much more sense indeed, the function should not be doing any math.
or some like to write it without zero like: ثلاثة وعشرون قرشاً i.e. without the zero.
I like this one better, short to the point and clear, having "صفر جنيه " before that might be a little disorienting, no one really says that in day to day life. How do we enable this option through the code? Or did you mean this has a different approach?
Also, some others prefer to also have the currency demonym (جنيه مصري) because it could mean other currency.
This is also a great option it helps keep things clear.
I am not an experienced C# developer, but I did use VBA back in the days, I think the whole .NET is the same just with different flavors, C#, VB, ASP...
Have a nice day,
btw, The code misses the 2000, I'm getting ألف instead of ألفا or ألفين...
I think line 154 should be
else Words999 = Word_100Wa + (IsLastEffTriplet ? "ان" : "ا");
instead of
else Words999 = Word_100Wa + (IsLastEffTriplet ? "ا" : "");
btw, The code misses the 2000, I'm getting ألف instead of ألفا or ألفين...
I think line 154 should be
else Words999 = Word_100Wa + (IsLastEffTriplet ? "ان" : "ا");
instead of
else Words999 = Word_100Wa + (IsLastEffTriplet ? "ا" : "");
Thanks, absolutely correct. My quick updates and changes missed this one.
This is the problem with Arabic. We say "الفا جنيه" "مليونا جنيه" if there is no number with it, but say "ألفان" and "مليونان" if there is a number with it.
This is in fact the purpose of calculating the flag 'IsLastEffTriplet' to find out when the triplet we manipulate has no longer any valid triplets on its right hand (i.e. all remaining are 0 triplets on the RH).
Thanks again.
I have now updated and corrected the code. There were 2 other issues:
The word "ثمانية" was not getting correctly changed as "ي" was missing. I introduced this error after I removed the arrays.
You seem to use/like the tanween above the letter like "ألفاً مليوناَ", so I changed that to your style rather than "الفًا مليونًا". This matches with how you write "جنيهاً" and "قرشاً".
I have added another test file here: https://github.com/MohsenAlyafei/arqamAR/blob/master/perCurrency/C_Sharp/arqamARC_EGP%20(Tests).cs
The test file was copied from arqamAR which has a selected set of numbers to test all parts and sections of the code. It is a bit large file. It is good to run it when you change or optimize the code.
Well done, I wonder if you will support sub currency as well (قروش) Also would you happen to have a .Net version of this?