Imports System
Imports System.Text
Imports System.Globalization
'''
''' Convierte números en su expresión numérica a su numeral cardinal
'''
Public NotInheritable Class NumerosAPalabras
#Region "Miembros estáticos"
Private Const UNI As Integer = 0, DIECI As Integer = 1, DECENA As Integer = 2, CENTENA As Integer = 3
Private Shared _matriz As String(,) = New String(CENTENA, 9) {
{Nothing, " uno", " dos", " tres", " cuatro", " cinco", " seis", " siete", " ocho", " nueve"},
{" diez", " once", " doce", " trece", " catorce", " quince", " dieciséis", " diecisiete", " dieciocho", " diecinueve"},
{Nothing, Nothing, Nothing, " treinta", " cuarenta", " cincuenta", " sesenta", " setenta", " ochenta", " noventa"},
{Nothing, Nothing, Nothing, Nothing, Nothing, " quinientos", Nothing, " setecientos", Nothing, " novecientos"}}
Private Const [sub] As Char = CChar(ChrW(26))
'Cambiar acá si se quiere otro comportamiento en los métodos de clase
Public Const SeparadorDecimalSalidaDefault As String = "con"
Public Const MascaraSalidaDecimalDefault As String = "00'/100.-'"
Public Const DecimalesDefault As Int32 = 2
Public Const LetraCapitalDefault As Boolean = False
Public Const ConvertirDecimalesDefault As Boolean = True
Public Const ApocoparUnoParteEnteraDefault As Boolean = False
Public Const ApocoparUnoParteDecimalDefault As Boolean = False
#End Region
#Region "Propiedades"
Private _decimales As Int32 = DecimalesDefault
Private _cultureInfo As CultureInfo = Globalization.CultureInfo.CurrentCulture
Private _separadorDecimalSalida As String = SeparadorDecimalSalidaDefault
Private _posiciones As Int32 = DecimalesDefault
Private _mascaraSalidaDecimal As String, _mascaraSalidaDecimalInterna As String = MascaraSalidaDecimalDefault
Private _esMascaraNumerica As Boolean = True
Private _letraCapital As Boolean = LetraCapitalDefault
Private _convertirDecimales As Boolean = ConvertirDecimalesDefault
Private _apocoparUnoParteEntera As Boolean = False
Private _apocoparUnoParteDecimal As Boolean
'''
''' Indica la cantidad de decimales que se pasarán a entero para la conversión
'''
''' Esta propiedad cambia al cambiar MascaraDecimal por un valor que empieze con '0'
Public Property Decimales() As Int32
Get
Return _decimales
End Get
Set(ByVal value As Int32)
If value > 10 Then
Throw New ArgumentException(value.ToString() + " excede el número máximo de decimales admitidos, solo se admiten hasta 10.")
End If
_decimales = value
End Set
End Property
'''
''' Objeto CultureInfo utilizado para convertir las cadenas de entrada en números
'''
Public Property CultureInfo() As CultureInfo
Get
Return _cultureInfo
End Get
Set(ByVal value As CultureInfo)
_cultureInfo = value
End Set
End Property
'''
''' Indica la cadena a intercalar entre la parte entera y la decimal del número
'''
Public Property SeparadorDecimalSalida() As String
Get
Return _separadorDecimalSalida
End Get
Set(ByVal value As String)
_separadorDecimalSalida = value
'Si el separador decimal es compuesto, infiero que estoy cuantificando algo,
'por lo que apocopo el "uno" convirtiéndolo en "un"
If value.Trim().IndexOf(" ") > 0 Then
_apocoparUnoParteEntera = True
Else
_apocoparUnoParteEntera = False
End If
End Set
End Property
'''
''' Indica el formato que se le dara a la parte decimal del número
'''
Public Property MascaraSalidaDecimal() As String
Get
If Not [String].IsNullOrEmpty(_mascaraSalidaDecimal) Then
Return _mascaraSalidaDecimal
Else
Return ""
End If
End Get
Set(ByVal value As String)
'determino la cantidad de cifras a redondear a partir de la cantidad de '0' o ''
'que haya al principio de la cadena, y también si es una máscara numérica
Dim i As Integer = 0
While i < value.Length AndAlso (value(i) = "0"c OrElse value(i) = "#")
i += 1
End While
_posiciones = i
If i > 0 Then
_decimales = i
_esMascaraNumerica = True
Else
_esMascaraNumerica = False
End If
_mascaraSalidaDecimal = value
If _esMascaraNumerica Then
_mascaraSalidaDecimalInterna = value.Substring(0, _posiciones) + "'" + value.Substring(_posiciones).Replace("''", [sub].ToString()).Replace("'", [String].Empty).Replace([sub].ToString(), "'") + "'"
Else
_mascaraSalidaDecimalInterna = value.Replace("''", [sub].ToString()).Replace("'", [String].Empty).Replace([sub].ToString(), "'")
End If
End Set
End Property
'''
''' Indica si la primera letra del resultado debe estár en mayúscula
'''
Public Property LetraCapital() As Boolean
Get
Return _letraCapital
End Get
Set(ByVal value As Boolean)
_letraCapital = value
End Set
End Property
'''
''' Indica si se deben convertir los decimales a su expresión nominal
'''
Public Property ConvertirDecimales() As Boolean
Get
Return _convertirDecimales
End Get
Set(ByVal value As Boolean)
_convertirDecimales = value
_apocoparUnoParteDecimal = value
If value Then
' Si la máscara es la default, la borro
If _mascaraSalidaDecimal = MascaraSalidaDecimalDefault Then
MascaraSalidaDecimal = ""
End If
ElseIf [String].IsNullOrEmpty(_mascaraSalidaDecimal) Then
MascaraSalidaDecimal = MascaraSalidaDecimalDefault
'Si no hay máscara dejo la default
End If
End Set
End Property
'''
''' Indica si de debe cambiar "uno" por "un" en las unidades.
'''
Public Property ApocoparUnoParteEntera() As Boolean
Get
Return _apocoparUnoParteEntera
End Get
Set(ByVal value As Boolean)
_apocoparUnoParteEntera = value
End Set
End Property
'''
''' Determina si se debe apococopar el "uno" en la parte decimal
'''
''' El valor de esta propiedad cambia al setear ConvertirDecimales
Public Property ApocoparUnoParteDecimal() As Boolean
Get
Return _apocoparUnoParteDecimal
End Get
Set(ByVal value As Boolean)
_apocoparUnoParteDecimal = value
End Set
End Property
#End Region
#Region "Constructores"
Public Sub New()
MascaraSalidaDecimal = MascaraSalidaDecimalDefault
SeparadorDecimalSalida = SeparadorDecimalSalidaDefault
LetraCapital = LetraCapitalDefault
ConvertirDecimales = _convertirDecimales
End Sub
Public Sub New(ByVal ConvertirDecimales As Boolean, ByVal MascaraSalidaDecimal As String, ByVal SeparadorDecimalSalida As String, ByVal LetraCapital As Boolean)
If Not [String].IsNullOrEmpty(MascaraSalidaDecimal) Then
Me.MascaraSalidaDecimal = MascaraSalidaDecimal
End If
If Not [String].IsNullOrEmpty(SeparadorDecimalSalida) Then
_separadorDecimalSalida = SeparadorDecimalSalida
End If
_letraCapital = LetraCapital
_convertirDecimales = ConvertirDecimales
End Sub
#End Region
#Region "Conversores de instancia"
Public Function ToCustomCardinal(ByVal Numero As Double) As String
Return Convertir(Convert.ToDecimal(Numero), _decimales, _separadorDecimalSalida, _mascaraSalidaDecimalInterna, _esMascaraNumerica, _letraCapital,
_convertirDecimales, _apocoparUnoParteEntera, _apocoparUnoParteDecimal)
End Function
Public Function ToCustomCardinal(ByVal Numero As String) As String
Dim dNumero As Double
If Double.TryParse(Numero, NumberStyles.Float, _cultureInfo, dNumero) Then
Return ToCustomCardinal(dNumero)
Else
Throw New ArgumentException("'" + Numero + "' no es un número válido.")
End If
End Function
Public Function ToCustomCardinal(ByVal Numero As Decimal) As String
Return ToCardinal(Numero)
End Function
Public Function ToCustomCardinal(ByVal Numero As Int32) As String
Return Convertir(Convert.ToDecimal(Numero), 0, _separadorDecimalSalida, _mascaraSalidaDecimalInterna, _esMascaraNumerica, _letraCapital,
_convertirDecimales, _apocoparUnoParteEntera, False)
End Function
#End Region
#Region "Conversores estáticos"
Public Shared Function ToCardinal(ByVal Numero As Int32) As String
Return Convertir(Convert.ToDecimal(Numero), 0, Nothing, Nothing, True, LetraCapitalDefault,
ConvertirDecimalesDefault, ApocoparUnoParteEnteraDefault, ApocoparUnoParteDecimalDefault)
End Function
Public Shared Function ToCardinal(ByVal Numero As Double) As String
Return Convertir(Convert.ToDecimal(Numero), DecimalesDefault, SeparadorDecimalSalidaDefault, MascaraSalidaDecimalDefault, True, LetraCapitalDefault,
ConvertirDecimalesDefault, ApocoparUnoParteEnteraDefault, ApocoparUnoParteDecimalDefault)
End Function
Public Shared Function ToCardinal(ByVal Numero As String, ByVal ReferenciaCultural As CultureInfo) As String
Dim dNumero As Double
If Double.TryParse(Numero, NumberStyles.Float, ReferenciaCultural, dNumero) Then
Return ToCardinal(dNumero)
Else
Throw New ArgumentException("'" + Numero + "' no es un número válido.")
End If
End Function
Public Shared Function ToCardinal(ByVal Numero As String) As String
Return NumerosAPalabras.ToCardinal(Numero, CultureInfo.CurrentCulture)
End Function
Public Shared Function ToCardinal(ByVal Numero As Decimal) As String
Return ToCardinal(Convert.ToDouble(Numero))
End Function
#End Region
Private Shared Function Convertir(ByVal Numero As Decimal, ByVal Decimales As Int32, ByVal SeparadorDecimalSalida As String, ByVal MascaraSalidaDecimal As String, ByVal EsMascaraNumerica As Boolean, ByVal LetraCapital As Boolean,
ByVal ConvertirDecimales As Boolean, ByVal ApocoparUnoParteEntera As Boolean, ByVal ApocoparUnoParteDecimal As Boolean) As String
Dim Num As Int64
Dim terna As Int32, centenaTerna As Int32, decenaTerna As Int32, unidadTerna As Int32, iTerna As Int32
Dim cadTerna As String
Dim Resultado As New StringBuilder()
Num = Math.Floor(Math.Abs(Numero))
If Num >= 1000000000001 OrElse Num < 0 Then
Throw New ArgumentException("El número '" + Numero.ToString() + "' excedió los límites del conversor: [0;1.000.000.000.001]")
End If
If Num = 0 Then
Resultado.Append(" cero")
Else
iTerna = 0
Do Until Num = 0
iTerna += 1
cadTerna = String.Empty
terna = Num Mod 1000
centenaTerna = Int(terna / 100)
decenaTerna = terna - centenaTerna * 100 'Decena junto con la unidad
unidadTerna = (decenaTerna - Math.Floor(decenaTerna / 10) * 10)
Select Case decenaTerna
Case 1 To 9
cadTerna = _matriz(UNI, unidadTerna) + cadTerna
Case 10 To 19
cadTerna = cadTerna + _matriz(DIECI, unidadTerna)
Case 20
cadTerna = cadTerna + " veinte"
Case 21 To 29
cadTerna = " veinti" + _matriz(UNI, unidadTerna).Substring(1)
Case 30 To 99
If unidadTerna <> 0 Then
cadTerna = _matriz(DECENA, Int(decenaTerna / 10)) + " y" + _matriz(UNI, unidadTerna) + cadTerna
Else
cadTerna += _matriz(DECENA, Int(decenaTerna / 10))
End If
End Select
Select Case centenaTerna
Case 1
If decenaTerna > 0 Then
cadTerna = " ciento" + cadTerna
Else
cadTerna = " cien" + cadTerna
End If
Exit Select
Case 5, 7, 9
cadTerna = _matriz(CENTENA, Int(terna / 100)) + cadTerna
Exit Select
Case Else
If Int(terna / 100) > 1 Then
cadTerna = _matriz(UNI, Int(terna / 100)) + "cientos" + cadTerna
End If
Exit Select
End Select
'Reemplazo el 'uno' por 'un' si no es en las únidades o si se solicító apocopar
If (iTerna > 1 OrElse ApocoparUnoParteEntera) AndAlso decenaTerna = 21 Then
cadTerna = cadTerna.Replace("veintiuno", "veintiún")
ElseIf (iTerna > 1 OrElse ApocoparUnoParteEntera) AndAlso unidadTerna = 1 AndAlso decenaTerna <> 11 Then
cadTerna = cadTerna.Substring(0, cadTerna.Length - 1)
'Acentúo 'veintidós', 'veintitrés' y 'veintiséis'
ElseIf decenaTerna = 22 Then
cadTerna = cadTerna.Replace("veintidos", "veintidós")
ElseIf decenaTerna = 23 Then
cadTerna = cadTerna.Replace("veintitres", "veintitrés")
ElseIf decenaTerna = 26 Then
cadTerna = cadTerna.Replace("veintiseis", "veintiséis")
End If
'Completo miles y millones
Select Case iTerna
Case 3
If Numero < 2000000 Then
cadTerna += " millón"
Else
cadTerna += " millones"
End If
Case 2, 4
If terna > 0 Then cadTerna += " mil"
End Select
Resultado.Insert(0, cadTerna)
Num = Int(Num / 1000)
Loop
End If
'Se agregan los decimales si corresponde
If Decimales > 0 Then
Dim EnteroDecimal As Int32 = Int(Math.Round((Numero - Int(Numero)) * Math.Pow(10, Decimales)))
If EnteroDecimal > 0 Then
Resultado.Append(" " + SeparadorDecimalSalida + " ")
If ConvertirDecimales Then
Dim esMascaraDecimalDefault As Boolean = MascaraSalidaDecimal = MascaraSalidaDecimalDefault
Resultado.Append(Convertir(Convert.ToDecimal(EnteroDecimal), 0, Nothing, Nothing, EsMascaraNumerica, False,
False, (ApocoparUnoParteDecimal AndAlso Not EsMascaraNumerica), False) + " " + (IIf(EsMascaraNumerica, "", MascaraSalidaDecimal)))
ElseIf EsMascaraNumerica Then
Resultado.Append(EnteroDecimal.ToString(MascaraSalidaDecimal))
Else
Resultado.Append(EnteroDecimal.ToString() + " " + MascaraSalidaDecimal)
End If
End If
End If
'Se pone la primer letra en mayúscula si corresponde y se retorna el resultado
If LetraCapital Then
Return Resultado(1).ToString().ToUpper() + Resultado.ToString(2, Resultado.Length - 2)
Else
Return Resultado.ToString().Substring(1)
End If
End Function
End Class