La era de la informatica

Sunday, March 25, 2007

Solucion Problema 4

Hola de nuevo. Os traigo la solución al problema 4. Era muy sencillo, pero hay que saber bien que operadores tienen preferencia y como actua cada uno de ellos. En todos los ejemplos usamos un puntero e incrementamos 1, pero el resultado de las operaciones puede ser muy diferente.

Lo primero que os pedía era organizar las instrucciones y agruparlas en grupos de instrucciones que produzcan el mismo resultado. Luego explicar que hace cada grupo de instrucciones. Por tanto lo primero que voy a hacer es agruparlas:

Primer grupo:
x = *(p+1);
Segundo grupo:
x = *p+1;
x= (*p)+1;
Tercer grupo:
x = (*p)++;
Cuarto grupo:
x = *(p)++;
x = *p++;
x = *(p++);
x = (*p++);
Quinto grupo:
x = *++p;


Vamos a ver que hace cada grupo. Para ello voy a establecer unas condiciones iniciales.
p es un puntero que apunta a un array de 2 posiciones [1,7]. Tras la instrucción vamos a ver cual es el contenido de x y el contenido de p con la instruccion printf("x=%d,p=%d\n",x,*p);

El primer grupo da como resultado:
x=7, p=1
Es decir, que primero aumentamos el puntero p a la segunda posicion, el contenido lo asignamos a x y p no se modifica.
El segundo grupo da como resultado:
x=2, p=1
Es decir, que a x se le asigna lo que apunte p mas 1, y p no se modifica.
El tercer grupo da como resultado:
x=1, p=2
Es decir, que a x se le asigna lo que vale p y luego se suma al contenido de p un 1.
El cuarto grupo da como resultado:
x=1, p=7
Es decir, se asigna a x lo que vale p y luego se aumenta el puntero p en una posicion
el quinto grupo da como resultado:
x=7, p=7
Es decir, que se incrementa en 1 la posicion de p y se asigna a x el valor.

Como veis las operaciones eran parecidas, pero producen resultados dispares. La moraleja de esto es que lo mas recomendable en C es usar siempre un convenio, utilizar siempre el mismo tipo de instrucciones para no dar lugar a confusiones. Las mas comunes suelen ser *p++ y (*p)++. La primera para recorrer un array y la segunda para guardar el valor e incrementar el valor del contenido de p.

Hasta la proxima

Sunday, March 18, 2007

Problema 4: Punteros y precedencia de operadores

Hola, este es el problema 4. Es muy sencillo como todos los anteriores. Se trata de agrupar las siguientes instrucciones entre aquellas que hagan exactamente lo mismo, y despues para cada grupo explicar que es exactamente lo que se esta haciendo. Estas son las instrucciones:

x = *(p+1);
x = *(p)++;
x = (*p) + 1 ;
x = (*p)++;
x = *p+1;
x = (*p++);
x = *(p++);
x = *++p;
x = *p++;

Se trata de nuevo de no usar un compilador sino intentar hacerlo a mano. Mandad vuestras respuestas a mi correo electronico.

Hasta la proxima.

Saturday, March 17, 2007

Solucion Problema 3

Hola de nuevo. Esta es la solución al problema 3, que trataba sobre ANSI C. La pregunta era, sobre este cógido ¿Que numero minimo de cambios hay que hacer para que compile con codigo ANSI, que funcione y la salida por pantalla sea logica?

#include <stdio.h>

void main(); {
int *p, x=5, y; // init
y = *(p = &x) +10;
int z;
flip-sign(p);
printf("x=%d,y=%d,p=%d\n",x,y,p);
}
flip-sign(int *n) {*n = -(*n)}

Empezemos linea por linea a ver los errores que encontramos y al final os pondre el codigo definitivo.

