Number to words (0-9999, Spanish)

Converts an integer of up to 4 digits into its written form in Spanish words — e.g. 5432 -> "cinco mil cuatrocientos treinta y dos". Uses lookup tables (arrays) and subprograms to handle the language's real irregularities (11-15, 16-29 fused, "cien" vs "ciento").

Open in the editor →

Sample algorithm, ready to run step by step in your browser. Nothing to install.

Flowchart

Flowchart of the Number to words (0-9999, Spanish) algorithm

Pseudocode

Algoritmo main
  // Convierte un número entero entre 0 y 9999 a su forma escrita en palabras (español) — ej. 5432 -> "cinco mil cuatrocientos treinta y dos". Usa tablas (vectores) para las irregularidades reales del idioma (11-15, 16-29 fusionados, 'cien' vs 'ciento', decenas y centenas con nombre propio) en vez de intentar una fórmula puramente aritmética.

  pal(1) <- "cero"
  pal(2) <- "uno"
  pal(3) <- "dos"
  pal(4) <- "tres"
  pal(5) <- "cuatro"
  pal(6) <- "cinco"
  pal(7) <- "seis"
  pal(8) <- "siete"
  pal(9) <- "ocho"
  pal(10) <- "nueve"
  pal(11) <- "diez"
  pal(12) <- "once"
  pal(13) <- "doce"
  pal(14) <- "trece"
  pal(15) <- "catorce"
  pal(16) <- "quince"
  pal(17) <- "dieciséis"
  pal(18) <- "diecisiete"
  pal(19) <- "dieciocho"
  pal(20) <- "diecinueve"
  pal(21) <- "veinte"
  pal(22) <- "veintiuno"
  pal(23) <- "veintidós"
  pal(24) <- "veintitrés"
  pal(25) <- "veinticuatro"
  pal(26) <- "veinticinco"
  pal(27) <- "veintiséis"
  pal(28) <- "veintisiete"
  pal(29) <- "veintiocho"
  pal(30) <- "veintinueve"
  dec(3) <- "treinta"
  dec(4) <- "cuarenta"
  dec(5) <- "cincuenta"
  dec(6) <- "sesenta"
  dec(7) <- "setenta"
  dec(8) <- "ochenta"
  dec(9) <- "noventa"
  cen(2) <- "doscientos"
  cen(3) <- "trescientos"
  cen(4) <- "cuatrocientos"
  cen(5) <- "quinientos"
  cen(6) <- "seiscientos"
  cen(7) <- "setecientos"
  cen(8) <- "ochocientos"
  cen(9) <- "novecientos"
  Leer N
  Si N==0
    resultado <- "cero"
  SiNo
    miles <- TRUNC(N/1000)
    resto <- N MOD 1000
    resultado <- ""
    Si miles>0
      Si miles==1
        resultado <- "mil"
      SiNo
        resultado <- pal(miles+1) + " mil"
      FinSi
    FinSi
    Si resto>0
      ConvertirMenorQueMil(resto, pal, dec, cen, palabraResto)
      Si miles>0
        resultado <- resultado + " " + palabraResto
      SiNo
        resultado <- palabraResto
      FinSi
    FinSi
  FinSi
  Escribir "El número ", N, " en palabras es: ", resultado
FinAlgoritmo

Funcion ConvertirMenorQueMil(n, pal, dec, cen, palabra)
  // Convierte un número entre 0 y 999 a su palabra en español — maneja el caso irregular 'cien' (sin 'to') vs 'ciento' (con más dígitos detrás), y delega las decenas/unidades a ConvertirMenorQueCien.

  c <- TRUNC(n/100)
  r <- n MOD 100
  palabra <- ""
  Si c>0
    Si c==1  Y  r==0
      palabra <- "cien"
    SiNo
      Si c==1
        palabra <- "ciento"
      SiNo
        palabra <- cen(c)
      FinSi
    FinSi
  FinSi
  Si r>0
    ConvertirMenorQueCien(r, pal, dec, palabraR)
    Si c>0
      palabra <- palabra + " " + palabraR
    SiNo
      palabra <- palabraR
    FinSi
  FinSi
FinFuncion

Funcion ConvertirMenorQueCien(n, pal, dec, palabra)
  // Convierte un número entre 0 y 99 a su palabra en español, usando las tablas 'pal' (0-29, con las irregularidades de 11-15 y 16-29 fusionadas) y 'dec' (múltiplos de diez desde 30).

  Si n<=29
    palabra <- pal(n+1)
  SiNo
    d <- TRUNC(n/10)
    u <- n MOD 10
    palabra <- dec(d)
    Si u>0
      palabra <- palabra + " y " + pal(u+1)
    FinSi
  FinSi
FinFuncion

The same algorithm in Java

package org.desarrollo;

import java.util.ArrayList;
import java.util.Scanner;

/**
 * Proyecto Java generado por freeDFDWeb a partir de un diagrama DFD.
 */
public class FreeDFDMain {
    /**
     * Convierte un número entero entre 0 y 9999 a su forma escrita en palabras (español) — ej. 5432 -> "cinco mil cuatrocientos treinta y dos". Usa tablas (vectores) para las irregularidades reales del idioma (11-15, 16-29 fusionados, 'cien' vs 'ciento', decenas y centenas con nombre propio) en vez de intentar una fórmula puramente aritmética.
     *
     * @param args no utilizado — freeDFDWeb no pasa argumentos de línea de comandos.
     */
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ArrayList<Double> pal = new ArrayList<>();
        ArrayList<Double> dec = new ArrayList<>();
        ArrayList<Double> cen = new ArrayList<>();
        double N = 0.0;
        String resultado = null;
        double miles = 0.0;
        double resto = 0.0;
        double[] palabraResto = {0.0}; // Arreglo de 1 elemento: simula paso por referencia (Java no permite mutar un double pasado a otro método) — se usa como "palabraResto[0]" en el resto del método

