Autor Tema: Curso de C - Ejercicio 49.1

0 Usuarios y 1 Visitante están viendo este tema.

06 Octubre, 2013, 07:12 pm
Leído 5588 veces

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
Problema 49.1. del curso de C

(Esto proviene de: Sección 49 del curso de C)
 
En todo programa, primero hay una etapa de diseño y planificación.

Hay que estudiar bien el problema antes de lanzarse a programar como locos.

Veamos el enunciado:
   
\( \bullet \)  49.1. Pedir al usuario que ingrese una base \( b \). Luego, pedir al usuario que ingrese números reales (en decimal), y convertirlos a base \( b \) a medida que el usuario los va ingresando. Continuar hasta que el usuario ingrese 0 o un valor fuera del rango que hemos convenido adoptar para el tipo double.

Aunque no lo parezca, el enunciado contiene algunas imprecisiones. Por ejemplo:
   
¿La base \( b \) es un número entero positivo?

Lo típico al hablar de cambios de bases es que: sí. Las bases son enteros positivos.
Pero hay teorías matemáticas que discuten bases con otras características.
Si éste fuera el caso, tendríamos que estudiar primero el tema de las bases "en general".

Por otro lado, siempre conviene intentar resolver primero la versión más simple de un programa, y luego atacar versiones más complicadas o sofisticadas.

\( \bullet \) Así que en una 1er versión del programa vamos a considerar sólo bases enteras positivas.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Ahora, sin embargo, se nos presentan otros problemas.
Dado un número, digamos \( x \), en base \( b \), necesitamos representarlo con tantos dígitos como indica la base, es decir, \( b \). Esto es, un alfabeto de \( b \) símbolos.
Lo usual es que el alfabeto de dígitos contenga los primeros elementos del conjunto de dígitos decimales: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
Esto sólo es posible cuando la base es diez o menor que diez.
Si la base es 16, se usan las letras A, B, C, D, E, F, como dígitos adicionales.
Podemos entonces usar esos mismos símbolos cuando la base sea mayor que diez y menor o igual que 16.

¿Y si la base es mayor que 16? ¿Se pueden usar las restantes letras del alfabeto?
En principio sí, pero notemos que hay algunos obstáculos.
Por ejemplo, la letra O se confundiría fácilmente con un dígito "cero".
Como la letra O ocupa la posición 15 en el alfabeto (siguiendo la secuencia de caracteres ASCII, o sea, rindiéndonos antes el imperialismo yanki), sólo podríamos llegar sin obstáculos hasta la N, que ocupa la posición 14.
No podríamos usar las letras de la P en adelante, porque el orden de los dígitos no sería ahora claro (de la N saltaríamos a la P, y eso sería confuso, pues la P representaría un valor menor que el que intuitvamente le adjudicaríamos).
Agregando los diez dígitos decimales, tendríamos como máximo unos 24 dígitos disponibles, lo cual nos permitiría representar bases que vayan hasta la 24.

Pero veo otro obstáculo. Es la letra I, que ocupa la posición 9 en el alfabeto. Es muy probable que llegue a confundirse con un dígito 1.
Pensando en eso, de nuevo descartaríamos las letras que siguen a la I, porque habría un salto antiintuitivo en la secuencia, al pasar de la H a la J en la lista de "dígitos".
Así que sólo tendríamos 8 letras disponibles para dígitos: de la A a la H (8 letras), pudiendo representar así bases hasta la 18.

Para bases mayores que 18 habría que decidir otro tipo de diseño.
Hay varias alternativas, pero cualquiera que elijamos, resulta extraño que elijamos un diseño basado en letras del alfabeto para las bases 17 y 18, y que ya para la base 19 adoptemos un diseño totalmente distinto.

Lo que podemos hacer aquí es dar opciones al usuario, para que elija la manera que él prefiera para representar dígitos.

No me convence ese enfoque, porque en la parte teórica no hemos visto herramientas del todo adecuadas para hacer que el programa venga tan "interactivo".

Así que para las bases 11 a 16 usaré el enfoque de utilizar letras del alfabeto para los dígitos adicionales, tomando lo que necesitemos de la secuencia: A, B, C, D, E, F.
Y para las bases mayores que 16 usaré sólo números escritos en decimal, pero de tal modo que los "dígitos" queden claramente separados entre sí.
Por ejemplo, un número en base 34 utilizaría los números 0 al 33 como "digitos", y esto obligaría a que pongamos un separador (por ejemplo, un apóstrofo):
   
11'7'9'0'33'14'11'11'2'25

El uso del apóstrofo soluciona el problema, y permite ahora salir del paso con cualquier base, porque ya tenemos un método que funciona en general.

Pero ocurre también que poner un apóstrofo por cada dígito hace muy accidentada la lectura del número.
Me parece preferible buscar un método en que no se usen esos apóstrofos.
Hacerlo así, sin más, da resultados ambiguos. El ejemplo anterior quedaría así:

1179033141111225

¿Qué significa ese número en base 34? ¿Comienza con un dígito 11, o con dos dígitos 1?

Se me ocurre aquí una solución bastante sencilla: usar dos dígitos decimales por cada dígito en base 34. Rellenar con 0's o con espacios en blanco cuando el dígito en base 34 tenga un valor menor que 10.
Así, el ejemplo anterior quedaría:
   
11070900331411110225

Con la convención de que cada par de dígitos decimales representa un dígito en base 34, ahora no hay ambigüedad en la lectura.
Esa seguidilla de cifras debe leerse como la secuencia de dígitos en base 34 siguiente:

11  7  9  0  33  14  11  11  2  25

Si usáramos "espacios" en vez de 0's, sería aún más fácil de leer:

11 7 9 033141111 225

Ahí se entiende que el "trozo" 033141111, que sigue a un "espacio", tiene que leerse como el dígito 0, seguido de los dígitos 33, 14, 11 y 11, ya que la ausencia de "espacios" indica que han de leerse dígitos "de a pares".

Según el contexto, esto puede resultar confuso, porque no sabemos si se trata de un solo número, o varios números separados por espacios en blanco.

Así que en vez de un espacio en blanco pondré allí algún caracter visible, como por ejemplo, un subrayado: _
El ejemplo anterior queda ahora así:

11_7_9_033141111_225

Ahora el subrayado _ sirve de marca para decir: "aquí no se lee un par de dígitos, sino uno solo".

Con la misma idea, podríamos representar dígitos en base 299, pero ahora necesitaríamos 3 dígitos decimales por cada dígito en base 299.
Por ejemplo:

115'8'27'277'66'4'11'0'114

sería un número con dígitos en base 299, separadads por apóstrofos.

Esto de los apóstrofos lo estamos usando, a fin de cuenta, como parte de la discusión del diseño, para entendernos, pero no como parte de los resultados que arrojará nuestro programa. ¿Loco no?

Nuestro programa intentará mostrar ese número con 3 caracteres, ya sea dígitos decimales o el subrayado: _.

115__8_27277_66__4_11__0114

Notemos que los dígitos entre 0 y 9 necesitan un doble subrayado: __,
mientras que los dígitos entre 10 y 99 necesitan un solo subrayado: _.
Los demás (entre 100 y 298), se leen tomando de 3 en 3 cifras decimales.
Por ejemplo, el "trozo" _27277 se lee como la secuencia de 2 dígitos en base 299: 27, 277.

A fin de cuentas, el subrayado _ está jugando el mismo papel que un 0.
Estaría de más, en ese caso.
Pero me parece a mí que un subaryado _ es más fácil de interpretar, al leerlo en pantalla, que un 0.
Contrastemos el efecto visual del mismo número en base 299 anterior, poniendo 0's en donde había subrayados:

