miércoles, 7 de abril de 2010

Convertir números a letras con PHP

Esta función sirve para convertir un numero con dos decimales a letras. Máxima cantidad a convertir (18 dígitos con 2 decimales) 999,999,999,999,999,999.99

NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE BILLONES NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE MILLONES NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE PESOS 99/100 M.N.

En esta liga podrás decargar el código: numeros_a_letras.zip
Si lo que prefieres es convertir números a letras en CodeIgniter 2, haz clic aquí
Si lo que prefieres es convertir números a letras usando POO/MVC, haz clic aquí

Este bloque sirve para crear un archivo .php de ejemplo para lanzar el conversor de números a letras:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title>Conversión numeros a letras</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>

    <body onLoad="document.forma1.cantidad.focus();">
        <form name="forma1" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
            <input type="text" name="cantidad" value="<?php echo isset($_POST['cantidad']) ? $_POST['cantidad'] : ''; ?>" size="50" maxlength="21" />  
            <input type="submit" name="boton1" value="Convertir...">
            <br/> 
            <textarea cols="70" rows="5"><?php echo isset($_POST['cantidad']) ? numtoletras($_POST['cantidad']) : ''; ?></textarea>
        </form>
    </body>
</html>
En seguida, el código PHP que convierte el número a letras:
<?php