En la primera linea no hay errores, se trata de incluir la libreria estandar de entrada y salida.
En la segunda linea encontramos el primer error.
void main(); {
El ; sobra, por tanto deberia ser
void main() {
la tercera linea contiene un error en los comentarios:
int *p,x=5,y; // init
// no es la forma correcta de comentar codigo en ANSI, los comentarios deben ir entre los simbolos /* y */ Por tanto quedaria asi:
int *p,x=5,y; /* init */
La cuarta linea no contiene errores, al puntero p se le hace apuntar a la variable x, y luego se suma 10 con la "dereferencia" (el valor donde apunta) de p y el resultado se almacena en y.
La quinta linea es erronea, ya que solo se pueden declarar variables al principio del bloque de la funcion. Por tanto esta linea la deberiamos pasar arriba, y pasaria a ser la cuarta linea del codigo y la cuarta pasara a ser la quinta. Si nos fijamos bien, la variable z no se usa nunca en el codigo, por lo que esta linea de codigo puede ser eliminada, es otra opcion.
La sexta linea contiene un error en en nombre de la funcion:
flip-sign(p);
Ya que el simbolo - indica resta, por tanto esta linea lo que hace es restar de la variable flip el resultado de la funcion sign, esto es incorrecto ya que ni la variable flip ni la funcion sign estan declaradas, para corregir esto hay que cambiar el nombre de la funcion, por ejemplo podriamos poner:
flip_sign(p);
La septima linea no contiene errores, pero hace algo que no tiene sentido, esto es, imprimir en pantalla el valor de un puntero, es algo que no nos interesa por lo que cambiariamos la linea por esta otra:
printf("x=%d,y=%d,p=%d\n",x,y,*p);
La octava linea es correcta.
La novena linea de codigo tiene 2 errores, una es el nombre de la funcion como se ha explicado antes, y otra es la sentencia dentro de la funcion debe terminarse en ; por tanto la linea correcta seria:
flip_sign(int *n) {*n = -(*n);}

Por tanto contando los cambios, son 7 los cambios minimos para que el codigo sea ANSI y semanticamente correcto. El codigo final sera:

#include <stdio.h>

void main() {
int *p, x=5, y; /* init */
int z;
y = *(p = &x) +10;
flip_sign(p);
printf("x=%d,y=%d,p=%d\n",x,y,*p);
}
flip_sign(int *n) {*n = -(*n);}

Para comprobarlo podeis usar el gcc con las opciones "-ansi -pedantic" que hacen que se compile el codigo teniendo en cuenta el estandar ANSI mas estricto.

Hasta la proxima.

Wednesday, March 14, 2007

Redondeo 2

Hola de nuevo. Voy a poner la sencilla solución al problema del redondeo. En realidad en su comentario de la anterior entrada, el cremero lo respondio correctamente pero de forma muy general. Su solucion fue:

Redondeo(x) = TRUNC(x + DEC(x))

Donde DEC se queda con la parte decimal de x y TRUNC con la parte entera. Esto es correcto, pero deja abierta la incognita de encontrar una forma adecuada de quedarnos con la parte decimal y con la parte entera. Ademas, esto realmente no necesita de llamadas a funciones, en realidad se puede realizar de forma mas sencilla observando que solo nos preocupa el redondeo teniendo en cuenta el primer decimal y solo si su valor es 5 o no. Como ya dije si el valor es >=5 se redonde hacia "arriba" (el menor entero mayor o igual que x) y si es <5 se redondea hacia "abajo" (el mayor entero menor o igual que x).

Para hacer esto podemos fijarnos en las funciones matematicas mas parecidas a la descripcion dada anteriormente. La función "suelo" (floor, en ingles) coge el mayor entero menor o igual que x (redondeo hacia abajo). La función "techo" (ceiling, en ingles) devuelve el menor entero mayor o igual que x (redondeo hacia arriba)

Dicho esto y puesto que solo nos preocupa que el primer decimal sea mayor o menor que 5, podemos usar una de estas funciones, suelo o techo. La pregunta sería como hacerlo usando bien el suelo o el techo de un valor real.

Pues la forma mas sencilla sería usar la función suelo(x), por ejemplo, de esta forma:

Redondeo(x) = Suelo(x + 0.5)

Siguiendo la notación dada por El Cremero anteriormente. nos queda por tanto definir esa función Suelo, ya que puede haber muchas implementaciones... o tal vez no? En un lenguage como C se puede hacer de la forma más sencilla posible con un "casting", ya que nos interesa un número entero podemos hacer un casting a un valor entero, de forma que en C lo más sencillo, teniendo que x es un número real (float), sería hacer lo siguiente:

z = (int) (x + 0.5);

Donde z sería una variable entera que guardaría el valor del redondeo. Esto era lo más sencillo, no hacía falta complicarse como veis. Pero tiene limitaciones, como todo en informática, debido a la precisión de los números. Para ambos tipos "float" e "int" se utilizan 32 bits, pero la forma de almacenar el número es distinta, pudiendo con un "float" especificar un valor entero mayor que lo que se puede con un "int", y al hacer el casting habría un desbordamiento y el valor final no sería correcto. Esto siempre hay que tenerlo en cuenta al hacer "castings" a tipos de datos distintos al que realmente se almacena, por tanto no es recomendable hacerlo a menos que se sepa con certeza que el resultado va a ser correcto.

Si quisieramos hacer un redondeo sencillo pero sin las limitaciones del "casting" se puede hacer teniendo en cuenta la notación en coma flotante y haciendo operaciones con bits, pero eso ya sería mas complicado y dependería del tipo y el lenguage de programación que se usase.

Sunday, March 11, 2007

Redondeo

Hola, quiero lanzar una pregunta al aire. ¿Cual es la función mas sencilla para redondear un número real a su número entero mas cercano? Es decir, si el primer decimal es <5 se redondea hacia abajo y si es >=5 hacia arriba. Meter comentarios con vuestras sugerencias o mandarlas al email. Dentro de unos días pondré mi sugerencia si nadie pone la misma...

Hasta la proxima.

Saturday, March 03, 2007

Problema 3: ANSI C

(Editado: Por petición popular he decidido dar una semana más para resolver este problema. Animaos que es muy sencillo. Teneis hasta el 18/03/2007 para mandar vuestras respuestas)

Hola de nuevo. Este es el problema 3 del blog, como veis intento mantener el ritmo de 1 problema por semana aunque no se si va a ser posible siempre. Vamos a ver el siguiente codigo:

#include <stdio.h>

void main(); {
int *p, x=5, y; // init
y = *(p = &x) +10;
int z;
flip-sign(p);
printf("x=%d,y=%d,p=%d\n",x,y,p);
}
flip-sign(int *n) {*n = -(*n)}


Este codigo tiene ciertos errores sintacticos y semanticos, y lo que se pretende es que compile y funcione. El codigo final debe ser ANSI C. La pregunta entonces es: ¿Que numero minmo de cambio hay que hacer para que compile con codigo ANSI, que funcione y la salida por pantalla sea logica? Mandar tambien el codigo definitvo que os ha quedado.

Lo interesante de esto es hacerlo a mano, sin usar un compilador. Os animo por tanto a no usar el compilador. Dentro de una semana publicare la respuesta.

Hasta la proxima

Solucion problema 2

Hola de nuevo. Ya ha terminado el plazo de 1 semana que di para resolver este segundo problema. Como habreis comprobado es algo trivial. Ambos codigos hacen algo parecido, esto es, mantener 2 hilos ejecutandose a la vez, uno de ellos se ejecuta primero, modificando una variable global y luego la imprime por pantalla. El otro hilo espera a que el primero termine y luego imprime la misma variable global por pantalla.

Esta es la salida del primer codigo por pantalla:
value = 5
value = 0

Esta es la salida del segundo codigo por pantalla:
value = 5
value = 5

La diferencia entre el primer codigo y el segundo es la llamada que se hace en el primer codigo a la función fork().

Como sabeis la llamada a la función fork crea un proceso "hijo" del proceso que se está ejecutando, copiando todo su espacio de direcciones en un nuevo lugar, de forma que con el mismo codigo se crean 2 procesos con ejecuciones totalmente independientes y con un espacio en memoria igual pero independiente.

En el primer codigo la creacion del hilo se hace dentro del proceso "hijo". Mientras el proceso padre espera a que el hijo termine. El hilo del proceso hijo modifica la variable global llamada "value", la imprime por pantalla y termina. Como esto se hace dentro del proceso hijo, aunque la variable global sea la misma (el espacio de memoria es igual), este cambio no se ve reflejado en el proceso padre (los espacios de memoria son iguales pero independientes), ya que se trata solo de una copia.

En el segundo codigo se ejecuta un solo proceso. En este proceso se crea un hilo que hace lo mismo que en el primer codigo, modificar la variable global "value" e imprimirla por pantalla. Ya que no llamamos a fork, la API de hilos no crea un espacio de memoria independiente, sino que permite que en el mismo proceso haya 2 hios de ejecucion sobre el mismo codigo y la misma memoria, es decir compartiendo absolutamente todo el espacio de memoria. Por tanto al modificar el hilo la variable global este cambio es "visto" por todos los hilos del proceso en ejecucion. Por tanto en el segundo codigo ambos hilos de ejecucion imprimen por pantalla el nuevo valor de la variable "value".

Como veis este problema era muy sencillo, solo hacia falta saber que con fork se copia el espacio de direcciones de un proceso y se crea un proceso hijo con un espacio de direcciones igual pero independiente. Mientras que al crear simplemente hilos se crean 2 ejecuciones independientes pero compartiendo el mismo espacio de direcciones ambas. Lo cual permite que se compartan recursos facilmente entre varios procesos, pero esto se debe de hacer con cautela, ya que el acceso simultaneo de varios procesos a la misma zona de memoria puede acabar en resultados indeseados.

Hasta la proxima