115__8_27277_66__4_11__0114

115008027277066004011000114


En general, si la base está entre \( 10^k < b \leq  10^{k+1} \), vamos a usar la convención de utilizar por cada dígito en base \( b \), exactamente \( k \) dígitos en base diez, y reemplazando los 0's con subrayados para mayor legibilidad.

Importante: se exceptúa el caso en que el dígito en base \( b \) es él mismo el 0. Allí sí usaremos el dígito 0 para representarlo.

Este último detalle seguramente nos traerá algún pequeño escollo cuando diseñemos el programa. Por eso lo he remarcado.
Todas las "excepciones" traen dolores de cabeza que luego hay que atender.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Cuando la base es \( b=1 \), el programa debe indicar un mensaje de error, ya que no es posible escribir números en base 1.
Lo mismo si la base es 0 o un número negativo.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Cuando se debe realizar una conversión de una base a otra, suele ser muy complicado lidiar con la parte detrás del punto fraccionario.
Consideremos el siguiente ejemplo:

Es fácil pasar el número 104 (en base diez) a base 7, porque hacemos:
   
\( 104 / 7 \)  --->  14 con resto 6
\( 14 / 7 \)   --->   2 con resto 0
\( 2 / 7 \)    --->   0 con resto 2

Luego, 104 en base 7 es 206 (tomamos los restos de atrás hacia adelante).
Este enfoque nos obligaría a ir guardando los restos, para mostrarlos al final a todos, en orden inverso.
Pero no es necesario hacerlo así.
Podemos operar "al revés":
   
\( 104 / 7^2 \) ---> 2 con resto 6
\( 6   / 7^1 \) ---> 0 con resto 6
\( 6   / 7^0 \) ---> 6 con resto 0

Ahora tomamos los "cocientes" sucesivos, en vez de los "restos", y eso nos va "regurgitando" los dígitos en base 7, que nos da:
   
2 0 6

¿Cómo lidiar con los números que tienen una parte fraccionaria?

Pasar el número 0.104 (en base diez) a base 7.

Aquí se procede de modo algo distinto: se va multiplicando por 7 a fin de obtener los sucesivos dígitos, hasta "terminar", si es que esto es posible, o hasta hallar una repetición periódica.
   
\( 0.104 \cdot 7  \)   ---> 0.728. Se toma la parte entera 0 como dígito en base, y el resto sigue operando:
\( 0.728 \cdot 7  \)   ---> 5.096 ---> Se toma 5 como dígito, y "resto" 0.096
\( 0.096 \cdot 7  \)   ---> 0.672 ---> Se toma 0 como dígito, y "resto" 0.672
\( 0.672 \cdot 7  \)   ---> 4.704 ---> Se toma 4 como dígito, y "resto" 0.704

Esto está destinado a continuar sin fin.

Pero necesitamos que nuestro programa termine en algún lugar.
Deberíamos poder reconocer que hay una repetición a partir de algún momento, indicar que allí hay un período (de dígitos en base 7), y terminar allí el programa.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Más aún, tengamos en cuenta que el resultado obtenido no será del todo preciso, ya que en realidad los cálculos anteriores realizan una previa conversión interna del número decimal 0.104 a base 2.
Los cálculos los hace la computadora internamente en base 2, y allí podemos tener pérdidas de precisión.

Como trabajaremos con datos de tipo double, sabemos que al menos 10 dígitos decimales serán confiables o significativos.
Así que podríamos proseguir nuestros cálculos hasta agotar esos 10 decimales significativos.

O bien, podríamos advertir al usuario que más allá de esos 10 decimales los resultados pueden contener errores, e igual le mostramos lo que nos dé.

En ese caso, tenemos que reconocer en qué momento los dígitos en base 7 comienzan a repetirse, si es que lo hacen.
Esto no resulta sencillo.
Dado que tenemos "apenas" 10 decimales significativos, al multiplicarlos en secuencia por 7 (o por otra base cualquiera) podemos obtener, como mucho, una lista de \( 10^{10} \) "restos" distintos (números entre 0 y 1).
O sea que el proceso terminar en alguna parte. No obstante, esos son demasiados dígitos a mostrar en pantalla.

Dado que la precisión (en base decimal) del tipo double está indicada por la macro DBL_DIG, no conviene presentar más dígitos en base \( b \) que esos cuando \( b \) es mayor que 10.
Eso dará una buena idea del número. Si queremos, podemos exhibir un par de dígitos más en base \( b \), como para "espiar" un poco más allá, a ver qué hace la computadora con esto.

Cuando la base \( b \) es menor que 10, debiéramos presentar más dígitos que la cantidad DBL_DIG. ¿Cuántos?
Bueno, algo así como multiplicar por un factor de \( [\log_{b} 10] + 1 \) (los corchetes indican "parte entera").
Esto para base 2 nos da un factor de 4.

No veo del todo sencilla la parte de "reconocer la parte periódica" del número convertido a base \( b \).
Esto requeriría algún tipo de manipulación de fracciones.
Se puede hacer, pero lo dejaré para una 2da versión del programa.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

En general, se ve que tenemos procedimientos que nos permiten ir generando las cifras en base \( b \) de un número, sin necesidad de memorizar los dígitos que vamos obteniendo en alguna parte.

Esto permite resolver el problema con las herramientas teóricas que hemos visto hasta ahora, que son muy pocas.
También muestra que es posible resolver este problema sin el uso de memoria adicional.

(En realidad, estaríamos usando la pantalla del monitor como "memoria", jeje).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Primero conviene escribir el esqueleto del programa, y luego ir rellenando los huecos, que suelen ser de complejidad creciente, y requieren análisis detallado.

En virtud de la previa discusión de diseño, podemos vislumbrar algunas pautas generales del esqueleto.

Como siempre, incluiremos las librerías <stdio.h> para intercambiar datos con el usuario vía consola estándar, y <stdlib.h> para cambiar la página de códigos a 28591 a fin de visualizar correctamente los acentos en castellano, mediante:

   system("CHCP 28591");


También incluiremos la librería <stdbool.h>, acorde a los comentarios de la Sección 48 del curso.
Como no usaremos números complejos, no hará falta incluir esta vez <complex.h>.

Dado que nombramos la macro DBL_DIG de la librería <float.h>, podríamos necesitar incluir esta librería.
De hecho, no lo haré, y esto lo decido aquí caprichosamente.
Hemos dicho en la Sección 48 que sólo asumiremos 10 dígitos decimales significativos.

Así que preferiré usar una constante llamado Significant_Digits, puesta igual a 10, y usaré ésta en vez de DBL_DIG.

Esto nos evitará algunos inconvenientes imprevistos. Por ejemplo, podría darse el caso de que tengamos la suerte de tener una súpercomputadora en la que haya 40000 dígitos significativos para el tipo double. Eso causaría estragos en nuestro inocente programa, que intentaría mostrar siempre esa cantidad de dígitos.

En una futura versión del programa, se podría pedir al usuario que elija la cantidad de dígitos significativos que desea, informándole previamente de la cantidad máxima disponible.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Esqueleto

bc.00.01.c


// BC 00.01

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