//------    CONVERTIR NUMEROS A LETRAS         ---------------
//------    Máxima cifra soportada: 18 dígitos con 2 decimales
//------    999,999,999,999,999,999.99
// NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE BILLONES
// NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE MILLONES
// NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE PESOS 99/100 M.N.
//------    Creada por:                        ---------------
//------             ULTIMINIO RAMOS GALÁN     ---------------
//------            uramos@gmail.com           ---------------
//------    10 de junio de 2009. México, D.F.  ---------------
//------    PHP Version 4.3.1 o mayores (aunque podría funcionar en versiones anteriores, tendrías que probar)
function numtoletras($xcifra)
{
    $xarray = array(0 => "Cero",
        1 => "UN", "DOS", "TRES", "CUATRO", "CINCO", "SEIS", "SIETE", "OCHO", "NUEVE",
        "DIEZ", "ONCE", "DOCE", "TRECE", "CATORCE", "QUINCE", "DIECISEIS", "DIECISIETE", "DIECIOCHO", "DIECINUEVE",
        "VEINTI", 30 => "TREINTA", 40 => "CUARENTA", 50 => "CINCUENTA", 60 => "SESENTA", 70 => "SETENTA", 80 => "OCHENTA", 90 => "NOVENTA",
        100 => "CIENTO", 200 => "DOSCIENTOS", 300 => "TRESCIENTOS", 400 => "CUATROCIENTOS", 500 => "QUINIENTOS", 600 => "SEISCIENTOS", 700 => "SETECIENTOS", 800 => "OCHOCIENTOS", 900 => "NOVECIENTOS"
    );
//
    $xcifra = trim($xcifra);
    $xlength = strlen($xcifra);
    $xpos_punto = strpos($xcifra, ".");
    $xaux_int = $xcifra;
    $xdecimales = "00";
    if (!($xpos_punto === false)) {
        if ($xpos_punto == 0) {
            $xcifra = "0" . $xcifra;
            $xpos_punto = strpos($xcifra, ".");
        }
        $xaux_int = substr($xcifra, 0, $xpos_punto); // obtengo el entero de la cifra a covertir
        $xdecimales = substr($xcifra . "00", $xpos_punto + 1, 2); // obtengo los valores decimales
    }

    $XAUX = str_pad($xaux_int, 18, " ", STR_PAD_LEFT); // ajusto la longitud de la cifra, para que sea divisible por centenas de miles (grupos de 6)
    $xcadena = "";
    for ($xz = 0; $xz < 3; $xz++) {
        $xaux = substr($XAUX, $xz * 6, 6);
        $xi = 0;
        $xlimite = 6; // inicializo el contador de centenas xi y establezco el límite a 6 dígitos en la parte entera
        $xexit = true; // bandera para controlar el ciclo del While
        while ($xexit) {
            if ($xi == $xlimite) { // si ya llegó al límite máximo de enteros
                break; // termina el ciclo
            }

            $x3digitos = ($xlimite - $xi) * -1; // comienzo con los tres primeros digitos de la cifra, comenzando por la izquierda
            $xaux = substr($xaux, $x3digitos, abs($x3digitos)); // obtengo la centena (los tres dígitos)
            for ($xy = 1; $xy < 4; $xy++) { // ciclo para revisar centenas, decenas y unidades, en ese orden
                switch ($xy) {
                    case 1: // checa las centenas
                        if (substr($xaux, 0, 3) < 100) { // si el grupo de tres dígitos es menor a una centena ( < 99) no hace nada y pasa a revisar las decenas
                            
                        } else {
                            $key = (int) substr($xaux, 0, 3);
                            if (TRUE === array_key_exists($key, $xarray)){  // busco si la centena es número redondo (100, 200, 300, 400, etc..)
                                $xseek = $xarray[$key];
                                $xsub = subfijo($xaux); // devuelve el subfijo correspondiente (Millón, Millones, Mil o nada)
                                if (substr($xaux, 0, 3) == 100)
                                    $xcadena = " " . $xcadena . " CIEN " . $xsub;
                                else
                                    $xcadena = " " . $xcadena . " " . $xseek . " " . $xsub;
                                $xy = 3; // la centena fue redonda, entonces termino el ciclo del for y ya no reviso decenas ni unidades
                            }
                            else { // entra aquí si la centena no fue numero redondo (101, 253, 120, 980, etc.)
                                $key = (int) substr($xaux, 0, 1) * 100;
                                $xseek = $xarray[$key]; // toma el primer caracter de la centena y lo multiplica por cien y lo busca en el arreglo (para que busque 100,200,300, etc)
                                $xcadena = " " . $xcadena . " " . $xseek;
                            } // ENDIF ($xseek)
                        } // ENDIF (substr($xaux, 0, 3) < 100)
                        break;
                    case 2: // checa las decenas (con la misma lógica que las centenas)
                        if (substr($xaux, 1, 2) < 10) {
                            
                        } else {
                            $key = (int) substr($xaux, 1, 2);
                            if (TRUE === array_key_exists($key, $xarray)) {
                                $xseek = $xarray[$key];
                                $xsub = subfijo($xaux);
                                if (substr($xaux, 1, 2) == 20)
                                    $xcadena = " " . $xcadena . " VEINTE " . $xsub;
                                else
                                    $xcadena = " " . $xcadena . " " . $xseek . " " . $xsub;
                                $xy = 3;
                            }
                            else {
                                $key = (int) substr($xaux, 1, 1) * 10;
                                $xseek = $xarray[$key];
                                if (20 == substr($xaux, 1, 1) * 10)
                                    $xcadena = " " . $xcadena . " " . $xseek;
                                else
                                    $xcadena = " " . $xcadena . " " . $xseek . " Y ";
                            } // ENDIF ($xseek)
                        } // ENDIF (substr($xaux, 1, 2) < 10)
                        break;
                    case 3: // checa las unidades
                        if (substr($xaux, 2, 1) < 1) { // si la unidad es cero, ya no hace nada
                            
                        } else {
                            $key = (int) substr($xaux, 2, 1);
                            $xseek = $xarray[$key]; // obtengo directamente el valor de la unidad (del uno al nueve)
                            $xsub = subfijo($xaux);
                            $xcadena = " " . $xcadena . " " . $xseek . " " . $xsub;
                        } // ENDIF (substr($xaux, 2, 1) < 1)
                        break;
                } // END SWITCH
            } // END FOR
            $xi = $xi + 3;
        } // ENDDO

        if (substr(trim($xcadena), -5, 5) == "ILLON") // si la cadena obtenida termina en MILLON o BILLON, entonces le agrega al final la conjuncion DE
            $xcadena.= " DE";

        if (substr(trim($xcadena), -7, 7) == "ILLONES") // si la cadena obtenida en MILLONES o BILLONES, entoncea le agrega al final la conjuncion DE
            $xcadena.= " DE";

        // ----------- esta línea la puedes cambiar de acuerdo a tus necesidades o a tu país -------
        if (trim($xaux) != "") {
            switch ($xz) {
                case 0:
                    if (trim(substr($XAUX, $xz * 6, 6)) == "1")
                        $xcadena.= "UN BILLON ";
                    else
                        $xcadena.= " BILLONES ";
                    break;
                case 1:
                    if (trim(substr($XAUX, $xz * 6, 6)) == "1")
                        $xcadena.= "UN MILLON ";
                    else
                        $xcadena.= " MILLONES ";
                    break;
                case 2:
                    if ($xcifra < 1) {
                        $xcadena = "CERO PESOS $xdecimales/100 M.N.";
                    }
                    if ($xcifra >= 1 && $xcifra < 2) {
                        $xcadena = "UN PESO $xdecimales/100 M.N. ";
                    }
                    if ($xcifra >= 2) {
                        $xcadena.= " PESOS $xdecimales/100 M.N. "; //
                    }
                    break;
            } // endswitch ($xz)
        } // ENDIF (trim($xaux) != "")
        // ------------------      en este caso, para México se usa esta leyenda     ----------------
        $xcadena = str_replace("VEINTI ", "VEINTI", $xcadena); // quito el espacio para el VEINTI, para que quede: VEINTICUATRO, VEINTIUN, VEINTIDOS, etc
        $xcadena = str_replace("  ", " ", $xcadena); // quito espacios dobles
        $xcadena = str_replace("UN UN", "UN", $xcadena); // quito la duplicidad
        $xcadena = str_replace("  ", " ", $xcadena); // quito espacios dobles
        $xcadena = str_replace("BILLON DE MILLONES", "BILLON DE", $xcadena); // corrigo la leyenda
        $xcadena = str_replace("BILLONES DE MILLONES", "BILLONES DE", $xcadena); // corrigo la leyenda
        $xcadena = str_replace("DE UN", "UN", $xcadena); // corrigo la leyenda
    } // ENDFOR ($xz)
    return trim($xcadena);
}

