⬅️ Volver a la clase 041 · 🧬 Primos del Atlas · 🧟 Índice de lenguajes vivos · 📚 Índice
Esta página resuelve el problema de esta clase —el total de una venta— en los lenguajes antiguos que siguen ejecutándose hoy en producción. Y lo hace preguntando, en cada uno, lo que pregunta esta clase: ¿cómo se escribe un literal, cómo se nombra un valor y cómo se promete que un nombre no cambiará?
🎯 Estos lenguajes no están aquí por ser antiguos
El criterio es doble y se declara en la ficha de cada uno: que se ejecute hoy —banca, sanidad, aviónica, ERP, diseño de chips, CAD— y que deje a la vista un concepto que los diez del núcleo esconden.
Aquí ese concepto es el enlace entre un nombre y un valor, que es exactamente el tema de la clase. Y resulta que estos lenguajes lo resuelven de formas que el núcleo ya no muestra: COBOL declara la forma exacta del literal antes de usarlo, Ada tiene números con nombre sin tipo y de precisión ilimitada, Smalltalk escribe literales decimales exactos, y MUMPS directamente no tiene declaraciones ni constantes. Ver cuatro respuestas distintas a la misma pregunta es lo que convierte
constde palabra memorizada en decisión comprendida.Y ninguno de ellos es una foto fija: casi todos han incorporado en los últimos años JSON, REST, GPU, Unicode o Git. Cada ficha lo detalla en su sección
🔄 Lo que se ha modernizado.
precio_unitario cantidad descuentoTotal: <total con 2 decimales>total = precio_unitario * cantidad * (1 - descuento)| stdin | esperado |
|---|---|
15000 2 0.10 |
Total: 27000.00 |
999.9 3 0 |
Total: 2999.70 |
5000 0 0.20 |
Total: 0.00 |
Qué está verificado en esta página. Los ocho lenguajes de la sección 🟢 se ejecutan en CI contra este mismo
casos.json, igual que las diez implementaciones del núcleo (workflow Labs). Los de la sección 🟡 no pueden cumplir este contrato sin falsear el lenguaje, y se explica por qué. Los de la sección ⚪ sí podrían, pero su cadena de herramientas no está en los runners: son correctos, sin sello de máquina.
Ficha completa · Banca, seguros, gobierno, medios de pago ·
cobc -x -free total.cob
IDENTIFICATION DIVISION.
PROGRAM-ID. TOTAL-VENTA.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LINEA PIC X(80).
01 TXT-PRECIO PIC X(20).
01 TXT-CANTIDAD PIC X(20).
01 TXT-DESCUENTO PIC X(20).
01 PRECIO PIC 9(9)V99 COMP-3.
01 CANTIDAD PIC 9(9)V99 COMP-3.
01 DESCUENTO PIC 9V9(4) COMP-3.
01 TOTAL-CALC PIC 9(12)V99 COMP-3.
01 TOTAL-EDITADO PIC ZZZZZZZZZ9.99.
PROCEDURE DIVISION.
ACCEPT LINEA
UNSTRING LINEA DELIMITED BY ALL SPACES
INTO TXT-PRECIO TXT-CANTIDAD TXT-DESCUENTO
END-UNSTRING
MOVE FUNCTION NUMVAL(TXT-PRECIO) TO PRECIO
MOVE FUNCTION NUMVAL(TXT-CANTIDAD) TO CANTIDAD
MOVE FUNCTION NUMVAL(TXT-DESCUENTO) TO DESCUENTO
COMPUTE TOTAL-CALC ROUNDED = PRECIO * CANTIDAD * (1 - DESCUENTO)
MOVE TOTAL-CALC TO TOTAL-EDITADO
DISPLAY "Total: " FUNCTION TRIM(TOTAL-EDITADO)
STOP RUN.
Lo que esta clase enseña en COBOL. Aquí la variable se declara antes que nada y con su forma
exacta: PIC 9(9)V99 COMP-3 no dice "un número", dice nueve dígitos enteros, dos decimales, coma
implícita y representación decimal empaquetada. No hay inferencia de tipo, no hay literal que decida
por ti, no hay conversión silenciosa. En Python escribes precio = 15000 y el lenguaje elige; en
COBOL eliges tú, y esa elección queda escrita para quien audite el programa dentro de treinta años.
Y la constante existe desde el primer día, en la propia declaración:
01 IVA PIC 9V99 VALUE 0.19.
01 MAX-DESC PIC 9V99 VALUE 0.50.
78 NOMBRE-APP VALUE "Facturación". *> nivel 78: constante de verdad
La cláusula VALUE da el valor inicial; el nivel 78 (y CONSTANT desde COBOL 2002) declara algo
que no se puede modificar. COBOL tiene además constantes figurativas —ZERO, SPACES,
HIGH-VALUES, LOW-VALUES— que son literales con nombre incorporados al lenguaje: MOVE SPACES TO
LINEA rellena de espacios sin importar cuánto mida el campo.
Ficha completa · HPC, clima, física, BLAS/LAPACK ·
gfortran -O2 total.f90
program total_venta
implicit none
real(kind=8) :: precio, cantidad, descuento, total
character(len=32) :: buffer
read(*, *) precio, cantidad, descuento
total = precio * cantidad * (1.0d0 - descuento)
write(buffer, '(F20.2)') total
write(*, '(A)') 'Total: ' // trim(adjustl(buffer))
end program total_venta
Lo que esta clase enseña en Fortran. Dos lecciones sobre literales, y las dos son trampas reales.
La primera: implicit none. Sin esa línea, una variable no declarada no es un error: Fortran
le asigna tipo según su inicial (i–n enteros, el resto reales), herencia directa de 1957. Un
totl mal tecleado se convertiría en un real nuevo con valor basura, en silencio. Es el mismo
problema que resuelven use strict en Perl y Option Explicit en VBA, y es la razón de que la
primera línea de todo Fortran serio sea siempre la misma.
La segunda: el literal lleva su precisión encima. 1.0 es precisión simple; 1.0d0 es doble.
Escribir 1.0 en una expresión de dobles introduce una conversión y puede perder dígitos. La forma
portable y moderna es declarar el kind y usarlo en el literal:
use iso_fortran_env, only: real64
real(real64) :: total
real(real64), parameter :: IVA = 0.19_real64 ! constante: valor fijado al compilar
parameter es la constante de Fortran: se evalúa en tiempo de compilación y no puede aparecer a
la izquierda de una asignación. El sufijo _real64 en el literal es lo que garantiza que la
constante nazca ya con la precisión correcta.
Ficha completa · Aviónica, espacio, ferrocarril, defensa ·
gnatmake total_venta.adb
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Long_Float_Text_IO; use Ada.Long_Float_Text_IO;
procedure Total_Venta is
-- El tipo dice lo que el negocio permite, no solo lo que la máquina guarda.
subtype Descuento_T is Long_Float range 0.0 .. 1.0;
Precio, Cantidad : Long_Float;
Descuento : Descuento_T;
Total : Long_Float;
begin
Get (Precio);
Get (Cantidad);
Get (Descuento); -- un 1.5 aquí levanta Constraint_Error
Total := Precio * Cantidad * (1.0 - Descuento);
Put ("Total: ");
Put (Total, Fore => 1, Aft => 2, Exp => 0);
New_Line;
end Total_Venta;
Lo que esta clase enseña en Ada. Ada tiene dos clases de constante distintas, y la segunda no existe en ningún lenguaje del núcleo:
-- 1) Constante con tipo: como el `const` que ya conoces.
IVA : constant Long_Float := 0.19;
-- 2) NÚMERO CON NOMBRE: sin tipo, exacto, evaluado con precisión ilimitada.
Pi : constant := 3.14159_26535_89793_23846;
Un_Millon : constant := 1_000_000;
Tercio : constant := 1.0 / 3.0; -- se guarda como fracción exacta
Un número con nombre (constant sin tipo) no es un Float ni un Integer: es un valor
matemático que el compilador manipula con precisión arbitraria y solo convierte al tipo concreto en
el punto donde se usa. Tercio no arrastra el error de redondeo de un Float; ese error aparece —
si aparece— al final. Es una separación entre el valor y su representación que casi ningún
lenguaje hace, y es exactamente la distinción que esta clase intenta enseñar.
Fíjate también en el subrayado como separador en 3.14159_26535 y 1_000_000: Ada lo introdujo
en 1983, y hoy lo han copiado Java, C#, Python, Rust, Ruby, C++ y Go. Y en que Descuento no es un
Long_Float cualquiera: su tipo lleva escrito el rango legal, así que un descuento de 1.5 detiene
el programa en el punto exacto en lugar de producir un total negativo.
Ficha completa · Object Pascal / Delphi:
escritorio empresarial, TPV, industria · fpc -Mobjfpc total_venta.pas
program TotalVenta;
{$MODE OBJFPC}{$H+}
var
Precio, Cantidad, Descuento, Total: Double;
begin
Read(Precio, Cantidad, Descuento);
Total := Precio * Cantidad * (1 - Descuento);
WriteLn('Total: ', Total:0:2);
end.
Lo que esta clase enseña en Pascal. Wirth diseñó este lenguaje precisamente alrededor del tema de esta clase, y se nota en dos decisiones.
La primera: := asigna y = compara. No es cosmética. En C, if (x = 5) compila, asigna y
evalúa como verdadero; es uno de los errores más caros de la historia del lenguaje. En Pascal es
imposible de escribir. La distinción entre "ligar un nombre a un valor" y "preguntar si dos
valores son iguales" está en la sintaxis, no en la disciplina del programador.
La segunda: las constantes tienen sección propia, y las hay de dos clases:
const
IVA = 0.19; { sin tipo: el compilador lo deduce en cada uso }
MAX_DESC: Double = 0.50; { con tipo: en Delphi/FPC es una variable de solo lectura }
EMPRESA = 'Ferretería';
Una constante sin tipo es un literal con nombre: se sustituye donde se usa y se adapta al
contexto. Una constante con tipo ocupa memoria de verdad y —detalle histórico que sorprende— con
la directiva {$J+} heredada de Turbo Pascal podía modificarse. Es un buen recordatorio de que
"constante" significa cosas distintas en lenguajes distintos, que es justo lo que esta clase quiere
que compruebes.
Y Total:0:2 es formateo en la propia sintaxis del Write: ancho mínimo 0, dos decimales, sin
cadena de plantilla y sin depender de la configuración regional.
Ficha completa · IA simbólica, CAD, investigación ·
sbcl --script total-venta.lisp
;;; total-venta.lisp — clase 041
(setf *read-default-float-format* 'double-float)
(let* ((precio (read))
(cantidad (read))
(descuento (read))
(total (* precio cantidad (- 1 descuento))))
(format t "Total: ~,2F~%" total))
Lo que esta clase enseña en Common Lisp. Aquí el literal es un concepto más profundo que en cualquier otro lenguaje de esta página, por dos motivos.
Primero: (read) no analiza una cadena, lee un objeto de Lisp. El mismo lector que interpreta el
código fuente interpreta la entrada del usuario. 15000 llega como entero, 0.10 como real, y si
escribieras (1 2 3) llegaría como una lista. No hay split, ni parseFloat, ni conversión: los
literales del programa y los datos de entrada son la misma cosa. Esa identidad es la
homoiconicidad, y esta clase es donde primero se puede tocar.
Segundo: Lisp tiene literales que otros lenguajes no tienen, incluidos los números racionales exactos:
(/ 1 3) ; => 1/3 un racional EXACTO, no 0.3333333
(* 3 (/ 1 3)) ; => 1 sin error de redondeo
(expt 2 200) ; => 1606938044258990275541962092341162602522202993782792835301376
Un entero de Lisp no tiene tamaño máximo y una fracción no se convierte a decimal salvo que lo pidas.
Compara con el int de 32 bits de la mayoría del núcleo.
Y para nombrar valores hay tres formas, con significados distintos:
(defconstant +iva+ 0.19) ; constante: redefinirla es un error
(defparameter *tasa* 0.19) ; global pensada para cambiarse a mano
(defvar *log* nil) ; global que NO se pisa si ya tenía valor
(let ((x 1)) ...) ; ligadura local
Las convenciones tipográficas —+constantes+, *especiales*— no las impone el compilador: las
impone la comunidad, y son universales. Que el lenguaje distinga defparameter de defvar (una se
reinicia al recargar el fichero, la otra no) muestra hasta qué punto Lisp se toma en serio el
momento en que un nombre se liga a un valor, que es el binding time del que habla esta clase.
Ficha completa · Diseño de chips (EDA), redes, testing ·
tclsh total.tcl
gets stdin linea
lassign [split [string trim $linea]] precio cantidad descuento
set total [expr {$precio * $cantidad * (1 - $descuento)}]
puts [format "Total: %.2f" $total]
Lo que esta clase enseña en Tcl. Es el caso límite y por eso vale la pena verlo: en Tcl todos
los valores son cadenas. 15000 no es un entero que se imprime como texto; es el texto "15000"
que expr decide leer como número. No hay literales numéricos, no hay literales booleanos, no hay
tipos: hay cadenas y comandos que las interpretan.
Eso responde de golpe a la pregunta de la clase sobre tipado débil. Cuando en JavaScript te
sorprende que "5" * 2 dé 10, estás viendo una versión atenuada de lo que Tcl hace como principio
de diseño declarado (Everything Is A String).
Sobre constantes: durante treinta años Tcl no tuvo ninguna, y la convención era el nombre en
mayúsculas y la disciplina del equipo. Tcl 9.0 (2024) añadió const:
const IVA 0.19
set IVA 0.21 ;# error: can't set "IVA": variable is a constant
Que un lenguaje de 1988 incorpore constantes en 2024 dice dos cosas: que sigue en desarrollo, y que esta clase trata de un problema que nunca deja de ser relevante.
Y el detalle crítico: expr siempre con llaves. Sin ellas, Tcl sustituye las variables antes
de pasar el texto a expr, lo que es más lento y —si una variable contiene texto arbitrario— abre
una vía de inyección. Es la regla número uno del lenguaje.
Ficha completa · Sysadmin, texto, bioinformática · perl total.pl
use strict;
use warnings;
my $linea = <STDIN>;
chomp $linea;
my ($precio, $cantidad, $descuento) = split ' ', $linea;
my $total = $precio * $cantidad * (1 - $descuento);
printf "Total: %.2f\n", $total;
Lo que esta clase enseña en Perl. Los sigilos son una respuesta distinta a la pregunta de qué
declara un nombre: $ no dice "esto es un número" ni "esto es una cadena", dice "esto se está
usando como un valor único". @lista es la lista entera y $lista[0] es un elemento —con $,
porque un elemento suelto es un escalar—. El sigilo describe la forma del acceso, no el tipo del
dato. Es un eje que ningún lenguaje del núcleo tiene.
Y por eso $precio puede contener "15000" y multiplicarse sin conversión explícita: el escalar de
Perl guarda a la vez una representación numérica y una textual, y usa la que pida el contexto.
Las constantes se declaran con un módulo, no con sintaxis:
use constant IVA => 0.19;
use constant MAX_DESC => 0.50;
use constant COLORES => ('rojo', 'verde'); # también listas
print IVA; # 0.19 — es una subrutina en línea, no una variable: sin sigilo
Que use constant sea un módulo y no una palabra clave es muy representativo de Perl: la
constante se implementa como una subrutina que devuelve siempre lo mismo y que el compilador
sustituye en línea. El lenguaje prefiere dar herramientas para construir la característica antes que
incorporarla.
Y use strict es aquí lo mismo que implicit none en Fortran: sin él, $totl mal tecleado crea una
variable global nueva con valor indefinido y el programa imprime Total: 0.00 sin quejarse.
Ficha completa · Videojuegos, navegadores, finanzas, HPC ·
g++ -std=c++17 total.cpp
#include <iomanip>
#include <iostream>
int main() {
double precio{}, cantidad{}, descuento{};
if (!(std::cin >> precio >> cantidad >> descuento)) {
return 1;
}
const double total = precio * cantidad * (1 - descuento);
std::cout << "Total: " << std::fixed << std::setprecision(2) << total << '\n';
return 0;
}
Lo que esta clase enseña en C++. Es el lenguaje con más grados de constancia de todos, y compararlos aclara qué significa realmente "constante":
const double IVA = 0.19; // no se puede modificar
constexpr double IVA2 = 0.19; // además, se conoce AL COMPILAR
consteval double doble(double x){return 2*x;} // OBLIGA a evaluarse al compilar
constinit double tabla = calcular(); // inicializada antes de main()
constexpr int TAM = 10;
int datos[TAM]; // solo compila porque TAM es constexpr, no solo const
const es una promesa sobre quién puede escribir; constexpr es una afirmación sobre cuándo se
conoce el valor. Son ejes independientes, y esa distinción —que en Python o JavaScript ni siquiera
se puede expresar— es la versión más precisa del binding time que estudia esta clase: constexpr
liga el valor en tiempo de compilación, const solo restringe la mutación en ejecución.
Los literales también llevan información: 27000.0 es double, 27000.0f es float, 27000ULL es
un entero largo sin signo, 0b1101 es binario y 1'000'000 usa la comilla simple como separador
(C++14, la misma idea que Ada popularizó). Y double precio{} con llaves inicializa a cero y
prohíbe las conversiones que pierden información, algo que = permitiría en silencio.
Estos lenguajes no pueden leer de stdin y escribir en stdout sin dejar de ser ellos mismos.
No es una limitación del material: es su naturaleza. Aquí el cálculo es el mismo y la forma de entrar
y salir es la de su anfitrión. No pasan por el verificador, y se dice.
Ficha completa · IBM i: ERP, retail, logística, manufactura ·
CRTBNDRPG sobre IBM i
En IBM i un programa recibe sus datos por parámetros, por un fichero o por una pantalla, nunca por la entrada estándar.
**free
ctl-opt dftactgrp(*no) actgrp(*caller);
dcl-pi TOTVTA;
precio packed(11:2) const;
cantidad packed(11:2) const;
descuento packed(5:4) const;
end-pi;
dcl-c IVA 0.19; // constante con nombre
dcl-c MAX_DESC 0.50;
dcl-s total packed(15:2) inz(0); // variable, con valor inicial explícito
dcl-s salida char(40);
total = precio * cantidad * (1 - descuento);
salida = 'Total: ' + %char(total);
dsply salida;
*inlr = *on;
return;
Lo que esta clase enseña en RPG. La distinción entre constante y variable es sintáctica y
tajante: dcl-c declara una constante con nombre —no ocupa memoria, no puede aparecer a la
izquierda de una asignación— y dcl-s declara una variable independiente, con su valor inicial en
inz(). Son dos palabras clave distintas, no un modificador.
Y el tipo del dinero es packed(11:2): decimal empaquetado, aritmética exacta, la misma decisión
que toma COBOL. Cuando esta clase pregunta "¿qué tipo tiene el literal 15000?", la respuesta en un
ERP no es "un entero": es "once dígitos con dos decimales, en decimal, porque es dinero".
Sí podrían cumplir el contrato, pero su cadena de herramientas no está en los runners de CI.
Ficha completa · Mainframe z/OS: banca, seguros · IBM Enterprise PL/I
total_venta: procedure options(main);
declare precio fixed decimal(11,2);
declare cantidad fixed decimal(11,2);
declare descuento fixed decimal(5,4);
declare total fixed decimal(15,2);
declare presenta picture 'ZZZZZZZZZ9V.99';
declare IVA fixed decimal(3,2) value (0.19); /* constante */
on endfile(sysin) stop;
get list (precio, cantidad, descuento);
total = precio * cantidad * (1 - descuento);
presenta = total;
put skip list ('Total: ' || trim(presenta));
end total_venta;
Lo que esta clase enseña en PL/I. Es el único lenguaje de esta página que separa explícitamente las dos dimensiones de un número: la base (decimal o binaria) y la escala (fija o flotante). Existen las cuatro combinaciones:
declare a fixed decimal(11,2); /* dinero: exacto, base 10 */
declare b fixed binary(31); /* entero de máquina */
declare c float decimal(15); /* real con precisión decimal */
declare d float binary(53); /* el `double` de siempre */
Casi todos los lenguajes modernos te dan int y double y ocultan que esas son solo dos casillas de
una matriz de cuatro. PL/I las pone las cuatro sobre la mesa porque nació para unir el mundo
científico de Fortran con el empresarial de COBOL. El atributo value declara la constante, y la
picture es un dato numérico de imagen: la misma idea que el campo editado de COBOL.
Ficha completa · Sanidad: historia clínica, VistA, Epic · YottaDB
TOTVTA ; Total de una venta -- clase 041
read linea
set precio = $piece(linea, " ", 1)
set cantidad = $piece(linea, " ", 2)
set descuento = $piece(linea, " ", 3)
set total = precio * cantidad * (1 - descuento)
write "Total: ", $justify(total, 0, 2), !
quit
Lo que esta clase enseña en M. Es la respuesta más radical de toda la página: no hay declaraciones, no hay tipos y no hay constantes. Ninguna de las tres cosas. Una variable existe desde que se le asigna algo, todo valor es una cadena, y si quieres una constante la escribes en mayúsculas y confías en el equipo.
Suena primitivo hasta que aparece la línea que hace único a M:
set ^VENTAS($horolog, "total") = total
El circunflejo cambia todo: ^VENTAS es un global, es decir, una variable persistente. Eso
ya está en disco, es transaccional y lo ven todos los procesos, con la misma sintaxis con la que
escribirías una variable local. La pregunta de esta clase —qué es una variable, cuánto vive el enlace
entre un nombre y un valor— tiene en M una tercera respuesta que el núcleo no contempla: el enlace
puede sobrevivir al programa. Sin ORM, sin INSERT, sin serializar. Un carácter de diferencia.
Ficha completa · Banca, seguros, trading · Pharo
| linea partes precio cantidad descuento total |
linea := stdin nextLine.
partes := linea substrings collect: [ :cada | cada asNumber ].
precio := partes first.
cantidad := partes second.
descuento := partes third.
total := precio * cantidad * (1 - descuento).
Transcript
show: 'Total: ', (total printShowingDecimalPlaces: 2);
cr.
Lo que esta clase enseña en Smalltalk. Aquí el literal es un objeto, sin excepción. 15000
no es un valor primitivo: es una instancia de SmallInteger a la que puedes enviarle mensajes, y
15000 factorial funciona (y devuelve un número de más de cincuenta mil dígitos, porque los enteros
crecen sin límite). No hay tipos primitivos fuera del sistema de objetos, y por tanto no hay
autoboxing, ni Integer frente a int, ni ninguna de las costuras que Java arrastra.
La sintaxis de literales es notablemente rica, e incluye uno que resuelve el problema del dinero:
27000.00s2 "ScaledDecimal: DECIMAL EXACTO con 2 decimales, no punto flotante"
3/4 "una fracción exacta"
16r1F "31 en base 16 — la base se escribe delante"
#unSimbolo "símbolo: cadena única e inmutable"
#(1 2 'tres') "array literal"
$a "un carácter"
27000.00s2 es un ScaledDecimal: aritmética decimal exacta como el COMP-3 de COBOL, pero
escrita como un literal del lenguaje. Y 16r1F invierte la convención que conoces —en vez de 0x1F,
la base va delante y funciona para cualquier base entre 2 y 36.
Sobre constantes: Smalltalk no tiene una palabra clave para ellas. La forma idiomática es un método de clase que devuelve el valor, porque en un lenguaje donde todo es un mensaje, una constante es simplemente un método que siempre responde lo mismo:
Impuestos class >> iva
^ 0.19s2
Doce lenguajes con una media de cuarenta años cada uno, un solo problema, y doce respuestas distintas a la pregunta de esta clase: COBOL declara la forma del dato antes de calcular, Ada separa el valor de su representación, C++ separa quién puede escribir de cuándo se conoce el valor, Lisp borra la frontera entre literal y dato, Smalltalk convierte el literal en objeto, Tcl lo convierte todo en texto, y MUMPS deja que el enlace sobreviva al programa.
Ninguna de esas respuestas es la que da Python. Y ese es el motivo de que estén aquí.
⏮️ Volver a la clase 041 · 🧬 Los primos del Atlas · 🧟 Índice de lenguajes vivos