int main(void) {
    system("CHCP 28591");
   
    int Significant_Digits;
    Significant_Digits = 10;
   
    printf("BaseConv (Base Converter) v. 0.01\n\n");
    printf("Este programa convierte números x de base decimal a base b.\n\n");
    printf("Se le pedirá al usuario que elija una base b para trabajar.\n");
    printf("Luego, se le pedirá al usuario que escriba números x en base decimal, para ser convertidos a base b.\n");
    printf("Para terminar, ingrese el número x = 0\n\n");
   
    int b;  /* Base a la que convertiremos los números decimales */
   
    do {
        printf("Elija la base b = ");       
        scanf("%d", &b);
       
        if (b <= 1)
           printf("Error: la base debe ser b >= 2.\nIntente de nuevo.\n\n");
           
    } while(b <= 1);
    /* Desde aquí ya estamos seguros que b >= 2 */
   
    double x;  /* Esta variable contendrá el número que deseamos expresar en base b */
   
    do {
        printf("Escriba un número decimal x = ");
        scanf("%lg", &x);
       
        // Convertir x a base b, e ir mostrando los dígitos de la conversión
       
    } while(x != 0.0);
   
    printf("Con la elección x = 0 este programa termina.\n");
    printf("Fin del programa.\n");

    system("PAUSE");   
    return 0;
}



Arriba de todo: una línea de comentario con el nombre y versión del programa. Esto es información para el programador (por ahora, nosotros mismos solamente).

Tras la inclusión de las librerías estándar, entramos directo en la función main(), que es en donde se desarrollará nuestro programa.

La primer sentencia cambia la página de códigos en la línea de comandos de Windows, de modo que se visualicen correctamente los acentos y eñes del castellano.
Quienes usen Linux u otro sistema, pueden quitar esa línea, o cambiarla por alguna otra cosa conveniente para su sistema particular.

Al final del programa tenemos otra llamada al sistema, sólo para hacer una pausa en la ejecución del programa, y evitar que nos desaparezca la ventana de línea de comandos antes de ver los últimos mensajes del programa.
Esta línea también puede omitirse o sustituirse por algún otro procedimiento.

Importante: Observen que sólo he hecho uso de la función system() al principio y al final del programa, para que quede claro que sólo en esos puntos de ejecución estoy haciendo uso de llamadas al sistema. Más aún, quitando esas dos líneas, se puede omitir la inclusión de la librería <stdlib.h>.

Al principio se declara un objeto que quedará constante a lo largo del programa: la variable entera Significant_Digits, que ponemos igual a 10, de una vez y para siempre. Esto respeta nuestra convención hecha en la Sección 48 de la teoría, en que los dígitos significativos de valores double serán, para nosotros, de 10.
Decisiones de diseño para casos más generales, vendrán más adelante en el curso.

A continuación viene una seguidilla de instrucciones printf() que muestran mensajes al usuario:
   
- Nombre y versión del programa.
         Las versiones con números menores que 1.00 se usan para programas aún no completados.

- Propósito general del programa.

- Explicación del funcionamiento del programa, junto con algunas indicaciones al usuario, y resultados esperados.

- Alguna indicación o explicación de cuándo y cómo se termina el programa.

Después de esas preliminares, se declara un objeto b de tipo int, en el que guardaremos la información de la base de numeración a la que haremos las conversiones.

Luego se pide al usuario que elija una base b.
Se le informa con un mensaje mediante printf() qué es el dato que debe ingresar.
Se usa scanf() para "capturar" el dato ingresado por el usuario.

Normalmente el usuario tiene que presionar la tecla ENTER para terminar el ingreso de datos en forma correcta.

Observemos que hemos usado una estructura do while() para el ingreso de este dato.
¿Por qué?
Muy sencillo: para perseguir estilo pesadilla al usuario mientras este ingrese una base que no nos gusta.
Le obligamos a que ingrese un número \( b \geq 2 \), que si no, se repite el pedido de ingresar otra vez un número.

Podemos estar seguros de que al usuario rebelde le ganaremos por cansancio. Tal así funciona Terminator: con un do while():   

do {

    buscar_a_Sarah_Connor_y_atentar_contra_su_vida();

} while(Sarah_Connor_sigue_viva());


Eso sí: Terminator no falla jamás gracias a que alguien se tomó el trabajo de escribir correctamente los punto y coma al final de cada instrucción.

Cuando el usuario ingresa un valor de base no deseado, le informamos con un mensaje de error.
Esto requiere que previamente verifiquemos si el valor ha sido erróneo, lo cual nos obliga a utilizar una estructura if().

Una consecuencia desagradable de esto es que acabamos por "preguntar" dos veces lo mismo: ¿es cierto que \( b\leq 1 \)? Lo preguntamos en el if() y luego en la línea while();.
Por ahora, esto lo dejamos así, porque es más claro a la vista.
Hay modos antiestéticos de quitar esa comparación adicional...

Una vez que el usuario ha cesado en su rebeldía, y ha elegido una base \( b\geq 2 \), observemos que podemos confiar en ello y no tenemos necesidad de verificarlo nuevamente.
Así que, desde el punto de vista lógico, es siempre cierto a partir de allí que \( b\geq 2 \), y esto lo pusimos como un comentario.

Es lo que damos en llamar: un invariante lógico del programa.
Es una condición que sabemos que a partir de ese momento se cumple, hasta que aparezca otro comentario que nos informe de lo contrario.

Trabajaremos siempre asiduamente con comentarios del tipo "invariante lógico".

Seguidamente declaramos un objeto x de tipo double.
Esta variable irá cambiando de valor muchas veces, pues es en donde guardaremos los números que el usuario nos vaya dando para trabajar.

Lo que sigue es un while(), tal que en cada iteración se le pide al usuario que ingrese un número decimal (que puede tener parte fraccionaria).
A continuación se lo ha de convertir a base \( b \), pero como esto es complicado, lo postergamos, y ponemos ahí sólo una línea de comentario con la promesa de que lo vamos a resolver luego.

El ciclo se repite hasta que el usuario elige un valor de x igual a 0.0 (ó 0).

Finalmente el programa se despide con algunos mensajes.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Continuará... :)

06 Octubre, 2013, 10:38 pm
Respuesta #1

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
Primero retocaremos la cuestión de los dígitos significativos en base b.

Esto es fácil.
Calculamos aproximadamente \( \log_b 10 \) y lo guardamos en una variable que llamaremos Signif_factor.
A este número lo multiplicamos por Significant_Digits, y lo guardamos en la variable Signif_base_b.

Voy a definir tango Signif_factor como Signif_base_b, como de tipo double.
Si bien no tiene sentido una cantidad fraccionaria de dígitos, lo dejo así por ahora.

A continuación le informamos al usuario, mediante instrucciones printf(), cuántos dígitos se van a usar como máximo en los resultados del programa.
Allí exhibimos la parte entera de la variable Signif_base_b.
Para lograr esto, basta hacer un cast explícito hacia el tipo int, así:

(int) Signif_base_b


Los números \( \log_b 10 \) no los vamos a calcular directamente, sino que los voy a poner aproximadamente, y a mano.
No es un detalle muy importante por ahora, y nos alcanza con esto.
Por eso verán que aparece ahí un extraño switch(), cuya misión es poner manualmente un valor aproximado de dicho logaritmo en la variable Signif_factor.

______________

También agregamos comentarios al programa, que lo separan en 3 secciones principales:

\( \bullet \) Sección A: Preliminares
\( \bullet \) Sección B: Elección de base b, y preparación de dígitos significativos en base b
\( \bullet \) Sección C: Conversión de números decimales a base b.

______________

En esta versión del programa, sólo hemos hecho cambios en la Sección B.

bc0.02.c


#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