// END FUNCTION

function subfijo($xx)
{ // esta función regresa un subfijo para la cifra
    $xx = trim($xx);
    $xstrlen = strlen($xx);
    if ($xstrlen == 1 || $xstrlen == 2 || $xstrlen == 3)
        $xsub = "";
    //
    if ($xstrlen == 4 || $xstrlen == 5 || $xstrlen == 6)
        $xsub = "MIL";
    //
    return $xsub;
}

// END FUNCTION
?>

En esta liga podrás decargar el código: numeros_a_letras.zip

43 comentarios:

  1. waooo muy bueno man... me fue muy util.. excelente..

    ResponderEliminar
  2. Da un error en la linea 78, usando codeigniter favor revisar, Gracias

    ResponderEliminar
  3. Algun tip para utilizar separador de miles??

    Saludos y gracias.

    ResponderEliminar
  4. muchas gracias por tu aporte es magnifico

    ahora deja lo pruebo con mas detalle

    ResponderEliminar
  5. Excelente... en realidad si que es bueno.
    Eres el better

    ResponderEliminar
  6. gracias, La tienes de Burro, de donde eres

    ResponderEliminar
  7. Gracias por el aporte

    ResponderEliminar
  8. Que tal amigos! intente implementar esta funciones para PHP 5.3 pero al ejecutarlas me aparecia un error:Undefined index. Despues de darle vueltas y vueltas buscando la falla se me ocurrio que tendria que ver con el indice del array xarray[] pues al buscar la cifra que estuvieramos tratando y no encontrarla en dicho array EN VERSIONES ANTERIORES DE PHP la busqueda devolvia un null y PHP lo tomaba como un false que era lo que se esperaba. Pero en las versiones mas actualizadas PHP es mas estricto con los indices de los arreglos y no permite comparar valores que no coinciden con los indices. Por eso la solucion es sustituir las lineas
    $xseek = $xarray[substr($xaux, 0, 3)]; y $xseek = $xarray[substr($xaux, 1, 2)]; por estas otras: $xseek = in_array(substr($xaux, 0, 3),$xarray); y $xseek = in_array(substr($xaux, 1, 2),$xarray);

    ResponderEliminar
    Respuestas
    1. Tienes toda la razón, amigo, muchas gracias por tu aportación. Saludos.

      Eliminar
    2. Muy bueno tu script, en general me funcionó excelente, aunque tuve algunos pequeños inconvenientes, te adjunto lo que encontré:

      $xcadena = str_replace("Y Pesos", "Mil Pesos", $xcadena); // Cuando termina en miles
      if(substr($xcifra, -4, 4) == "0000"){
      $xcadena = str_replace("Pesos M/Cte", "Mil Pesos M/Cte", $xcadena); // Miles
      }
      $xcadena = str_replace("Mil Mil", "Mil", $xcadena); // Corregir Leyenda
      $xcadena = str_replace("De Mil Pesos", "De Pesos", $xcadena); // Corregir Leyenda
      $xcadena = str_replace("Ciento Mil", "Cien Mil", $xcadena); // Cuando termina en ciento
      $xcadena = str_replace("Veintipesos", "Veinte Mil Pesos", $xcadena); // Cuando termina en Veinti

      De resto todo esta excelente muchas gracias

      Eliminar
  9. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  10. Hubiera sido mejor que le quitaras por defecto el pesos, por ejemplo yo lo necesito para cantidades no monetarias el que quiera pesos, o dolares o lo que sea que lo agregue despues de llamar la función pero excelente aporte, gracias.

    ResponderEliminar
  11. Es muy bueno, pero al manejar números como del 11 al 29 en lugar de escribir once, doce, veintiseis, etc, obtienes diez y uno, diez y dos, veinte y seis... habría que corregir ese detalle, pero en general eres muy bueno.

    ResponderEliminar
    Respuestas
    1. Hola Luis, primeramete gracias por tu comentario. Al respecto se me hace raro que te dé estos resultados para los números que mencionas pues este código lo he utilizado para imprimir contratos y se sigue utilizando, incluso lo acabo de probar otra vez y me funciona bien, te dejó aquí la liga para que lo puedas descargar: http://ultiminioramos.vacau.com/codigos/2010/numeros.zip, Saludos.

      Eliminar
  12. Hola,
    hace un tiempo se me presentó la necesidad de algo asi. Aunque lo necesitaba como mínimo en catalán y, también necesito la conversión inversa y detección dentro de cadenas con más texto.

    No encontré nada que se ajustara a lo que necesito y, en realidad, no es nada prioritario (sólo una interesante mejora) por lo que, lógicamente, no valia la pena el trabajo de implementarlo...

    ...en el trabajo. Pero me pareció un ejercicio interesante así que, tras darle un par de vueltas, me puse a ello y creo que me salió algo bastante potable....

    Lo he tenido perdido por casa bastante tiempo hasta que hace poco decidí empezar a recuperar cachibaches que tengo perdidos por ahí para subirlos a GitHub y me volví a enganchar...

    De momento sólo convierte en un sentido (números a texto), pero puede hacerlo en vários idiomas (la idea es que en cualquiera que se le configure) y variaciones de los mismos (como género, dialectos, ordinales...) sin más límites que los que imponga el própio idioma si es el caso.

    Por ahora soporta:
    * Catalán masculino y femenino (creo que sin errores).
    * Castellano masculino y femenino (creo que sin errores).
    * Inglés (no muy testado, pero creo que va bien).
    * Alemán (con la ayuda de tutoriales web. Probablemente haya incorrecciones).
    * Números romanos

    Si a alguien le interesa, puede encontrarlo en:

    https://github.com/bitifet/alphanum/blob/master/lib/alphanum.class.php


    No hay mucha documentación todavia, pero el código está comentado y es bastante autoexplicativo. Y si alguien tiene alguna duda puede abrir una incidencia en el mismo GitHub y así queda para otros que puedan tener el mismo problema ;-)

    Aún faltan muchas cosas, como soporte para decimales o, sobretodo, la conversión inversa, pero poquito a poco... ;-)


    Saludos.

    ResponderEliminar
  13. excelente ayuda de tu parte...muchas gracias..

    ResponderEliminar
  14. Hola Ultiminio, Soy Martín R. Mondragon...

    Solo para saludarte, Tu función esta muy buena....
    Tengo una clase para convertir números a letras que subí a la red para compartir, utilizo la recursividad, para resolver nombres de hasta cientos de miles de octillones...

    échale un ojo...

    http://mygnet.net/codigos/php/calculoyconversiones/clase_para_convertir_numeros_a_letras_con_php.3615

    Estamos en contacto...

    sld.

    ResponderEliminar
  15. estoy probando el codigo que menciona al principio, pero marca este error desde el principio


    Fatal error: Call to undefined function numtoletras() in C:\AppServ\www\numeros-letras\index.php on line 14


    cual puede ser el problema.
    y el link para descargar redirecciona al 000webhost.com y no al archivo

    espero la respuesta es para un trabajo

    ResponderEliminar
    Respuestas
    1. Hola, ya corregí el link para descargar el código de prueba. además hice el update al código para que corra en las versiones de php 5.3+.

      Eliminar
  16. Está excelente, muchas gracias por tu aporte me ha sido de mucha ayuda.

    ResponderEliminar
  17. oye amigo muchas gracias x compartir con todos nosotros tu trabajo se te agradesco muxisimo saludos y buena suerte

    ResponderEliminar
  18. Muy Buena Funcion, Excelente lo que necesito, gracias por el aporte. Saludos desde Orizaba ver.

    ResponderEliminar
  19. Hola amigo muy buena funcion, muchas gracias por tu aporte, me sirvio mucho. Saludos

    ResponderEliminar
  20. gracias broder por el aporte era lo ultimo que me faltaba para completar mi sistema

    muchas gracias

    suerte..

    ResponderEliminar
  21. Genial, muchas gracias! Me salvaste mucho tiempo :)

    ResponderEliminar
  22. Hola Ultiminio !
    Para agradecerte tu valiosa ayuda !

    ResponderEliminar
  23. muy bueno pero tien un error, en ves de mil, sale un mil, como puedo arreglarlo?

    ResponderEliminar
  24. hola excelente aporte solo una duda y solo pregunto yo sigo como aplicar el siguiente forma, estyo tratando de aplicar el helper para numeros sin el tipo de moneda, que deberia añadir para que tampoco pierda la opción de usar el tipo moneda

    saludos !!!

    ResponderEliminar
  25. Gracias por el codigo, Excelente !!

    ResponderEliminar
  26. Excelente muchas gracias por el aporte

    ResponderEliminar
  27. muy bueno!

    Sugerencia de dos ajusticos:
    $xcadena = str_replace("veintitres", utf8_encode("veintitrés"), $xcadena);
    $xcadena = str_replace("dieciseis", utf8_encode("dieciséis"), $xcadena);

    ResponderEliminar
  28. Funciona muy bien y gracias por el aporte!

    ResponderEliminar
  29. Buen resultado INMEDIATO !!
    Gracias mil por el aporte (Y)

    ResponderEliminar
  30. MUCHAS GRACIAS AMIGO ME SIRVIO BASTANTE EN PHP

    ResponderEliminar
  31. Buenas noches amigo, gracias por tu ayuda con todo respeto te quiero hacer una pregunta. Estoy desarrollando una aplicación comercial la cual vendo a empresas, puedo utilizar este codigo para proyectos comerciales o debo de pagar algo por ello? Gracias amigo. Att: Abrahanns

    ResponderEliminar
    Respuestas
    1. Buena noche Abrahanns, puedes utilizar este código sin restricciones para tus proyectos comerciales. Saludos.

      Eliminar
  32. Muy bueno y funcional el codigo, muchas gracias por el aporte. Felicidades

    ResponderEliminar

Datos personales

Mi foto
Podrás encontrar códigos recursos y artículos sobre PHP, JavaScript, jQuery, MooTools, Ajax, CSS, HTML, UML, RUP, AUP, XP (eXtreme Programming), Six-Sigma, CMMI, FrameWorks, Zend Framework, Magento, CodeIgniter, CakePHP, Joomla 1.5, Doctrine, Active Record, ORM, POO, MVC, MySql, PostgreSql. Este espacio está destinado a ayudar y compartir un poco de lo mucho que he recibido de la comunidad en la Red.