        vecSet(pal, (int)(1), "cero");
        vecSet(pal, (int)(2), "uno");
        vecSet(pal, (int)(3), "dos");
        vecSet(pal, (int)(4), "tres");
        vecSet(pal, (int)(5), "cuatro");
        vecSet(pal, (int)(6), "cinco");
        vecSet(pal, (int)(7), "seis");
        vecSet(pal, (int)(8), "siete");
        vecSet(pal, (int)(9), "ocho");
        vecSet(pal, (int)(10), "nueve");
        vecSet(pal, (int)(11), "diez");
        vecSet(pal, (int)(12), "once");
        vecSet(pal, (int)(13), "doce");
        vecSet(pal, (int)(14), "trece");
        vecSet(pal, (int)(15), "catorce");
        vecSet(pal, (int)(16), "quince");
        vecSet(pal, (int)(17), "dieciséis");
        vecSet(pal, (int)(18), "diecisiete");
        vecSet(pal, (int)(19), "dieciocho");
        vecSet(pal, (int)(20), "diecinueve");
        vecSet(pal, (int)(21), "veinte");
        vecSet(pal, (int)(22), "veintiuno");
        vecSet(pal, (int)(23), "veintidós");
        vecSet(pal, (int)(24), "veintitrés");
        vecSet(pal, (int)(25), "veinticuatro");
        vecSet(pal, (int)(26), "veinticinco");
        vecSet(pal, (int)(27), "veintiséis");
        vecSet(pal, (int)(28), "veintisiete");
        vecSet(pal, (int)(29), "veintiocho");
        vecSet(pal, (int)(30), "veintinueve");
        vecSet(dec, (int)(3), "treinta");
        vecSet(dec, (int)(4), "cuarenta");
        vecSet(dec, (int)(5), "cincuenta");
        vecSet(dec, (int)(6), "sesenta");
        vecSet(dec, (int)(7), "setenta");
        vecSet(dec, (int)(8), "ochenta");
        vecSet(dec, (int)(9), "noventa");
        vecSet(cen, (int)(2), "doscientos");
        vecSet(cen, (int)(3), "trescientos");
        vecSet(cen, (int)(4), "cuatrocientos");
        vecSet(cen, (int)(5), "quinientos");
        vecSet(cen, (int)(6), "seiscientos");
        vecSet(cen, (int)(7), "setecientos");
        vecSet(cen, (int)(8), "ochocientos");
        vecSet(cen, (int)(9), "novecientos");
        N = readDouble(scanner);
        if (((N == 0))) {
            resultado = "cero";
        } else {
            miles = (double)(long)(div(N, 1000));
            resto = mod(N, 1000);
            resultado = "";
            if (((miles > 0))) {
                if (((miles == 1))) {
                    resultado = "mil";
                } else {
                    resultado = (fmt(vecGet(pal, (int)((miles + 1)))) + " mil");
                }
            }
            if (((resto > 0))) {
                ConvertirMenorQueMil(resto, pal, dec, cen, palabraResto);
                if (((miles > 0))) {
                    resultado = ((resultado + " ") + fmt(palabraResto[0]));
                } else {
                    resultado = palabraResto[0];
                }
            }
        }
        System.out.println("El número " + fmt(N) + " en palabras es: " + resultado);
    }

    /**
     * Convierte un número entre 0 y 999 a su palabra en español — maneja el caso irregular 'cien' (sin 'to') vs 'ciento' (con más dígitos detrás), y delega las decenas/unidades a ConvertirMenorQueCien.
     *
     * @param n double
     * @param pal double
     * @param dec double
     * @param cen ArrayList<Double>
     * @param palabra String[] — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
     */
    static void ConvertirMenorQueMil(double n, double pal, double dec, ArrayList<Double> cen, String[] palabra) {
        double c = 0.0;
        double r = 0.0;
        double[] palabraR = {0.0}; // Arreglo de 1 elemento: simula paso por referencia (Java no permite mutar un double pasado a otro método) — se usa como "palabraR[0]" en el resto del método

        c = (double)(long)(div(n, 100));
        r = mod(n, 100);
        palabra[0] = "";
        if (((c > 0))) {
            if (((((c == 1)) && ((r == 0))))) {
                palabra[0] = "cien";
            } else {
                if (((c == 1))) {
                    palabra[0] = "ciento";
                } else {
                    palabra[0] = vecGet(cen, (int)(c));
                }
            }
        }
        if (((r > 0))) {
            ConvertirMenorQueCien(r, pal, dec, palabraR);
            if (((c > 0))) {
                palabra[0] = ((palabra[0] + " ") + fmt(palabraR[0]));
            } else {
                palabra[0] = palabraR[0];
            }
        }
    }

    /**
     * Convierte un número entre 0 y 99 a su palabra en español, usando las tablas 'pal' (0-29, con las irregularidades de 11-15 y 16-29 fusionadas) y 'dec' (múltiplos de diez desde 30).
     *
     * @param n double
     * @param pal ArrayList<Double>
     * @param dec ArrayList<Double>
     * @param palabra double[] — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
     */
    static void ConvertirMenorQueCien(double n, ArrayList<Double> pal, ArrayList<Double> dec, double[] palabra) {
        double d = 0.0;
        double u = 0.0;

        if (((n <= 29))) {
            palabra[0] = vecGet(pal, (int)((n + 1)));
        } else {
            d = (double)(long)(div(n, 10));
            u = mod(n, 10);
            palabra[0] = vecGet(dec, (int)(d));
            if (((u > 0))) {
                palabra[0] = ((fmt(palabra[0]) + " y ") + fmt(vecGet(pal, (int)((u + 1)))));
            }
        }
    }

    static boolean readBoolean(Scanner scanner) {
        String t = scanner.nextLine().trim().toLowerCase();
        if (t.equals("verdadero") || t.equals("true") || t.equals("1")) return true;
        if (t.equals("falso") || t.equals("false") || t.equals("0")) return false;
        throw new RuntimeException("Se esperaba Verdadero o Falso, se recibio: " + t);
    }

    static double readDouble(Scanner scanner) {
        return Double.parseDouble(scanner.nextLine().trim());
    }

    static ArrayList<Double> readVector(Scanner scanner) {
        ArrayList<Double> result = new ArrayList<>();
        for (String token : scanner.nextLine().split(",")) {
            String t = token.trim();
            if (!t.isEmpty()) result.add(Double.parseDouble(t));
        }
        return result;
    }

    /** Formatea un double sin ".0" final cuando es entero — replica cómo freeDFDWeb (JS) imprime números, ej. factorial(5) debe mostrar "120", no "120.0". */
    static String fmt(double d) {
        if (!Double.isInfinite(d) && !Double.isNaN(d) && d == Math.floor(d) && Math.abs(d) < 1e15) {
            return String.valueOf((long) d);
        }
        return String.valueOf(d);
    }

    /** `String.valueOf(true)` da "true" — el pseudolenguaje escribe e imprime "Verdadero"/"Falso". */
    static String boolStr(boolean b) {
        return b ? "Verdadero" : "Falso";
    }

    /** `ToChar(n)` de Flowgorithm: código de carácter → carácter. `Character.toChars` cubre también los puntos fuera del BMP (emoji), que no caben en un `char`. */
    static String toChar(double code) {
        int c = (int) code;
        if (c < 0 || c > 0x10FFFF) {
            throw new RuntimeException("ToChar(" + c + "): codigo de caracter fuera de rango");
        }
        return new String(Character.toChars(c));
    }

    /** `ToFixed(r, i)` de Flowgorithm: TRUNCA a `i` decimales, no redondea. `Locale.ROOT` para que el separador sea el punto siempre, igual que el resto del generador. */
    static String toFixed(double value, double digits) {
        int d = (int) digits;
        double factor = Math.pow(10, d);
        double truncated = (double)(long)(value * factor) / factor;
        return String.format(java.util.Locale.ROOT, "%." + d + "f", truncated);
    }

    static double vecGet(ArrayList<Double> v, int index) {
        int i = index - 1;
        if (i < 0 || i >= v.size() || v.get(i) == null) {
            throw new RuntimeException("La posición " + index + " del vector no tiene un valor asignado todavía");
        }
        return v.get(i);
    }

    static void vecSet(ArrayList<Double> v, int index, double value) {
        int i = index - 1;
        while (v.size() <= i) v.add(null);
        v.set(i, value);
    }

    static double div(double a, double b) {
        if (b == 0) throw new ArithmeticException("División por cero");
        return a / b;
    }

    static double mod(double a, double b) {
        if (b == 0) throw new ArithmeticException("Módulo por cero");
        return a % b;
    }

    static void sleep(double seconds) {
        try { Thread.sleep((long) (seconds * 1000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

The same algorithm in Python

#!/usr/bin/env python3
"""
Programa generado por freeDFDWeb — pseudocódigo exportado a Python.
Para correr: python3 main.py (ver README.md)
"""
import math
import random
import sys
import time
import datetime


class DfdRuntimeError(Exception):
    pass


def read_boolean():
    t = input().strip().lower()
    if t in ('verdadero', 'true', '1'):
        return True
    if t in ('falso', 'false', '0'):
        return False
    raise DfdRuntimeError('Se esperaba Verdadero o Falso, se recibio: ' + t)


def read_double():
    return float(input().strip())


def read_vector():
    return [float(t.strip()) for t in input().split(',') if t.strip() != '']


def fmt(d):
    """Formatea sin ".0" final cuando es entero — replica cómo freeDFDWeb (JS) imprime números."""
    if math.isinf(d):
        return 'Infinity' if d > 0 else '-Infinity'
    if math.isnan(d):
        return 'NaN'
    if d == math.floor(d) and abs(d) < 1e15:
        return str(int(d))
    return str(d)


def bool_str(b):
    """`str(True)` da "True" (mayúscula) — el pseudolenguaje escribe e imprime "Verdadero"/"Falso"."""
    return 'Verdadero' if b else 'Falso'


def sgn(x):
    """`Sgn(n)` de Flowgorithm. Devuelve x tal cual en el cero para conservar el signo del -0.0, igual que `Math.sign` de JS."""
    if math.isnan(x):
        return x
    if x > 0:
        return 1.0
    if x < 0:
        return -1.0
    return x


def to_fixed(value, digits):
    """`ToFixed(r, i)` de Flowgorithm: TRUNCA a i decimales, no redondea."""
    d = int(digits)
    factor = 10 ** d
    truncated = math.trunc(value * factor) / factor
    return f'{truncated:.{d}f}'


def vec_get(v, index):
    i = index - 1
    if i < 0 or i >= len(v) or v[i] is None:
        raise DfdRuntimeError(f'La posición {index} del vector no tiene un valor asignado todavía')
    return v[i]


def vec_set(v, index, value):
    i = index - 1
    while len(v) <= i:
        v.append(None)
    v[i] = value


def div(a, b):
    if b == 0:
        raise DfdRuntimeError('División por cero')
    return a / b


def mod(a, b):
    if b == 0:
        raise DfdRuntimeError('Módulo por cero')
    # `math.fmod`, NUNCA el `%` nativo de Python — el signo del resultado de
    # `%` en Python sigue al DIVISOR (-7 % 3 == 2), mientras que JS (y el
    # intérprete real) usan resto con signo del DIVIDENDO (-7 % 3 == -1),
    # que es exactamente lo que `math.fmod` reproduce.
    return math.fmod(a, b)


def redon(x):
    """Redondeo tipo `Math.round` de JS (.5 siempre hacia +Infinity) — NUNCA `round()` nativo de Python (redondeo bancario: round(2.5) == 2)."""
    return math.floor(x + 0.5)


def ConvertirMenorQueMil(n, pal, dec, cen, palabra):
    """
    Convierte un número entre 0 y 999 a su palabra en español — maneja el caso irregular 'cien' (sin 'to') vs 'ciento' (con más dígitos detrás), y delega las decenas/unidades a ConvertirMenorQueCien.

    Args:
        n: float
        pal: float
        dec: float
        cen: list[float]
        palabra: str — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
    """
    palabraR = [0.0]  # Lista de 1 elemento: simula paso por referencia (Python no permite mutar un float pasado a otra función) — se usa como "palabraR[0]" en el resto de la función

    c = float(math.trunc(div(n, 100)))
    r = mod(n, 100)
    palabra[0] = ""
    if (c > 0):
        if ((c == 1) and (r == 0)):
            palabra[0] = "cien"
        else:
            if (c == 1):
                palabra[0] = "ciento"
            else:
                palabra[0] = vec_get(cen, int(c))
    if (r > 0):
        ConvertirMenorQueCien(r, pal, dec, palabraR)
        if (c > 0):
            palabra[0] = ((palabra[0] + " ") + fmt(palabraR[0]))
        else:
            palabra[0] = palabraR[0]

def ConvertirMenorQueCien(n, pal, dec, palabra):
    """
    Convierte un número entre 0 y 99 a su palabra en español, usando las tablas 'pal' (0-29, con las irregularidades de 11-15 y 16-29 fusionadas) y 'dec' (múltiplos de diez desde 30).

    Args:
        n: float
        pal: list[float]
        dec: list[float]
        palabra: float — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
    """
    if (n <= 29):
        palabra[0] = vec_get(pal, int((n + 1)))
    else:
        d = float(math.trunc(div(n, 10)))
        u = mod(n, 10)
        palabra[0] = vec_get(dec, int(d))
        if (u > 0):
            palabra[0] = ((fmt(palabra[0]) + " y ") + fmt(vec_get(pal, int((u + 1)))))

def main():
    """Convierte un número entero entre 0 y 9999 a su forma escrita en palabras (español) — ej. 5432 -> "cinco mil cuatrocientos treinta y dos". Usa tablas (vectores) para las irregularidades reales del idioma (11-15, 16-29 fusionados, 'cien' vs 'ciento', decenas y centenas con nombre propio) en vez de intentar una fórmula puramente aritmética."""
    pal = []
    dec = []
    cen = []
    palabraResto = [0.0]  # Lista de 1 elemento: simula paso por referencia (Python no permite mutar un float pasado a otra función) — se usa como "palabraResto[0]" en el resto de la función

    vec_set(pal, int(1), "cero")
    vec_set(pal, int(2), "uno")
    vec_set(pal, int(3), "dos")
    vec_set(pal, int(4), "tres")
    vec_set(pal, int(5), "cuatro")
    vec_set(pal, int(6), "cinco")
    vec_set(pal, int(7), "seis")
    vec_set(pal, int(8), "siete")
    vec_set(pal, int(9), "ocho")
    vec_set(pal, int(10), "nueve")
    vec_set(pal, int(11), "diez")
    vec_set(pal, int(12), "once")
    vec_set(pal, int(13), "doce")
    vec_set(pal, int(14), "trece")
    vec_set(pal, int(15), "catorce")
    vec_set(pal, int(16), "quince")
    vec_set(pal, int(17), "dieciséis")
    vec_set(pal, int(18), "diecisiete")
    vec_set(pal, int(19), "dieciocho")
    vec_set(pal, int(20), "diecinueve")
    vec_set(pal, int(21), "veinte")
    vec_set(pal, int(22), "veintiuno")
    vec_set(pal, int(23), "veintidós")
    vec_set(pal, int(24), "veintitrés")
    vec_set(pal, int(25), "veinticuatro")
    vec_set(pal, int(26), "veinticinco")
    vec_set(pal, int(27), "veintiséis")
    vec_set(pal, int(28), "veintisiete")
    vec_set(pal, int(29), "veintiocho")
    vec_set(pal, int(30), "veintinueve")
    vec_set(dec, int(3), "treinta")
    vec_set(dec, int(4), "cuarenta")
    vec_set(dec, int(5), "cincuenta")
    vec_set(dec, int(6), "sesenta")
    vec_set(dec, int(7), "setenta")
    vec_set(dec, int(8), "ochenta")
    vec_set(dec, int(9), "noventa")
    vec_set(cen, int(2), "doscientos")
    vec_set(cen, int(3), "trescientos")
    vec_set(cen, int(4), "cuatrocientos")
    vec_set(cen, int(5), "quinientos")
    vec_set(cen, int(6), "seiscientos")
    vec_set(cen, int(7), "setecientos")
    vec_set(cen, int(8), "ochocientos")
    vec_set(cen, int(9), "novecientos")
    N = read_double()
    if (N == 0):
        resultado = "cero"
    else:
        miles = float(math.trunc(div(N, 1000)))
        resto = mod(N, 1000)
        resultado = ""
        if (miles > 0):
            if (miles == 1):
                resultado = "mil"
            else:
                resultado = (fmt(vec_get(pal, int((miles + 1)))) + " mil")
        if (resto > 0):
            ConvertirMenorQueMil(resto, pal, dec, cen, palabraResto)
            if (miles > 0):
                resultado = ((resultado + " ") + fmt(palabraResto[0]))
            else:
                resultado = palabraResto[0]
    print("El número " + fmt(N) + " en palabras es: " + resultado)


if __name__ == '__main__':
    main()

The same algorithm in C#

using System;
using System.Collections.Generic;
using System.Globalization;

/// <summary>Excepción propia para errores de ejecución del programa generado (división por cero, índice de vector sin asignar, etc.) — mismo criterio que el `RuntimeError` real de freeDFDWeb.</summary>
class DfdRuntimeException : Exception {
    public DfdRuntimeException(string message) : base(message) { }
}

/// <summary>
/// Proyecto C# generado por freeDFDWeb a partir de un diagrama DFD.
/// </summary>
class Program {
    /// <summary>
    /// Convierte un número entero entre 0 y 9999 a su forma escrita en palabras (español) — ej. 5432 -> "cinco mil cuatrocientos treinta y dos". Usa tablas (vectores) para las irregularidades reales del idioma (11-15, 16-29 fusionados, 'cien' vs 'ciento', decenas y centenas con nombre propio) en vez de intentar una fórmula puramente aritmética.
    /// </summary>
    /// <param name="args">no utilizado — freeDFDWeb no pasa argumentos de línea de comandos.</param>
    static void Main(string[] args) {
        CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
        List<double?> pal = new List<double?>();
        List<double?> dec = new List<double?>();
        List<double?> cen = new List<double?>();
        double N = 0.0;
        string resultado = null;
        double miles = 0.0;
        double resto = 0.0;
        double palabraResto = 0.0;

        VecSet(pal, (int)(1), "cero");
        VecSet(pal, (int)(2), "uno");
        VecSet(pal, (int)(3), "dos");
        VecSet(pal, (int)(4), "tres");
        VecSet(pal, (int)(5), "cuatro");
        VecSet(pal, (int)(6), "cinco");
        VecSet(pal, (int)(7), "seis");
        VecSet(pal, (int)(8), "siete");
        VecSet(pal, (int)(9), "ocho");
        VecSet(pal, (int)(10), "nueve");
        VecSet(pal, (int)(11), "diez");
        VecSet(pal, (int)(12), "once");
        VecSet(pal, (int)(13), "doce");
        VecSet(pal, (int)(14), "trece");
        VecSet(pal, (int)(15), "catorce");
        VecSet(pal, (int)(16), "quince");
        VecSet(pal, (int)(17), "dieciséis");
        VecSet(pal, (int)(18), "diecisiete");
        VecSet(pal, (int)(19), "dieciocho");
        VecSet(pal, (int)(20), "diecinueve");
        VecSet(pal, (int)(21), "veinte");
        VecSet(pal, (int)(22), "veintiuno");
        VecSet(pal, (int)(23), "veintidós");
        VecSet(pal, (int)(24), "veintitrés");
        VecSet(pal, (int)(25), "veinticuatro");
        VecSet(pal, (int)(26), "veinticinco");
        VecSet(pal, (int)(27), "veintiséis");
        VecSet(pal, (int)(28), "veintisiete");
        VecSet(pal, (int)(29), "veintiocho");
        VecSet(pal, (int)(30), "veintinueve");
        VecSet(dec, (int)(3), "treinta");
        VecSet(dec, (int)(4), "cuarenta");
        VecSet(dec, (int)(5), "cincuenta");
        VecSet(dec, (int)(6), "sesenta");
        VecSet(dec, (int)(7), "setenta");
        VecSet(dec, (int)(8), "ochenta");
        VecSet(dec, (int)(9), "noventa");
        VecSet(cen, (int)(2), "doscientos");
        VecSet(cen, (int)(3), "trescientos");
        VecSet(cen, (int)(4), "cuatrocientos");
        VecSet(cen, (int)(5), "quinientos");
        VecSet(cen, (int)(6), "seiscientos");
        VecSet(cen, (int)(7), "setecientos");
        VecSet(cen, (int)(8), "ochocientos");
        VecSet(cen, (int)(9), "novecientos");
        N = ReadDouble();
        if ((N == 0)) {
            resultado = "cero";
        } else {
            miles = Math.Truncate(Div(N, 1000));
            resto = Mod(N, 1000);
            resultado = "";
            if ((miles > 0)) {
                if ((miles == 1)) {
                    resultado = "mil";
                } else {
                    resultado = (Fmt(VecGet(pal, (int)((miles + 1)))) + " mil");
                }
            }
            if ((resto > 0)) {
                ConvertirMenorQueMil(resto, pal, dec, cen, ref palabraResto);
                if ((miles > 0)) {
                    resultado = ((resultado + " ") + Fmt(palabraResto));
                } else {
                    resultado = palabraResto;
                }
            }
        }
        Console.WriteLine("El número " + Fmt(N) + " en palabras es: " + resultado);
    }

    /// <summary>
    /// Convierte un número entre 0 y 999 a su palabra en español — maneja el caso irregular 'cien' (sin 'to') vs 'ciento' (con más dígitos detrás), y delega las decenas/unidades a ConvertirMenorQueCien.
    /// </summary>
    /// <param name="n">double</param>
    /// <param name="pal">double</param>
    /// <param name="dec">double</param>
    /// <param name="cen">List<double?></param>
    /// <param name="palabra">string — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)</param>
    static void ConvertirMenorQueMil(double n, double pal, double dec, List<double?> cen, ref string palabra) {
        double c = 0.0;
        double r = 0.0;
        double palabraR = 0.0;

        c = Math.Truncate(Div(n, 100));
        r = Mod(n, 100);
        palabra = "";
        if ((c > 0)) {
            if (((c == 1) && (r == 0))) {
                palabra = "cien";
            } else {
                if ((c == 1)) {
                    palabra = "ciento";
                } else {
                    palabra = VecGet(cen, (int)(c));
                }
            }
        }
        if ((r > 0)) {
            ConvertirMenorQueCien(r, pal, dec, ref palabraR);
            if ((c > 0)) {
                palabra = ((palabra + " ") + Fmt(palabraR));
            } else {
                palabra = palabraR;
            }
        }
    }

    /// <summary>
    /// Convierte un número entre 0 y 99 a su palabra en español, usando las tablas 'pal' (0-29, con las irregularidades de 11-15 y 16-29 fusionadas) y 'dec' (múltiplos de diez desde 30).
    /// </summary>
    /// <param name="n">double</param>
    /// <param name="pal">List<double?></param>
    /// <param name="dec">List<double?></param>
    /// <param name="palabra">double — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)</param>
    static void ConvertirMenorQueCien(double n, List<double?> pal, List<double?> dec, ref double palabra) {
        double d = 0.0;
        double u = 0.0;

        if ((n <= 29)) {
            palabra = VecGet(pal, (int)((n + 1)));
        } else {
            d = Math.Truncate(Div(n, 10));
            u = Mod(n, 10);
            palabra = VecGet(dec, (int)(d));
            if ((u > 0)) {
                palabra = ((Fmt(palabra) + " y ") + Fmt(VecGet(pal, (int)((u + 1)))));
            }
        }
    }

    static readonly Random Rnd = new Random();

    static bool ReadBoolean() {
        string t = (Console.ReadLine() ?? "").Trim().ToLowerInvariant();
        if (t == "verdadero" || t == "true" || t == "1") return true;
        if (t == "falso" || t == "false" || t == "0") return false;
        throw new Exception("Se esperaba Verdadero o Falso, se recibio: " + t);
    }

    static double ReadDouble() {
        return double.Parse(Console.ReadLine().Trim(), CultureInfo.InvariantCulture);
    }

    static List<double?> ReadVector() {
        List<double?> result = new List<double?>();
        foreach (string token in Console.ReadLine().Split(',')) {
            string t = token.Trim();
            if (t.Length > 0) result.Add(double.Parse(t, CultureInfo.InvariantCulture));
        }
        return result;
    }

    /// <summary>Formatea un double sin ".0" final cuando es entero — replica cómo freeDFDWeb (JS) imprime números.</summary>
    static string Fmt(double d) {
        if (double.IsPositiveInfinity(d)) return "Infinity";
        if (double.IsNegativeInfinity(d)) return "-Infinity";
        if (double.IsNaN(d)) return "NaN";
        if (d == Math.Floor(d) && Math.Abs(d) < 1e15) {
            return ((long)d).ToString(CultureInfo.InvariantCulture);
        }
        return d.ToString(CultureInfo.InvariantCulture);
    }

    /// <summary>`true.ToString()` da "True" (mayúscula) — el pseudolenguaje escribe e imprime "Verdadero"/"Falso".</summary>
    static string BoolStr(bool b) {
        return b ? "Verdadero" : "Falso";
    }

    /// <summary>`Sgn(n)` de Flowgorithm. Helper y no `Math.Sign`: ese LANZA sobre NaN, mientras el intérprete devuelve NaN. Devuelve x en el cero para conservar el -0.0.</summary>
    static double Sgn(double x) {
        if (double.IsNaN(x)) return x;
        if (x > 0) return 1.0;
        if (x < 0) return -1.0;
        return x;
    }

    /// <summary>`ToFixed(r, i)` de Flowgorithm: TRUNCA a i decimales, no redondea. `InvariantCulture` para que el separador sea el punto, igual que `Fmt`.</summary>
    static string ToFixed(double value, double digits) {
        int d = (int) digits;
        double factor = Math.Pow(10, d);
        double truncated = Math.Truncate(value * factor) / factor;
        return truncated.ToString("F" + d, CultureInfo.InvariantCulture);
    }

    static double VecGet(List<double?> v, int index) {
        int i = index - 1;
        if (i < 0 || i >= v.Count || !v[i].HasValue) {
            throw new DfdRuntimeException("La posición " + index + " del vector no tiene un valor asignado todavía");
        }
        return v[i].Value;
    }

    static void VecSet(List<double?> v, int index, double value) {
        int i = index - 1;
        while (v.Count <= i) v.Add(null);
        v[i] = value;
    }

    static double Div(double a, double b) {
        if (b == 0) throw new DfdRuntimeException("División por cero");
        return a / b;
    }

    static double Mod(double a, double b) {
        if (b == 0) throw new DfdRuntimeException("Módulo por cero");
        // El operador `%` nativo de C# para `double` SÍ coincide con el
        // signo de JS (confirmado real: -7 % 3 == -1 en ambos) — a
        // diferencia de Python, no hace falta ningún wrapper de signo.
        return a % b;
    }

    /// <summary>Redondeo tipo `Math.round` de JS (.5 siempre hacia +Infinity) — NUNCA `Math.Round()` directo (bancario en .NET: Math.Round(2.5) == 2).</summary>
    static double Redon(double x) {
        return Math.Floor(x + 0.5);
    }
}

The same algorithm in C++

#include <iostream>
#include <string>
#include <vector>
#include <optional>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <chrono>
#include <thread>
#include <charconv>
#include <stdexcept>

/**
 * Programa C++ generado por freeDFDWeb a partir de un diagrama DFD.
 */

const double PI = 3.14159265358979323846;

class DfdRuntimeError : public std::runtime_error {
public:
    explicit DfdRuntimeError(const std::string& message) : std::runtime_error(message) {}
};

std::string readLine() {
    std::string line;
    if (!std::getline(std::cin, line)) return "";
    if (!line.empty() && line.back() == '\r') line.pop_back();
    return line;
}

/** `std::stod` (usado por `toNumberOrThrow`) NO tira con texto no numérico de forma consistente en todos los casos (ignora sufijos raros) pero SÍ tira `std::invalid_argument` con texto totalmente no numérico — verificado real. Envuelto para relanzar como `DfdRuntimeError`, mismo tipo de excepción que el resto del programa generado. */
double toNumberOrThrow(const std::string& value) {
    try {
        return std::stod(value);
    } catch (const std::exception&) {
        throw DfdRuntimeError("Se esperaba un número, se encontró \"" + value + "\"");
    }
}

bool readBoolean() {
    std::string t = readLine();
    for (auto& c : t) c = (char)std::tolower((unsigned char)c);
    if (t == "verdadero" || t == "true" || t == "1") return true;
    if (t == "falso" || t == "false" || t == "0") return false;
    throw std::runtime_error("Se esperaba Verdadero o Falso, se recibio: " + t);
}

double readDouble() {
    return toNumberOrThrow(readLine());
}

std::vector<std::optional<double>> readVector() {
    std::vector<std::optional<double>> result;
    std::stringstream ss(readLine());
    std::string token;
    while (std::getline(ss, token, ',')) {
        size_t start = token.find_first_not_of(" \t");
        size_t end = token.find_last_not_of(" \t");
        if (start == std::string::npos) continue;
        result.push_back(toNumberOrThrow(token.substr(start, end - start + 1)));
    }
    return result;
}

/** Formatea sin ".0" final cuando es entero, con representación de ida y vuelta exacta para decimales (`std::to_chars`, C++17) — replica cómo freeDFDWeb (JS) imprime números. Diverge de JS solo en notación científica de magnitudes extremas (ver comentario de cabecera del archivo), gap aceptado. */
std::string fmt(double d) {
    if (std::isinf(d)) return d > 0 ? "Infinity" : "-Infinity";
    if (std::isnan(d)) return "NaN";
    char buf[64];
    if (d == std::floor(d) && std::abs(d) < 1e15) {
        auto res = std::to_chars(buf, buf + sizeof(buf), (long long)d);
        return std::string(buf, res.ptr);
    }
    auto res = std::to_chars(buf, buf + sizeof(buf), d);
    return std::string(buf, res.ptr);
}

/** `std::cout << bool` imprime "1"/"0" — el pseudolenguaje escribe e imprime "Verdadero"/"Falso". */
std::string boolStr(bool b) {
    return b ? "Verdadero" : "Falso";
}

/** `Sgn(n)` de Flowgorithm. Devuelve x en el cero para conservar el signo del -0.0, igual que `Math.sign` de JS. */
double sgn(double x) {
    if (std::isnan(x)) return x;
    if (x > 0) return 1.0;
    if (x < 0) return -1.0;
    return x;
}

/** `ToChar(n)`: punto de código → carácter. C++ es el único destino sin conversión nativa, así que codifica UTF-8 a mano — `std::string` acá son bytes, no puntos de código. */
std::string toCharStr(double code) {
    long long c = (long long) code;
    if (c < 0 || c > 0x10FFFF) {
        throw DfdRuntimeError("ToChar(" + std::to_string(c) + "): codigo de caracter fuera de rango");
    }
    std::string out;
    if (c < 0x80) {
        out += (char)c;
    } else if (c < 0x800) {
        out += (char)(0xC0 | (c >> 6));
        out += (char)(0x80 | (c & 0x3F));
    } else if (c < 0x10000) {
        out += (char)(0xE0 | (c >> 12));
        out += (char)(0x80 | ((c >> 6) & 0x3F));
        out += (char)(0x80 | (c & 0x3F));
    } else {
        out += (char)(0xF0 | (c >> 18));
        out += (char)(0x80 | ((c >> 12) & 0x3F));
        out += (char)(0x80 | ((c >> 6) & 0x3F));
        out += (char)(0x80 | (c & 0x3F));
    }
    return out;
}

/** `ToCode(c)`: inversa de `toCharStr` — decodifica el PRIMER punto de código UTF-8, no el primer byte (si no, una "ñ" daría 195). */
double toCode(const std::string& s) {
    if (s.empty()) {
        throw DfdRuntimeError("ToCode espera un texto de un caracter");
    }
    unsigned char b0 = (unsigned char)s[0];
    if (b0 < 0x80) return (double)b0;
    int extra = (b0 >= 0xF0) ? 3 : (b0 >= 0xE0) ? 2 : 1;
    long long cp = b0 & (0x3F >> extra);
    for (int i = 1; i <= extra; i++) {
        if ((size_t)i >= s.size()) {
            throw DfdRuntimeError("ToCode: texto UTF-8 mal formado");
        }
        cp = (cp << 6) | ((unsigned char)s[i] & 0x3F);
    }
    return (double)cp;
}

/** `ToFixed(r, i)` de Flowgorithm: TRUNCA a i decimales, no redondea. */
std::string toFixed(double value, double digits) {
    int d = (int) digits;
    double factor = std::pow(10.0, d);
    double truncated = std::trunc(value * factor) / factor;
    std::ostringstream out;
    out << std::fixed << std::setprecision(d) << truncated;
    return out.str();
}

std::string toUpperStr(const std::string& s) {
    std::string result = s;
    std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::toupper(c); });
    return result;
}

std::string toLowerStr(const std::string& s) {
    std::string result = s;
    std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); });
    return result;
}