int main(void) {
    system("CHCP 28591"); // Página de códigos adecuada al castellano
       
    // Sección A: Preliminares
   
    printf("BaseConv (Base Converter) v. 0.02\n\n");
    printf("Este programa convierte números x de base decimal a base b.\n\n");
    printf("Se le pedirá al usuario que elija una base b para trabajar.\n");
    printf("Luego, se le pedirá al usuario que escriba números x en base decimal, para ser convertidos a base b.\n");
    printf("Para terminar, ingrese el número x = 0\n\n");
   
    // Sección B: Base b, dígitos significativos en base b
   
    int Significant_Digits;
    Significant_Digits = 10;
   
    int b;  /* Base a la que convertiremos los números decimales */
   
    do {
        printf("Elija la base b = ");       
        scanf("%d", &b);
       
        if (b <= 1)
           printf("Error: la base debe ser b >= 2.\nIntente de nuevo.\n\n");
           
    } while(b <= 1);
    /* Desde aquí ya estamos seguros que b >= 2 */
   
    double Signif_factor;
   
    switch (b) {
        case 2:
            Signif_factor = 3.32;
            break;
        case 3:
            Signif_factor = 2.1;
            break;
        case 4:
            Signif_factor = 1.66;
            break;
        case 5:
            Signif_factor = 1.43;
            break;
        case 6:
            Signif_factor = 1.29;
            break;
        case 7:
            Signif_factor = 1.18;
             break;
       case 8:
            Signif_factor = 1.11;
            break;
        case 9:
            Signif_factor = 1.05;
            break;
        default:
            Signif_factor = 1.0;
    }
   
    double Signif_base_b;
    Signif_base_b = Signif_factor * Significant_Digits;
   
    printf("Los siguientes resultados se mostrarán hasta con ");
    printf("%d", (int) Signif_base_b);
    printf(" dígitos significativos.\n\n");
   
    // ------------------------------------------------------------
       
    // Sección C: Pasar números decimales x a base b
   
    double x;  /* Esta variable contendrá el número que deseamos expresar en base b */
   
    do {
        printf("Escriba un número decimal x = ");
        scanf("%lg", &x);
       
        // Convertir x a base b, e ir mostrando los dígitos de la conversión
       
    } while(x != 0.0);
   
    printf("Con la elección x = 0 este programa termina.\n");
    printf("Fin del programa.\n");
   
    system("PAUSE");   
    return 0;
}



07 Octubre, 2013, 06:13 am
Respuesta #2

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
A la Sección B del programa le añadiremos un par de subsecciones B1 y B2.

La subsección B.1 comprenderá sólo la parte del do while() que usábamos para elegir una base b.

la subsección B.2 será aquella parte encargada de seleccionar cuántos dígitos en base b se van a mostrar.
Esa parte queda sin cambios, aunque he agregado un printf() que agrega una línea en blanco a la salida.

La subsección B.3 es nueva, y se encarga de calcular el logaritmo en base 10 de la base b.
Hacemos esto con el propósito de saber cuántos dígitos decimales son necesarios para representar cada dígito en base b.
Si b está entre 2 y 10, basta 1 decimal por cada dígito en base b.
Si está entre 11 y 100, bastan 2 decimales por cada dígito en base b.
Y así sucesivamente.

Para encontrar la cantidad de decimales adecuada, hay que hacer una búsqueda.
El algoritmo que uso es el siguiente:

    int decimal_b;   // Cantidad de dígitos decimales para cada dígito en base b
    int ten_pow;
   
    for(decimal_b = 0, ten_pow = 1; ten_pow < b; decimal_b++, ten_pow *= 10)
         ;
   
    // 10^{decimal_b} == ten_pow
    // decimal_b == [log_10 (b-1)] + 1

La variable decimal_b contendrá la cantidad de decimales necesaria.
Se comienza la cuenta en 0, en la parte de inicialización del ciclo for( ; ; ).
Preguntamos si 10 elevado a dicha cuenta es una cantidad menor que b.
En ese caso, aumentamos la cuenta en 1 (con decimal_b ++).

Las potencias de 10 las vamos obteniendo junto con las iteraciones del ciclo for( ; ; ).
Es decir, en vez de calcularlas todas las veces, mejor vamos multiplicando por 10 en cada paso, y así obtenemos en cada iteración la siguiente potencia de 10.
Esto se hace en la variable ten_pow.

En realidad, en cada paso vale la igualdad: 10^{decimal_b} == ten_pow (estoy mezclando notación de Latex con C, perdonen).

Vemos aquí el uso de los operadores de incremento ++ y de asignación con producto *=.
Lo que hace la expresión ten_pow *= 10 es tomar el valor que había en la variable ten_pow, multiplicarlo por 10, y el resultado obtenido lo asigna a ten_pow, con lo cual el contenido en memoria del objeto ten_pow se modifica.
De paso, astutamente se modifica de manera que en cada paso se obtiene la siguiente potencia de 10.

Las iteraciones continúan hasta que la potencia de 10 supera el valor de b.
El estado en que queda el programa lo hemos escrito en forma de comentarios:
   
* La variable decimal_b contiene el valor de la parte entera del logaritmo en base 10 de (b - 1), aumentado en 1.

A continuación, agregamos unos cuantos printf() informando al usuario que los números en base b se van a exhibir en pantalla de modo que cada dígito en base b será representado por una cantidad de decimal_b decimales.

    printf("Cada dígito en base ");
    printf("%d", b);
    printf(" será representado por ");
    printf("%d", decimal_b);
    printf(" dígito(s) en base diez.\n\n");
         


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Como se puede apreciar, el estilo de programación que estoy usando es bien claro:
   
Todo está bien documentado, tanto para el programador como para el usuario del programa.

Los programas "misteriosos", que nadie sabe qué hacen, ni cómo, ni por qué, no le sirven a nadie.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Pasemos a la Sección C.

Dentro del ciclo do while() he agregado dos subsecciones: c1, C2 y C3.

La subsección C1 arregla un poco los potenciales problemas de signos.
Guarda el signo de x en alguna parte.
Y luego trabaja sólo con el valor absoluto de x.

        // Subsección C.1: Trabajar con la parte "positiva" de x, para más comodidad
       
        int signo;       
        signo = (x >= 0.0)? +1: -1;
       
        if (signo < 0)
           x = -x;     // Ahora x es  "positivo"
       
        // x > 0.0  en lo que sigue...
           


Una vez descartados los valores nulos y negativos, sólo nos quedan valores positivos de x,
y esto nos facilitirá algunas cosas (esto es necesario aquí, debido a que todavía trabajamos con escasas herramientas del lenguaje).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

La subsección C2 realiza una operación preliminar: calcula la parte entera del logaritmo en base b del número x previamente elegido por el usuario.
En otras palabras, se busca el entero k tal que:
   
\( b^k\leq x < b^{k+1} \)

El procedimiento es similar al que usamos previamente para calcular el logaritmo en base 10 de b.
Consideramos las sucesivas potencias de b, y las guardamos en la variable b_pow_k.
Ese nombre es bastante explícito: sugiere que el valor allí guardado es el número \( b^k \).

Se realiza una búsqueda aumentando de 1 en 1 el valor de k, hasta que se cumpla la condición:
   
\( x < b^k  \)

Esto no es exactamente lo que queríamos, pero "a veces" se parece.

Supongamos que \( x\geq1  \).
En ese caso, como la sucesión \( b^k \) crece con k, y tiende a \( \infty \), seguramente existirá un valor de k tal que:

\( b^{k-1}\leq x < b^k  \)

Dado que x es de tipo double, tiene un valor acotado, así que hay un máximo valor de k posible para cualquier entrada x que elija el usuario, sin importar cuán maligno éste sea.
Nos queda lo siguiente:

        // Subsección C.1: Buscar k tal que b^k <= x < b^{k+1}
       
        int k;
        double b_pow_k;
       
        k = 0, b_pow_k = 1.0;
       
        while (b_pow_k <= x)
            k++,   
            b_pow_k *= b;

Observemos cómo se ha usado el operador coma para poner todas las expresiones juntas en una sola sentencia terminada con punto y coma en el while().
Esto permite evitar el uso de llaves, cosa que me agrada más.

