Esta página lleva la tesis del programa hasta el final: aprende el representante, reconoce la familia entera. El mismo problema de la clase —pasar de texto a real, y de real a entero— resuelto por los primos de cada familia del Atlas, no solo por los diez lenguajes del núcleo.
Si entendiste la versión de Python, la de Ruby te resultará familiar aunque no la hayas visto nunca. Ese reconocimiento es exactamente lo que este curso quiere producir.
⚠️ Qué está verificado y qué no. Ruby, Perl y Lua se ejecutan en CI contra el mismo
casos.jsonque el núcleo, igual que las diez implementaciones de la clase (workflow Labs). Los otros 17 primos son material de lectura: su toolchain no está en el workflow, así que están escritos para ser correctos pero sin el sello de la máquina. Verificar tres de veinte no es verificarlos todos.
entero=<parte entera truncada> real=<valor con 2 decimales>entero = truncar(real), y el real se formatea con dos decimales| stdin | esperado |
|---|---|
3.7 |
entero=3 real=3.70 |
5.0 |
entero=5 real=5.00 |
8.9 |
entero=8 real=8.90 |
Dos conversiones en tres líneas: una de texto a número y otra de real a entero. La segunda esconde la trampa: truncar no es redondear, y no todos los lenguajes eligen lo mismo por defecto.
Representantes del núcleo: Python · PHP. Aquí conviven los dos extremos del curso: lenguajes que convierten texto en número sin que se lo pidas (Perl, Tcl) y lenguajes que exigen decirlo (Python).
f = STDIN.gets.to_f
# to_i trunca hacia cero; round() redondearía. Ruby NO coacciona sola:
# "3" + 1 es un error de tipo, al contrario que en Perl.
puts "entero=#{f.to_i} real=#{format('%.2f', f)}"
use strict;
use warnings;
my $f = <STDIN>;
# Perl es el rey de la coerción implícita: el mismo escalar es texto o número
# según el operador que lo toque. int() trunca hacia cero, no redondea.
printf "entero=%d real=%.2f\n", int($f), $f;
local f = io.read("n")
-- Ojo: math.floor redondea hacia ABAJO, no hacia cero. Para los casos
-- positivos de la clase coincide con truncar; para -3.7 daría -4, no -3.
-- Desde Lua 5.3 existen enteros reales y math.tointeger.
print(string.format("entero=%d real=%.2f", math.floor(f), f))
gets stdin linea
# En Tcl todo es cadena: double() e int() son funciones de expr, no casts.
set f [expr {double($linea)}]
puts "entero=[expr {int($f)}] real=[format %.2f $f]"
f <- as.numeric(readLines("stdin", n = 1))
# as.integer() trunca hacia cero; round() usa redondeo al par más cercano.
cat(sprintf("entero=%d real=%.2f\n", as.integer(f), f))
Qué reconocer: los cinco escriben la conversión con una llamada explícita, igual que el int()
de Python, pero la diferencia real está en lo que pasa sin llamarla. En Perl "3.7" * 2 vale
7.4 sin protestar; en Ruby, "3.7" * 2 devuelve "3.73.7" porque el * de cadena repite. Y
atención a la trampa de Lua: math.floor no es truncar, y solo coincide con el int() de Python
mientras el número sea positivo.
Representantes del núcleo: JavaScript · TypeScript.
import 'dart:io';
void main() {
final f = double.parse(stdin.readLineSync()!.trim());
// int y double no se convierten solos en Dart: truncate() es obligatorio.
print('entero=${f.truncate()} real=${f.toStringAsFixed(2)}');
}
// ActionScript corre en el reproductor Flash, sin stdin: se ilustra el cálculo.
package {
public class Conversion {
public static function convertir(s:String):String {
var f:Number = parseFloat(s);
// int() trunca hacia cero, igual que el Math.trunc de JavaScript.
return "entero=" + int(f) + " real=" + f.toFixed(2);
}
}
}
Qué reconocer: ActionScript es JavaScript con tipos declarados, y arrastra su coerción: el +
concatena si un operando es cadena, y por eso "entero=" + int(f) funciona sin conversión explícita.
Dart cortó justamente con eso. Su int y su double son tipos distintos que no se mezclan, no
existe la conversión implícita en el +, y por tanto la interpolación es la única forma limpia de
pegar un número a un texto. Es la misma corrección que hizo TypeScript sobre JavaScript, pero
llevada al tiempo de ejecución.
Representante del núcleo: Java. La JVM sí tiene coerción implícita, pero solo
en la dirección segura: int sube a double solo; bajar de double a int exige un cast.
fun main() {
val f = readLine()!!.trim().toDouble()
// Kotlin eliminó incluso las conversiones ampliadoras implícitas de Java:
// un Int NO se asigna a un Double sin toDouble(). toInt() trunca hacia cero.
println("entero=${f.toInt()} real=${"%.2f".format(f)}")
}
object Conversion extends App {
val f = scala.io.StdIn.readLine().trim.toDouble
println(f"entero=${f.toInt}%d real=$f%.2f")
}
def f = System.in.newReader().readLine().trim() as double
// Groovy resucita la coerción dinámica sobre la JVM: `as` convierte casi cualquier cosa.
printf("entero=%d real=%.2f%n", (int) f, f)
;; (long f) trunca hacia cero; (Math/round f) redondearía.
(let [f (Double/parseDouble (clojure.string/trim (read-line)))]
(println (format "entero=%d real=%.2f" (long f) f)))
Qué reconocer: los cuatro truncan hacia cero porque abajo hay una única instrucción de bytecode,
d2i, y esa instrucción trunca. Lo que cambia es cuánto te obligan a escribirlo. Kotlin es el más
estricto de todos —más incluso que Java, porque prohíbe también las conversiones ampliadoras
automáticas—, mientras que Groovy va al extremo contrario y recupera con as una coerción de
lenguaje dinámico. Misma máquina virtual, filosofías opuestas.
Representante del núcleo: C#.
// F# no admite NINGUNA conversión implícita, ni siquiera int → float:
// `int` y `float` son funciones que hay que escribir. `int` trunca hacia cero.
let f = float (stdin.ReadLine().Trim())
printfn "entero=%d real=%.2f" (int f) f
Imports System.Globalization
Module Conversion
Sub Main()
Dim f = Double.Parse(Console.ReadLine().Trim(), CultureInfo.InvariantCulture)
' Trampa clásica de VB: CInt() REDONDEA al par más cercano (CInt(3.5) = 4,
' CInt(2.5) = 2). El que trunca hacia cero es Fix().
Dim entero As Long = CLng(Fix(f))
Console.WriteLine("entero=" & entero & " real=" & f.ToString("F2", CultureInfo.InvariantCulture))
End Sub
End Module
Qué reconocer: los tres corren sobre el CLR y comparten Double y CultureInfo —esa cultura
invariante es imprescindible aquí, porque en configuración regional española el separador decimal es
la coma y Parse fallaría—. Pero cada uno resuelve la conversión a entero de forma distinta: C# usa
un cast que trunca, F# una función que trunca, y VB.NET arrastra del BASIC clásico una pareja
peligrosa donde CInt redondea y solo Fix trunca. Es exactamente el tipo de detalle que hace
que un programa traducido «token a token» dé otro resultado.
Representante del núcleo: C. La conversión de double a int está definida por
el estándar: descarta la parte fraccionaria, es decir, trunca hacia cero.
#include <iostream>
#include <iomanip>
int main() {
double f;
std::cin >> f;
// La conversión sería implícita, pero static_cast la deja a la vista
// y avisa al compilador de que la pérdida de precisión es intencionada.
std::cout << "entero=" << static_cast<int>(f)
<< " real=" << std::fixed << std::setprecision(2) << f << '\n';
}
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
double f;
scanf("%lf", &f);
// El cast al estilo C, idéntico al de la implementación de la clase.
printf("entero=%d real=%.2f\n", (int)f, f);
}
return 0;
}
Qué reconocer: los dos aceptan el cast (int)f de C sin cambios, así que la implementación de la
clase compila tal cual en ambos. C++ añade static_cast, que hace lo mismo pero es buscable y
no puede confundirse con las conversiones peligrosas (reinterpret_cast, const_cast); es una
mejora de legibilidad, no de semántica. Objective-C no tocó nada de esto: su capa de objetos se monta
encima, sin alterar la aritmética heredada.
Representantes del núcleo: Go · Rust. Ninguno de los dos convierte nada solo: cada cambio de tipo se escribe, y por eso las conversiones son fáciles de auditar.
const std = @import("std");
pub fn main() !void {
var buf: [64]u8 = undefined;
const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
const f = try std.fmt.parseFloat(f64, std.mem.trim(u8, linea, " \r\n"));
// Zig prohíbe toda conversión con pérdida silenciosa: @intFromFloat es
// obligatorio y trunca hacia cero. El tipo destino lo fija la declaración.
const entero: i64 = @intFromFloat(f);
try std.io.getStdOut().writer().print("entero={d} real={d:.2}\n", .{ entero, f });
}
import std/[strutils, strformat]
# Nim sí convierte int → float solo, pero NUNCA float → int: int() es explícito y trunca.
let f = parseFloat(stdin.readLine().strip())
echo &"entero={int(f)} real={f:.2f}"
import std.stdio, std.string, std.conv;
void main() {
const f = readln().strip().to!double;
// Cuidado con el par de D: to!int REDONDEA y lanza si no cabe;
// cast(int) es el que trunca hacia cero, como en C.
writefln("entero=%d real=%.2f", cast(int) f, f);
}
Qué reconocer: los tres exigen escribir la conversión que pierde información, igual que el
as i64 de Rust y el int(f) de Go. Zig es el más radical: no hay cast genérico, hay funciones
integradas con nombre (@intFromFloat, @floatFromInt) que dicen exactamente qué se está haciendo.
Y D repite aquí la trampa que ya vimos en VB.NET: dos formas de convertir que no dan el mismo
resultado, to!int redondeando y cast(int) truncando.
Representante del núcleo: SQL. La conversión se pide como una operación sobre
un valor de la relación (CAST), no como un cambio de la variable.
:- initialization(main, main).
main :-
read_line_to_string(user_input, Linea),
% number_string convierte texto a número y funciona en ambas direcciones.
number_string(F, Linea),
% `is` fuerza la evaluación aritmética; truncate/1 corta hacia cero.
Entero is truncate(F),
format("entero=~d real=~2f~n", [Entero, F]).
% Datalog puro no tiene aritmética de conversión, funciones ni formateo de salida:
% no existe un truncate. El par (real, parte entera) tiene que declararse como hecho.
medida(3.7, 3).
medida(5.0, 5).
medida(8.9, 8).
entrada(3.7).
resultado(R, E) :- entrada(R), medida(R, E).
Qué reconocer: Prolog separa dos cosas que en los lenguajes imperativos van juntas. F = 3+4
no vale 7: unifica F con el término literal 3+4; hace falta is para pedir que se evalúe. Esa
distinción entre término y valor calculado es la razón de que la conversión de tipos en Prolog se
vea siempre alrededor de un is. Datalog no llega ni a tener aritmética —cualquier función
convertiría el lenguaje en algo que ya no garantiza terminar—, y esa renuncia es la misma que hace
SQL al no dejarte escribir bucles.
Veinte lenguajes, un solo problema, y el mismo esqueleto en todos: leer texto, volverlo número, recortarlo a entero y formatearlo con dos decimales. Lo que cambia es la forma y, en algunos casos, las garantías. Eso es lo transferible.