double vecGet(std::vector<std::optional<double>>& v, int index) {
    int i = index - 1;
    if (i < 0 || (size_t)i >= v.size() || !v[i].has_value()) {
        throw DfdRuntimeError("La posición " + std::to_string(index) + " del vector no tiene un valor asignado todavía");
    }
    return *v[i];
}

void vecSet(std::vector<std::optional<double>>& v, int index, double value) {
    int i = index - 1;
    if ((size_t)(i + 1) > v.size()) v.resize(i + 1);
    v[i] = value;
}

/**
 * `dividir` y no `div`, que sería el nombre obvio: `div` YA EXISTE en la
 * biblioteca estándar de C (`<cstdlib>`, devuelve un `div_t`) y está
 * declarada aunque no se incluya ese encabezado. Con dos literales
 * enteros, `div(7, 2)` resuelve a la versión de C —coincidencia exacta—
 * y el programa generado NO COMPILA:
 *
 *     error: cannot convert 'div_t' to 'double'
 *
 * O sea: CUALQUIER programa con una división fallaba al exportar a C++.
 * Encontrado el 30-oct-2026 compilando de verdad lo exportado
 * (`scripts/equivalencia/comparar.mjs`); ninguna prueba lo veía porque
 * ninguna compilaba C++.
 */