Ahora tendríamos que corregir el valor de k, porque se nos ha pasado "por 1" hacia arriba.
Lo que hay que hacer es disminuirlo en 1.
Pero esto lo postergaremos un poco hasta terminar el análisis.

Supongamos que \( 0< x < 1 \).

En este caso existe un entero negativo k tal que \( b^k\leq x< b^{k+1} \).
Como sea, el bucle while() anterior no se ejecuta ni una sola vez, porque inicialmente la variable b_pow_x vale 1.0, mientras que \( x < 1 \).
Así que en este caso, es como si allí no hubiésemos escrito nada.

Así, seguimos teniendo, sin cambios, que k == 0 y b_pow_k == 1.0.
A partir de ahí comenzamos a hacer una búsqueda "hacia atrás", restando de 1 en 1 el valor de k,
y generando potencias negativas de la base b.
Para esto, tenemos que "dividir" por b en cada iteración.
El algoritmo es similar a lo anterior, y queda así:

        while (x < b_pow_k)
            // x < b_pow_k
            k--,
            b_pow_k /= b;
           
        // b_pow_k <= x < b_pow_k


Hemos agregado comentarios denotando "invariantes del algoritmo".
Al comimenzo de cada iteración del while() se cumple que x < b_pow_k.
Al final de la iteración, no sabemos...

Se "decrementa" en 1 el valor de k, y se divide por b la variable b_pow_k.

Antes de iniciar el while() también es cierto que x < b_pow_k.
Esto es obligatorio, porque es la única manera en que el while() anterior a éste pudiera haber "dejado paso" al flujo del programa.
Como luego vamos dividiendo iterativamente por b, el resultado tras finalizar el while() es que:
   
b_pow_k <= x < b_pow_k

(Demostrarlo, si hiciera falta convencerse).

Ahora volvamos a la situación en que \( x\geq 1 \).
Allí nuestro primer while() nos había dejado en la situación \( b^{k-1}\leq x < b^k  \).
Si en esta situación llegamos al segundo while(), la condición x < b_pow_k es ahora verdadera.
Así que el while() se ejecuta al menos 1 vez, ¡y logra el efecto que deseábamos: corrige el valor de k bajándolo en 1!
Y esta será la única iteración que se producirá (convencerse de que esto es así).

O sea que la conjunción de los dos ciclos while(), uno después del otro, nos arregla las cosas para todos los valores de k, tanto positivos como negativos.
 
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

La subsección C3 contendrá la conversión de x a base b.

El procedimiento que emplearemos sirve para cualquier número en el rango de double, sin importar si es mayor que 1, o menor que 1.
La idea es que, si nos corremos hasta la potencia \( b^k \), el número x tiene una representación en base b de la forma:
   
\( d_0.d_1...d_N \cdot b^k \)

O sea, estamos escribiendo no sólo en base b, sino también usando notación exponencial con base b.

Esto no presenta ninguna "confusión", porque en base b, multiplicar por \( b^k \) equivale a correr el punto fraccionario k lugares hacia la derecha (si k es positivo), o -k lugares a la izquierda (si k es negativo).
O sea que no debemos asustarnos del significado de este resultado así obtenido.

Como ya tenemos guardada la potencia \( b^k \) en la variable b_pow_k, para obtener el dígito \( d_0 \) basta dividir x por dicha potencia.
Una vez hecho esto, restamos el dígito obtenido a x, pero debemos tener cuidado de restárselo respecto la "potencia k-ésima".
Para no "ensuciar" la variable x, vamos a utilizar otra variable llamada xx, que inicialmente es igual a x.
El código es éste:

        // Subsección C.3: Extracción de dígitos en base b
       
        printf("   ---> (en base ");
        printf("%d", b);
        printf(") :    ");
       
        double xx;  // Esta variable manipulará el valor inicial de x, sin cambiar x
        xx = x;
       
        // Primer dígito:
           
        int dig;       
        dig = (int) (xx /= b_pow_k);
           
        printf("%d", signo*dig);
        printf(".");

Primero iniciamos la variable xx de modo que valga lo mismo que x.
Luego dividimos su valor por la potencia \( b^k \), mediante la expresión: (xx /= b_pow_k).
Esa expresión la convertimos al tipo int para poder guardar su valor en la variable entera dig.
La variable entera dig guardará los sucesivos dígitos en base b que vayamos obteniendo.

El cast explícito hacia el tipo int produce un truncamiento del valor double alojado en xx.

Luego mandamos el dígito en base b recién obtenido a la pantalla con un printf().
De paso, le hemos colgado el signo que originalmente tenía x, al multiplicarlo por la variable signo.
Esto tiene sentido, porque el signo acompaña siempre al primer dígito en la escritura de un número.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Para los siguientes dígitos, observemos que ya nos quedó xx como un valor entre 0 y 1.
En este caso, teníamos un procedimiento sistemático que nos generaba los dígitos en base b: ir multiplicando sucesivamente por b, quedarnos con la parte entera del resultado, guardarla en la variable dig (pues es un nuevo dígito obtenido), luego mostrar el dígito en pantalla.

        // Siguientes dígitos

        for (int Nro_Digitos = 1; Nro_Digitos <= Signif_base_b; Nro_Digitos++) {
             xx -= dig;
             xx *= b;
             dig = (int) xx;
             
             // Aquí hay que arreglar el asunto de los caracteres subrayados _

             printf("%d", dig);
        }
   

El ciclo for() utiliza una variable interna, que es sólo un contador.
Cuenta el número de dígitos que se van a mostrar.
Como ya hemos mostrado 1 dígito de x en base b, la cuenta comienza en 1.

La cuenta se incrementa en 1 por cada iteración, hasta sobrepasar la máxima precisión que habíamos establecido en la variable Signif_base_b.

En cada iteración lo que se hace es lo siguiente:

* Se le resta a xx el último dígito que habíamos obtenido (y que está guardado en dig). (No hace falta tener en cuenta la potencia k-ésima en forma explícita, porque hemos "normalizado" xx de manera que sea un número entre 0 y b).

* Entonces en xx queda un "resto", que se multiplica ahora por b, y esto se vuelve a guardar en x, para la próxima iteración.

* La parte entera de xx es ahora el nuevo dígito, y entonces se le hace un cast explícito a (int) (para obtener dicha parte entera), y se guarda este valor en la variable dig.

* Finalmente se muestra el dígito con un printf().

Sin embargo, falta un paso intermedio que hemos puesto como comentario.

Tenemos que agregar convenientemente los subrayados, tal como convinimos en el primer post de este hilo.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Hay dos casos para analizar: o bien el dígito dig es 0, o bien no lo es.
Si es 0, hay que agregar tantos subrayados como decimales eran necesarios para representar dígitos en base b... menos 1.
O sea, hay que agregar decimal_b subrayados.
Esto se hace directamente con un bucle repetitivo for( ; ; ), así:

                 for (int j = 1; j <= decimal_b - 1; j++)
                    printf("_");


Si dig es cualquier otro dígito en base b distinto de 0, entonces tenemos que analizar entre cuáles dos potencias de 10 se encuentra, para saber cuántos subrayados hay que poner.

Lo que haremos será una búsqueda de adelante hacia atrás, comenzando desde la potencia de 10 más alta posible, y bajando hasta la potencia 0-ésima.
Por conveniencia, haremos referencia directamente a las potencias de 10 ya calculadas, antes que al exponente de dichas potencias.
Se comienza desde la potencia guardada en ten_pow, y se disminuye en 1 el exponente de 10, lo cual equivale a dividir por 10.
En cada iteración dividimos por 10 para ir "bajando" de 1 en 1 el exponente de la potencia de 10.