double dividir(double a, double b) {
    if (b == 0) throw DfdRuntimeError("División por cero");
    return a / b;
}

double mod(double a, double b) {
    if (b == 0) throw DfdRuntimeError("Módulo por cero");
    // `%` NO COMPILA sobre `double` en C++ — `std::fmod` es la única forma.
    // Su signo SÍ coincide con JS (confirmado real), a diferencia de
    // Python, que necesitaba un wrapper de signo además del de cero.
    return std::fmod(a, b);
}

/** Redondeo tipo `Math.round` de JS (.5 siempre hacia +Infinity) — NUNCA `std::round()` directo (redondea siempre alejándose de cero: round(-2.5) == -3, distinto de Math.round(-2.5) == -2). */
double redon(double x) {
    return std::floor(x + 0.5);
}

// ---------------------------------------------------------------------------
// Programa
// ---------------------------------------------------------------------------

/**
 * Convierte un número entre 0 y 999 a su palabra en español — maneja el caso irregular 'cien' (sin 'to') vs 'ciento' (con más dígitos detrás), y delega las decenas/unidades a ConvertirMenorQueCien.
 * @param n double
 * @param pal double
 * @param dec double
 * @param cen std::vector<std::optional<double>>
 * @param palabra std::string — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
 */
void ConvertirMenorQueMil(double n, double pal, double dec, std::vector<std::optional<double>> cen, std::string& palabra) {
    double c = 0.0;
    double r = 0.0;
    double palabraR = 0.0;

    c = std::trunc(dividir(n, 100));
    r = mod(n, 100);
    palabra = std::string("");
    if ((c > 0)) {
        if (((c == 1) && (r == 0))) {
            palabra = std::string("cien");
        } else {
            if ((c == 1)) {
                palabra = std::string("ciento");
            } else {
                palabra = vecGet(cen, (int)(c));
            }
        }
    }
    if ((r > 0)) {
        ConvertirMenorQueCien(r, pal, dec, palabraR);
        if ((c > 0)) {
            palabra = ((palabra + std::string(" ")) + fmt(palabraR));
        } else {
            palabra = palabraR;
        }
    }
}

/**
 * Convierte un número entre 0 y 99 a su palabra en español, usando las tablas 'pal' (0-29, con las irregularidades de 11-15 y 16-29 fusionadas) y 'dec' (múltiplos de diez desde 30).
 * @param n double
 * @param pal std::vector<std::optional<double>>
 * @param dec std::vector<std::optional<double>>
 * @param palabra double — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
 */
void ConvertirMenorQueCien(double n, std::vector<std::optional<double>> pal, std::vector<std::optional<double>> dec, double& palabra) {
    double d = 0.0;
    double u = 0.0;

    if ((n <= 29)) {
        palabra = vecGet(pal, (int)((n + 1)));
    } else {
        d = std::trunc(dividir(n, 10));
        u = mod(n, 10);
        palabra = vecGet(dec, (int)(d));
        if ((u > 0)) {
            palabra = ((fmt(palabra) + std::string(" y ")) + fmt(vecGet(pal, (int)((u + 1)))));
        }
    }
}

/**
 * Convierte un número entero entre 0 y 9999 a su forma escrita en palabras (español) — ej. 5432 -> "cinco mil cuatrocientos treinta y dos". Usa tablas (vectores) para las irregularidades reales del idioma (11-15, 16-29 fusionados, 'cien' vs 'ciento', decenas y centenas con nombre propio) en vez de intentar una fórmula puramente aritmética.
 */