Luego se itera hasta que el dígito dig resulta por fin menor que alguna potencia de 10.
Mientras esto ocurre, significa que tenemos que ir agregando caracteres subrayado: _.
¿Por qué? Porque mientras dig < p10 en el algoritmo que sigue, querrá decir que la potencia indicada en ese momento por la variable p10 no está siendo representada (o sea, ahí iría un 0).

                 for (int p10 = ten_pow/10; dig < p10; p10 /= 10)
                    printf("_");


Juntando los dos casos, amerita la inclusión de un if() que vaya hacia una u otra situación:

             if (dig == 0)
                 for (int j = 1; j <= decimal_b - 1; j++)
                    printf("_");
             else
                 for (int p10 = ten_pow/10; dig < p10; p10 /= 10)
                    printf("_");


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Al final de todo esto, hay que agregar en pantalla la información de la potencia \( b^k \), para que todo esté correcto.
Esto lo hacemos con unos cuantos printf():

        printf(" * ");
        printf("%d", b);
        printf("^");
        printf("%d", k);
        printf("\n\n");

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Ya está casi listo el programa.
Sólo nos falta agregar el caso de las bases 11 a 16, en los que habíamos dicho que íbamos a usar las letras del alfabeto A, B, C, D, E, F, como dígitos.

Al agregar ese detalle, podremos "lanzar al mercado" la versión 1.00 del programa.

Sin embargo, nuestro programa tiene errores graves de precisión, que se notan en algunos ejemplos concretos.

Esto se ve claramente al usar la base trivial b = 10 y jugar con algunos números allí.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

El programa completo tiene por ahora esta pinta:
   
bc00.10.c

// BC 00.10

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

int main(void) {
    system("CHCP 28591"); // Página de códigos adecuada al castellano
       
    // Sección A: Preliminares
   
    printf("BaseConv (Base Converter) v. 0.10\n\n");
    printf("Este programa convierte números x de base decimal a base b.\n\n");
    printf("Se le pedirá al usuario que elija una base b para trabajar.\n");
    printf("Luego, se le pedirá al usuario que escriba números x en base decimal, para ser convertidos a base b.\n");
    printf("Para terminar, ingrese el número x = 0\n\n");
   
    // Sección B: Base b, dígitos significativos en base b
   
    //    @ B.1: Elegir base b
   
    int Significant_Digits;
    Significant_Digits = 10;
   
    int b;  /* Base a la que convertiremos los números decimales */
   
    do {
        printf("Elija la base b = ");       
        scanf("%d", &b);
       
        if (b <= 1)
           printf("Error: la base debe ser b >= 2.\nIntente de nuevo.\n\n");
           
    } while(b <= 1);
    /* Desde aquí ya estamos seguros que b >= 2 */
   
    //    @ B.2: Dígitos significativos en base b
   
    double Signif_factor;
   
    switch (b) {
        case 2:
            Signif_factor = 3.32;
            break;
        case 3:
            Signif_factor = 2.1;
            break;
        case 4:
            Signif_factor = 1.66;
            break;
        case 5:
            Signif_factor = 1.43;
            break;
        case 6:
            Signif_factor = 1.29;
            break;
        case 7:
            Signif_factor = 1.18;
             break;
       case 8:
            Signif_factor = 1.11;
            break;
        case 9:
            Signif_factor = 1.05;
            break;
        default:
            Signif_factor = 1.0;
    }
   
    // Signif_factor es aproximadamente log 10/log b, para b < 10.
   
    double Signif_base_b;
    Signif_base_b = Signif_factor * Significant_Digits;
   
    printf("\n");
    printf("Los siguientes resultados se mostrarán hasta con ");
    printf("%d", (int) Signif_base_b);
    printf(" dígitos significativos.\n\n");
   
    //    @ B.3: Dígitos decimales por cada dígito en base b.
   
    int decimal_b;   // Cantidad de dígitos decimales para cada dígito en base b
    int ten_pow;
   
    for(decimal_b = 0, ten_pow = 1; ten_pow < b; decimal_b++, ten_pow *= 10)
         ;
         
    // decimal_b == [log_10 (b-1)] + 1 == {dígitos de (b-1) en base 10}
    // ten_pow == 10^{decimal_b}
       
    printf("Cada dígito en base ");
    printf("%d", b);
    printf(" será representado por ");
    printf("%d", decimal_b);
    printf(" dígito(s) en base diez.\n\n");
         
    // ------------------------------------------------------------
       
    // Sección C: Pasar números decimales x a base b
   
    double x;  /* Esta variable contendrá el número que deseamos expresar en base b */
   
    do {
        printf("Escriba un número decimal x = ");
        scanf("%lg", &x);
       
        if (x == 0.0)
             continue;
             
        // x != 0.0 en lo que sigue...
       
        // A partir de este punto, x contiene un valor "adecuado" de tipo double.
       
        //    @ C.1: Trabajar con la parte "positiva" de x, para más comodidad
       
        int signo;       
        signo = (x >= 0.0)? +1: -1;
       
        if (signo < 0)
           x = -x;     // Ahora x es  "positivo"
       
        // x > 0.0  en lo que sigue...
       
        //    @ C.2: Buscar k tal que b^k <= x < b^{k+1}
       
        int k;
        double b_pow_k;
       
        k = 0, b_pow_k = 1.0;
       
        while (b_pow_k <= x)
            k++,   
            b_pow_k *= b;
           
        // x < b_pow_k     ---->  el siguiente while itera al menos 1 vez
           
        while (x < b_pow_k)
            // x < b_pow_k
            k--,
            b_pow_k /= b;
           
        // b_pow_k <= x < b_pow_k
       
        //     @ C.3: Extracción de dígitos en base b
       
        printf("   ---> (en base ");
        printf("%d", b);
        printf(") :    ");
       
        double xx;  // Esta variable manipulará el valor inicial de x, sin cambiar x
        xx = x;
       
        // Primer dígito:
           
        int dig;       
        dig = (int) (xx /= b_pow_k);
           
        printf("%d", signo*dig);
        printf(".");
       
        // Siguientes dígitos

        for (int Nro_Digitos = 1; Nro_Digitos <= Signif_base_b; Nro_Digitos++) {
             xx -= dig;
             xx *= b;
             dig = (int) xx;
             
             if (dig == 0)
                 for (int j = 1; j <= decimal_b - 1; j++)
                    printf("_");
             else
                 for (int p10 = ten_pow/10; dig < p10; p10 /= 10)
                    printf("_");
                   

             printf("%d", dig);
        }
       
        printf(" * ");
        printf("%d", b);
        printf("^");
        printf("%d", k);
        printf("\n\n");
               
    } while(x != 0.0);
   
    // x == 0
   
    printf("\n");
    printf("Con la elección x = 0 este programa termina.\n");
    printf("Fin del programa.\n");
   
    system("PAUSE");   
    return 0;
}


08 Octubre, 2013, 06:26 am
Respuesta #3

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
Vamos a corregir algunos defectos en los cálculos.

Desde el punto de vista matemática, nuestro método es "perfecto". Hace lo siguiente:
   
\( \bullet \) Dado un número positivo x entre 0 y 1, lo multiplicamos por la base b, y tomamos la parte entera de esto.
\( \bullet \) El resultado de esa operación es el primer dígito de x.
\( \bullet \) Ahora restamos este dígito, y lo que nos queda es el "nuevo" valor de x a considerar, y repetimos el procedimiento anterior.

Un ejemplo sencillo muestra que nuestro programa falla con ese procedimiento.

Ejecutemos el programa con b = 10 (la base decimal), y pongamos el número x = 8.88888888.