int main() {
    std::vector<std::optional<double>> pal;
    std::vector<std::optional<double>> dec;
    std::vector<std::optional<double>> cen;
    double N = 0.0;
    std::string resultado = "";
    double miles = 0.0;
    double resto = 0.0;
    double palabraResto = 0.0;

    vecSet(pal, (int)(1), std::string("cero"));
    vecSet(pal, (int)(2), std::string("uno"));
    vecSet(pal, (int)(3), std::string("dos"));
    vecSet(pal, (int)(4), std::string("tres"));
    vecSet(pal, (int)(5), std::string("cuatro"));
    vecSet(pal, (int)(6), std::string("cinco"));
    vecSet(pal, (int)(7), std::string("seis"));
    vecSet(pal, (int)(8), std::string("siete"));
    vecSet(pal, (int)(9), std::string("ocho"));
    vecSet(pal, (int)(10), std::string("nueve"));
    vecSet(pal, (int)(11), std::string("diez"));
    vecSet(pal, (int)(12), std::string("once"));
    vecSet(pal, (int)(13), std::string("doce"));
    vecSet(pal, (int)(14), std::string("trece"));
    vecSet(pal, (int)(15), std::string("catorce"));
    vecSet(pal, (int)(16), std::string("quince"));
    vecSet(pal, (int)(17), std::string("dieciséis"));
    vecSet(pal, (int)(18), std::string("diecisiete"));
    vecSet(pal, (int)(19), std::string("dieciocho"));
    vecSet(pal, (int)(20), std::string("diecinueve"));
    vecSet(pal, (int)(21), std::string("veinte"));
    vecSet(pal, (int)(22), std::string("veintiuno"));
    vecSet(pal, (int)(23), std::string("veintidós"));
    vecSet(pal, (int)(24), std::string("veintitrés"));
    vecSet(pal, (int)(25), std::string("veinticuatro"));
    vecSet(pal, (int)(26), std::string("veinticinco"));
    vecSet(pal, (int)(27), std::string("veintiséis"));
    vecSet(pal, (int)(28), std::string("veintisiete"));
    vecSet(pal, (int)(29), std::string("veintiocho"));
    vecSet(pal, (int)(30), std::string("veintinueve"));
    vecSet(dec, (int)(3), std::string("treinta"));
    vecSet(dec, (int)(4), std::string("cuarenta"));
    vecSet(dec, (int)(5), std::string("cincuenta"));
    vecSet(dec, (int)(6), std::string("sesenta"));
    vecSet(dec, (int)(7), std::string("setenta"));
    vecSet(dec, (int)(8), std::string("ochenta"));
    vecSet(dec, (int)(9), std::string("noventa"));
    vecSet(cen, (int)(2), std::string("doscientos"));
    vecSet(cen, (int)(3), std::string("trescientos"));
    vecSet(cen, (int)(4), std::string("cuatrocientos"));
    vecSet(cen, (int)(5), std::string("quinientos"));
    vecSet(cen, (int)(6), std::string("seiscientos"));
    vecSet(cen, (int)(7), std::string("setecientos"));
    vecSet(cen, (int)(8), std::string("ochocientos"));
    vecSet(cen, (int)(9), std::string("novecientos"));
    N = readDouble();
    if ((N == 0)) {
        resultado = std::string("cero");
    } else {
        miles = std::trunc(dividir(N, 1000));
        resto = mod(N, 1000);
        resultado = std::string("");
        if ((miles > 0)) {
            if ((miles == 1)) {
                resultado = std::string("mil");
            } else {
                resultado = (fmt(vecGet(pal, (int)((miles + 1)))) + std::string(" mil"));
            }
        }
        if ((resto > 0)) {
            ConvertirMenorQueMil(resto, pal, dec, cen, palabraResto);
            if ((miles > 0)) {
                resultado = ((resultado + std::string(" ")) + fmt(palabraResto));
            } else {
                resultado = palabraResto;
            }
        }
    }
    std::cout << std::string("El número ") + fmt(N) + std::string(" en palabras es: ") + resultado << "\n";
    return 0;
}

The same algorithm in TypeScript

import * as fs from 'fs';

/**
 * Generado por freeDFDWeb, sin descripción en el diagrama original.
 */

class DfdRuntimeError extends Error {}

const inputLines: string[] = fs.readFileSync(0, 'utf-8').split('\n');
let inputIndex = 0;

/** Toda la entrada se lee de una sola vez al arrancar (ver cabecera del archivo) — cada llamada consume la siguiente línea ya bufferizada. */
function readLine(): string {
    return (inputLines[inputIndex++] ?? '').replace(/\r$/, '');
}

function readBoolean(): boolean {
    const t = readLine().trim().toLowerCase();
    if (t === 'verdadero' || t === 'true' || t === '1') return true;
    if (t === 'falso' || t === 'false' || t === '0') return false;
    throw new Error('Se esperaba Verdadero o Falso, se recibio: ' + t);
}

function readDouble(): number {
    return toNumberOrThrow(readLine().trim());
}

function readVector(): (number | undefined)[] {
    return readLine()
        .split(',')
        .map((t) => t.trim())
        .filter((t) => t.length > 0)
        .map((t) => toNumberOrThrow(t));
}

/** `Number(x)` nativo no tira error con texto no numérico (da `NaN`) — a diferencia del intérprete real (`toNumber`), que sí tira. Único punto donde este generador necesita reproducir ese comportamiento a mano. */
function toNumberOrThrow(value: string | number): number {
    const n = Number(value);
    if (Number.isNaN(n)) {
        throw new DfdRuntimeError(`Se esperaba un número, se encontró "${value}"`);
    }
    return n;
}

/** `String(true)` da "true" — el pseudolenguaje escribe e imprime "Verdadero"/"Falso". */
function boolStr(b: boolean): string {
    return b ? 'Verdadero' : 'Falso';
}

/** `ToFixed(r, i)` de Flowgorithm: TRUNCA a i decimales, no redondea — por eso no basta el `.toFixed()` nativo, que redondea. */
function toFixed(value: number, digits: number): string {
    const d = Math.trunc(digits);
    const factor = Math.pow(10, d);
    return (Math.trunc(value * factor) / factor).toFixed(d);
}