Dado que hemos elegido una precisión de 10 dígitos decimales, y dado que estamos en la base 10, no parece que este ejemplo vaya aa tener problema alguno.
El programa tendría que darnos como resultado el mismo número: 8.88888888 . 10^0.

Pero en realidad eso no ocurre, y obtenemos dígitos inexactos hacia el final.

El problema principal es que la computadora no tiene precisión infinita.
Al repetir varias iteraciones que involucran cálculos con números de punto flotante, se introducen imprecisiones en los cálculos.
Estas imprecisiones en general no son de temer, pero notemos que hay un paso en el algoritmo en que tomamos el valor de la variable xx, de punto flotante, y la truncamos al entero menor.

Supongamos que tenemos en algún paso del algoritmo que xx == 2.99999997. Ese número es "casi" 3. Pero al truncarlo a int nos da el dígito 2.
Si en los pasos anteriores hemos venido acumulando errores, puede que el valor de xx esté equivocado, y que matemáticamente el valor exacto en ese paso debiera ser, digamos xx == 3.00000001.
Así, matemáticamente, al truncar, obtendríamos el dígito 3, pero nuestro programa nos generaría un dígito 2, que está equivocado.

La cuestión aquí es que, mientras que la diferencia entre 3.00000001 y 2.99999997 es muy pequeña, al truncar cada número la diferencia es de 3 - 2 == 1, que es muy grande.

Peor todavía, es un inconveniente que se presenta con mucha frecuencia, así que no podemos hacernos los tontos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Cómo prevenimos este defecto?

Bueno, dado que estamos considerando una precisión de 10 decimales, esto quiere decir que, en nuestros programas, vamos a considerar que aquellos números que difieren de 1.0 en menos que 1e-9, son indistinguibles.

Vamos a llamar Double_Epsilon a ese número 1e-9.

Cuando tenemos cualquier otro número double, necesitamos reescalar este "épsilon".
Básicamente, diremos que xx es un valor double tal que su inmediato valor double "que consideramos diferente" es:

xx * (1.0 + Double_Epsilon)

Ahora bien, si xx está muy cerca de un valor entero, puede haber errores después de su truncamiento a int.

Una manera de detectar esta situación es comparando lo que sucede al truncar xx a int y luego truncando xx * (1.0 + Double_Epsilon) a int.
Si la diferencia entre las dos truncaciones es 1, quiere decir que xx estaba muy cerca de un número entero, y que muy posiblemente se esté subestimando su valor.
En ese caso, lo corregimos.

La corrección que haremos será en el mismísimo valor de xx, multiplicándolo por (1.0 + Double_Epsilon).
Esto no sólo corregirá el cálculo del dígito en la iteración presente, sino que dejará la corrección "ya puesta" para las iteraciones siguientes.

Todo esto está dicho informalmente, y necesita una prueba de rigor matemático, para estimar con precisión la magnitud de los errores, y realmente demostrar que esta metodología da buenos resultados.


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

En algún lugar al principio de la subsección C.3, declararemos esta variable Double_Epsilon, así:
   
            double Double_Epsilon;   // Coeficiente que mide errores de precisión
        Double_Epsilon = 1e-9;  // Se inicia a 10^-9 (precisión de double que hemos asumido)


Luego, dentro del ciclo for( ; ; ), y justo antes de la sentencia que define dig como la truncación de xx a int,
hacemos la verificación acerca de la posible pérdida de precisión, y modificamos xx si es necesario, así:

             if ( (int) (xx * (1.0 + Double_Epsilon)) - (int) xx > 0)
                 // Se ha perdido precisión
                 xx *= 1.0 + Double_Epsilon;     // Ajustar xx


Lamentablemente, esto no va a alcanzar para solucionar el problema completamente.

La cuestión aquí es que xx va cambiando en cada iteración, y se va quedando sólo con los dígitos más "tardíos" del valor original de x que el usuario había elegido.
A medida que avanzamos en las iteraciones, multiplicando siempre por la base b, y restando dig, el valor de xx se va quedando cada vez con menos decimales significativos.
No es cierto que los primeros 10 decimales de xx sean significativos, sino que esta cantidad se va reduciendo en un factor aproximado de \( \log_{10} b \).

Más concretamente, se pierde más o menos 1 dígito significativo en base b por cada iteración.
Y entonces en cada nueva iteración tenemos que ir "corriendo" el valor de la variable Double_Epsilon, multiplicándolo por b.

Así que agregamos estas líneas:

             // El valor de Double_Epsilon se ajusta "después" de haberlo usado, para llevarse bien con Signif_base_b
               
             Double_Epsilon *= b; // El coeficiente de error se va ajustando con cada iteración


El resto del programa puede dejarse como está.

En particular, un cambio importante en el análisis del programa, lo hemos puesto en práctica con muy pocas líneas nuevas.

Las ideas detrás de estos cambios son bastante fuertes, y sin embargo las instrucciones resultantes en C son sencillas.
Esto muestra que programar no es sólo largarse como locos a teclear comandos, sino que se requiere una fuerte cuota de análisis.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

También haremos un cambio menor: cambiaremos la primer línea por esta otra:

        system("CHCP 28591 > NUL"); // Página de códigos adecuada al castellano

El efecto de "> NUL"[/size][/font] es que la información del comando de DOS/Windows CHCP es "redirigida" al puerto NUL, lo que equivale a decir que no se muestra información alguno.
Esto es más elegante, porque evita que aparezca el mensaje de sistema anunciado que se cambió la página de códigos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Cambiaremos la subsección B.1 para que acepta una base máxima de 32767, que es de paso el máximo valor que admitimos nosotros para int (aunque en la mayoría de computadoras actuales el rango de int lleva mucho más lejos).
Quedará así:

    //    @ B.1: Elegir base b
   
    int Significant_Digits;
    Significant_Digits = 10;
   
    int Max_Base;
    Max_Base = 32767;
   
    int b;  // Base a la que convertiremos los números decimales
   
    do {
        printf("Elija la base b = ");       
        scanf("%d", &b);
       
        if (b <= 1)
           printf("Error: la base debe ser b >= 2.\nIntente de nuevo.\n\n");
        if (b >= Max_Base) {
           printf("Error: este programa sólo acepta bases b <= ");
           printf("%d", Max_Base);
           printf(".\nIntente de nuevo.\n\n");
        }
           
    } while(b <= 1);
   
    // b >= 2 && b <= Max_Base


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Otro cambio que me parece importante hacer aquí es el de ajustar el número de dígitos significativos según cada base.
Para ello, debemos ir reduciendo el valor de Signif_Factor a medida que el valor de la base b aumenta.
Lo mantendremos consecuente con la exigencia de que haya 10 dígitos significativos en base 10.

Para otras bases, el factor a aplicar será igual al recíproco del logaritmo en base 10 de 10, aproximadamente, y más aún, redondeado hacia abajo.
¿Por qué hacia abajo?
Porque esto propiciaría menos dígitos significativos en base b, lo cual significaría menos riesgos de errores cuando va aumentando el valor de la variable Double_Epsilon.
(Esta variable puede tomar valores excesivamente grandes si no se programa con cuidado).

En realidad, en la próxima versión del programa vamos a usar una solución más elegante y segura que la de aquí.

Como ahora redondeamos hacia abajo, algunos casos del switch() van a cambiar un poquito.

Y más importante: el caso default va a considerar varios rangos de casos para las bases b mayores que 10.


    double Signif_factor;
   
    switch (b) {
        case 2:
            Signif_factor = 3.32;
            break;
        case 3:
            Signif_factor = 2.09;
            break;
        case 4:
            Signif_factor = 1.66;
            break;
        case 5:
            Signif_factor = 1.43;
            break;
        case 6:
            Signif_factor = 1.28;
            break;
        case 7:
            Signif_factor = 1.18;
             break;
       case 8:
            Signif_factor = 1.10;
            break;
        case 9:
            Signif_factor = 1.04;
            break;
        case 10:
            Signif_factor = 1.0;
            break;
        default:
            if (b <= 12)
               Signif_factor = 0.9;
            else if (b <= 17)
               Signif_factor = 0.8;
            else if (b <= 26)
               Signif_factor = 0.7;
            else if (b <= 46)
               Signif_factor = 0.6;
            else if (b <= 100)
               Signif_factor = 0.5;
            else if (b <= 316)
               Signif_factor = 0.4;
            else if (b <= 2154)
               Signif_factor = 0.3;
            else
               Signif_factor = 0.2;
    }

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

La nueva versión del programa queda así:
   
bc00.11.c

// BC 00.11

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

int main(void) {
    system("CHCP 28591 > NUL"); // Página de códigos adecuada al castellano
   
    // Sección A: Preliminares
   
    printf("BaseConv (Base Converter) v. 0.11\n\n");
    printf("Este programa convierte números x de base decimal a base b.\n\n");
    printf("Se le pedirá al usuario que elija una base b para trabajar.\n");
    printf("Luego, se le pedirá al usuario que escriba números x en base decimal, para ser convertidos a base b.\n");
    printf("Para terminar, ingrese el número x = 0\n\n");
   
    // Sección B: Base b, dígitos significativos en base b
   
    //    @ B.1: Elegir base b
   
    int Significant_Digits;
    Significant_Digits = 10;
   
    int Max_Base;
    Max_Base = 32767;
   
    int b;  // Base a la que convertiremos los números decimales
   
    do {
        printf("Elija la base b = ");       
        scanf("%d", &b);
       
        if (b <= 1)
           printf("Error: la base debe ser b >= 2.\nIntente de nuevo.\n\n");
        if (b >= Max_Base) {
           printf("Error: este programa sólo acepta bases b <= ");
           printf("%d", Max_Base);
           printf(".\nIntente de nuevo.\n\n");
        }
           
    } while(b <= 1);
   
    // b >= 2 && b <= Max_Base
   
    //    @ B.2: Dígitos significativos en base b
   
    double Signif_factor;
   
    switch (b) {
        case 2:
            Signif_factor = 3.32;
            break;
        case 3:
            Signif_factor = 2.09;
            break;
        case 4:
            Signif_factor = 1.66;
            break;
        case 5:
            Signif_factor = 1.43;
            break;
        case 6:
            Signif_factor = 1.28;
            break;
        case 7:
            Signif_factor = 1.18;
             break;
       case 8:
            Signif_factor = 1.10;
            break;
        case 9:
            Signif_factor = 1.04;
            break;
        case 10:
            Signif_factor = 1.0;
            break;
        default:
            if (b <= 12)
               Signif_factor = 0.9;
            else if (b <= 17)
               Signif_factor = 0.8;
            else if (b <= 26)
               Signif_factor = 0.7;
            else if (b <= 46)
               Signif_factor = 0.6;
            else if (b <= 100)
               Signif_factor = 0.5;
            else if (b <= 316)
               Signif_factor = 0.4;
            else if (b <= 2154)
               Signif_factor = 0.3;
            else
               Signif_factor = 0.2;
    }
   
    // Signif_factor es aproximadamente log 10/log b, para b < 10.
   
    double Signif_base_b;
    Signif_base_b = Signif_factor * Significant_Digits;
   
    printf("\n");
    printf("Los siguientes resultados se mostrarán hasta con ");
    printf("%d", (int) Signif_base_b);
    printf(" dígitos significativos.\n\n");
   
    //    @ B.3: Dígitos decimales por cada dígito en base b.
   
    int decimal_b;   // Cantidad de dígitos decimales para cada dígito en base b
    int ten_pow;
   
    for(decimal_b = 0, ten_pow = 1; ten_pow < b; decimal_b++, ten_pow *= 10)
         ;
         
    // decimal_b == [log_10 (b-1)] + 1 == {dígitos de (b-1) en base 10}
    // ten_pow == 10^{decimal_b}
       
    printf("Cada dígito en base ");
    printf("%d", b);
    printf(" será representado por ");
    printf("%d", decimal_b);
    printf(" dígito(s) en base diez.\n\n");
         
    // ------------------------------------------------------------
       
    // Sección C: Pasar números decimales x a base b
   
    double x;  /* Esta variable contendrá el número que deseamos expresar en base b */
   
    do {
        printf("Escriba un número decimal x = ");
        scanf("%lg", &x);
       
        if (x == 0.0)
             continue;
             
        // x != 0.0 en lo que sigue...
       
       // A partir de este punto, x contiene un valor "adecuado" de tipo double.
       
        //    @ C.1: Trabajar con la parte "positiva" de x, para más comodidad
       
        int signo;       
        signo = (x >= 0.0)? +1: -1;
       
        if (signo < 0)
           x = -x;     // Ahora x es  "positivo"
       
        // x > 0.0  en lo que sigue...
       
        //    @ C.2: Buscar k tal que b^k <= x < b^{k+1}
       
        int k;
        double b_pow_k;
       
        k = 0, b_pow_k = 1.0;
       
        while (b_pow_k <= x)
            k++,   
            b_pow_k *= b;
           
        // x < b_pow_k     ---->  el siguiente while itera al menos 1 vez
           
        while (x < b_pow_k)
            // x < b_pow_k
            k--,
            b_pow_k /= b;
           
        // b_pow_k <= x < b_pow_k
       
        //     @ C.3: Extracción de dígitos en base b
       
        printf("   ---> (en base ");
        printf("%d", b);
        printf(") :    ");
       
         double xx;  // Esta variable manipulará el valor inicial de x, sin cambiar x
        xx = x;
       
        double Double_Epsilon;   // Coeficiente que mide errores de precisión
        Double_Epsilon = 1e-9;  // Se inicia a 10^-9 (precisión de double que hemos asumido)
       
        // Primer dígito:
        xx /= b_pow_k;
       
        int dig;       
        dig = (int) xx;
           
        printf("%d", signo*dig);
        printf(".");
       
        // Siguientes dígitos
       
        for (int Nro_Digitos = 1; Nro_Digitos < Signif_base_b; Nro_Digitos++) {
             xx -= dig;  // En este punto siempre se cumple que 0 <= x < 1
             xx *= b;    // Aquí puede perderse precisión en la parte entera
             
             if ( (int) (xx * (1.0 + Double_Epsilon)) - (int) xx > 0)
                 // Se ha perdido precisión
                 xx *= 1.0 + Double_Epsilon;     // Ajustar xx
                 
             // El valor de Double_Epsilon se ajusta "después" de haberlo usado, para llevarse bien con Signif_base_b
               
             Double_Epsilon *= b; // El coeficiente de error se va ajustando con cada iteración
             
             dig = (int) xx;
             
             if (dig == 0)
                 for (int j = 1; j <= decimal_b - 1; j++)
                    printf("_");
             else
                 for (int p10 = ten_pow/10; dig < p10; p10 /= 10)
                    printf("_");
                   

             printf("%d", dig);
        }
       
        printf(" * ");
        printf("%d", b);
        printf("^");
        printf("%d", k);
        printf("\n\n");
               
    } while(x != 0.0);
   
    // x == 0
   
    printf("\n");
    printf("Con la elección x = 0 este programa termina.\n");
    printf("Fin del programa.\n");
   
    system("PAUSE");   
    return 0;
}