function vecGet(v: (number | undefined)[], index: number): number {
    const i = Math.trunc(index) - 1;
    const value = v[i];
    if (i < 0 || value === undefined) {
        throw new DfdRuntimeError(`La posición ${index} del vector no tiene un valor asignado todavía`);
    }
    return value;
}

function vecSet(v: (number | undefined)[], index: number, value: number): void {
    v[Math.trunc(index) - 1] = value;
}

function div(a: number, b: number): number {
    if (b === 0) throw new DfdRuntimeError('División por cero');
    return a / b;
}

function mod(a: number, b: number): number {
    if (b === 0) throw new DfdRuntimeError('Módulo por cero');
    // El `%` nativo de JS/TS coincide exacto con el del intérprete real
    // (mismo motor) — a diferencia de Python, no hace falta ningún wrapper
    // de signo, solo el chequeo de división por cero.
    return a % b;
}


/**
 * Convierte un número entre 0 y 999 a su palabra en español — maneja el caso irregular 'cien' (sin 'to') vs 'ciento' (con más dígitos detrás), y delega las decenas/unidades a ConvertirMenorQueCien.
 * @param n number
 * @param pal number
 * @param dec number
 * @param cen (number | undefined)[]
 * @param palabra string[] — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
 */
function ConvertirMenorQueMil(n: number, pal: number, dec: number, cen: (number | undefined)[], palabra: string[]): void {
    let c: number = 0;
    let r: number = 0;
    let palabraR: number[] = [0]; // Arreglo de 1 elemento: simula paso por referencia (JS/TS no tiene parámetros por referencia para primitivos) — se usa como "palabraR[0]" en el resto de la función

    c = Math.trunc(div(n, 100));
    r = mod(n, 100);
    palabra[0] = "";
    if ((c > 0)) {
        if (((c === 1) && (r === 0))) {
            palabra[0] = "cien";
        } else {
            if ((c === 1)) {
                palabra[0] = "ciento";
            } else {
                palabra[0] = vecGet(cen, c);
            }
        }
    }
    if ((r > 0)) {
        ConvertirMenorQueCien(r, pal, dec, palabraR);
        if ((c > 0)) {
            palabra[0] = ((palabra[0] + " ") + String(palabraR[0]));
        } else {
            palabra[0] = palabraR[0];
        }
    }
}

/**
 * Convierte un número entre 0 y 99 a su palabra en español, usando las tablas 'pal' (0-29, con las irregularidades de 11-15 y 16-29 fusionadas) y 'dec' (múltiplos de diez desde 30).
 * @param n number
 * @param pal (number | undefined)[]
 * @param dec (number | undefined)[]
 * @param palabra number[] — por referencia (el subprograma lo reasigna; el valor final es visible para quien llama)
 */
function ConvertirMenorQueCien(n: number, pal: (number | undefined)[], dec: (number | undefined)[], palabra: number[]): void {
    let d: number = 0;
    let u: number = 0;

    if ((n <= 29)) {
        palabra[0] = vecGet(pal, (n + 1));
    } else {
        d = Math.trunc(div(n, 10));
        u = mod(n, 10);
        palabra[0] = vecGet(dec, d);
        if ((u > 0)) {
            palabra[0] = ((String(palabra[0]) + " y ") + String(vecGet(pal, (u + 1))));
        }
    }
}

/**
 * Convierte un número entero entre 0 y 9999 a su forma escrita en palabras (español) — ej. 5432 -> "cinco mil cuatrocientos treinta y dos". Usa tablas (vectores) para las irregularidades reales del idioma (11-15, 16-29 fusionados, 'cien' vs 'ciento', decenas y centenas con nombre propio) en vez de intentar una fórmula puramente aritmética.
 */
function main(): void {
    let pal: (number | undefined)[] = [];
    let dec: (number | undefined)[] = [];
    let cen: (number | undefined)[] = [];
    let N: number = 0;
    let resultado: string = '';
    let miles: number = 0;
    let resto: number = 0;
    let palabraResto: number[] = [0]; // Arreglo de 1 elemento: simula paso por referencia (JS/TS no tiene parámetros por referencia para primitivos) — se usa como "palabraResto[0]" en el resto de la función

    vecSet(pal, 1, "cero");
    vecSet(pal, 2, "uno");
    vecSet(pal, 3, "dos");
    vecSet(pal, 4, "tres");
    vecSet(pal, 5, "cuatro");
    vecSet(pal, 6, "cinco");
    vecSet(pal, 7, "seis");
    vecSet(pal, 8, "siete");
    vecSet(pal, 9, "ocho");
    vecSet(pal, 10, "nueve");
    vecSet(pal, 11, "diez");
    vecSet(pal, 12, "once");
    vecSet(pal, 13, "doce");
    vecSet(pal, 14, "trece");
    vecSet(pal, 15, "catorce");
    vecSet(pal, 16, "quince");
    vecSet(pal, 17, "dieciséis");
    vecSet(pal, 18, "diecisiete");
    vecSet(pal, 19, "dieciocho");
    vecSet(pal, 20, "diecinueve");
    vecSet(pal, 21, "veinte");
    vecSet(pal, 22, "veintiuno");
    vecSet(pal, 23, "veintidós");
    vecSet(pal, 24, "veintitrés");
    vecSet(pal, 25, "veinticuatro");
    vecSet(pal, 26, "veinticinco");
    vecSet(pal, 27, "veintiséis");
    vecSet(pal, 28, "veintisiete");
    vecSet(pal, 29, "veintiocho");
    vecSet(pal, 30, "veintinueve");
    vecSet(dec, 3, "treinta");
    vecSet(dec, 4, "cuarenta");
    vecSet(dec, 5, "cincuenta");
    vecSet(dec, 6, "sesenta");
    vecSet(dec, 7, "setenta");
    vecSet(dec, 8, "ochenta");
    vecSet(dec, 9, "noventa");
    vecSet(cen, 2, "doscientos");
    vecSet(cen, 3, "trescientos");
    vecSet(cen, 4, "cuatrocientos");
    vecSet(cen, 5, "quinientos");
    vecSet(cen, 6, "seiscientos");
    vecSet(cen, 7, "setecientos");
    vecSet(cen, 8, "ochocientos");
    vecSet(cen, 9, "novecientos");
    N = readDouble();
    if ((N === 0)) {
        resultado = "cero";
    } else {
        miles = Math.trunc(div(N, 1000));
        resto = mod(N, 1000);
        resultado = "";
        if ((miles > 0)) {
            if ((miles === 1)) {
                resultado = "mil";
            } else {
                resultado = (String(vecGet(pal, (miles + 1))) + " mil");
            }
        }
        if ((resto > 0)) {
            ConvertirMenorQueMil(resto, pal, dec, cen, palabraResto);
            if ((miles > 0)) {
                resultado = ((resultado + " ") + String(palabraResto[0]));
            } else {
                resultado = palabraResto[0];
            }
        }
    }
    console.log("El número " + String(N) + " en palabras es: " + resultado);
}

main();

Generated by freeDFDWeb from the flowchart above. It is the same file the editor’s Export button downloads.

Opens and saves the formats you already use

Imports and exports PSeInt (.psc) and Flowgorithm (.fprg), and opens .dfd files from classic DFD. All in the browser, nothing to install: works on Windows, Mac, Linux, Chromebook and phones.