¿Cómo os trata el verano? Espero que estés bien fresquitos y a la sombra :-)
Yo por mi parte estoy bastante contento, ya que todavía estamos a Julio y este año ya es el tercero con más entradas en el blog! Me alegra ver que poco a poco he ido retomando la escritura, siendo que incluso en 2018 estuve todo el año sin escribir...
En fin, vamos a entrar en materia... vamos a seguir con la identificación de puntos críticos en la arquitectura BigInteger para realizar la paralelizazión.
En esta entrada, trataremos la resta.
¡Vamos allá!
Como ya vimos, la resta de BigInteger tenía un funcionamiento muy similar a la suma, pero estaba diseñada de manera que siempre contábamos con "a >= b".
Por eso, veréis que la identificación de puntos críticos es menor que en la suma, pero el beneficio es el mismo.
Todo el código relacionado con la resta, es el siguiente
Get Raw
0001/*
0002 * Función pSub. Usar para restar dos números.
0003 *
0004 * Simula la operación a = a - b.
0005 *
0006 * Si len(a) < len(b), se intercambian los valores
0007 */
0008static void pSub(void* va, void *vb){
0009 int sig;
0010 int comp;
0011 struct BigInteger* a =
0012 (struct BigInteger*)malloc(sizeof(struct BigInteger));
0013 struct BigInteger* b =
0014 (struct BigInteger*)malloc(sizeof(struct BigInteger));
0015
0016 if (a == NULL || b == NULL)
0017 showError(7);
0018
0019 memcpy(a, va, sizeof(struct BigInteger));
0020 memcpy(b, vb, sizeof(struct BigInteger));
0021
0022 hardEquals(a, b, &comp);
0023 sig = signum(a->n[a->count], b->n[b->count]);
0024
0025 //si ambos son negativos, comp = 1 significa a < b
0026 if((comp == 2 && sig < 11) || (comp == 1 && sig == 11)){
0027 pSub(b, a);
0028
0029 //cambiamos el signo
0030 b->n[b->count] *= -1;
0031
0032 //reasignamos
0033 memcpy(a, b, sizeof(struct BigInteger));
0034 }else if(comp == 0){
0035 a->n[0] = 0;
0036 a->count = 0;
0037 }else{
0038 //normalizamos los signos
0039 if(sig == 1)
0040 b->n[b->count] *= -1;
0041 else if(sig == 10)
0042 a->n[a->count] *= -1;
0043 else if(sig == 11){
0044 a->n[a->count] *= -1;
0045 b->n[b->count] *= -1;
0046 }
0047
0048 //si tienen el mismo signo, se resta, sino se suma
0049 if(sig == 0 || sig == 11)
0050 subtract(a, b);
0051 else
0052 pAdd(a, b);
0053
0054 if(sig == 10 || sig == 11)
0055 //en estos casos, siempre se le va la vuelta al signo
0056 a->n[a->count] *= -1;
0057 }
0058
0059 //ajustamos el resultado
0060 memcpy(va, a, sizeof(struct BigInteger));
0061
0062 //liberamos memoria
0063 free(a);
0064 free(b);
0065}
0066
0067/*
0068 * Función sub. Usar para restar dos números.
0069 *
0070 * Simula la operación a = a - b.
0071 *
0072 * Si len(a) < len(b), se intercambian los valores
0073 */
0074void sub(void *va, void *vb) {
0075 //validamos los punteros antes de tratarlos
0076 validateBI(va);
0077 validateBI(vb);
0078
0079 //delegamos en la función estática
0080 pSub(va, vb);
0081}
0082
0083/*
0084 * Función subrtact.
0085 *
0086 * Realiza la resta.
0087 */
0088static void subtract(void *va, void *vb){
0089 int i = 0;
0090 int accType = 0;
0091 struct BigInteger* a =
0092 (struct BigInteger*)malloc(sizeof(struct BigInteger));
0093 struct BigInteger* b =
0094 (struct BigInteger*)malloc(sizeof(struct BigInteger));
0095
0096 if (a == NULL || b == NULL)
0097 showError(8);
0098
0099 memcpy(a, va, sizeof(struct BigInteger));
0100 memcpy(b, vb, sizeof(struct BigInteger));
0101
0102 //restamos los dígitos comunes
0103 for(;i <= b->count;i++)
0104 a->n[i] -= b->n[i];
0105
0106 //si el último dígito es negativo
0107 if(a->n[a->count] < 0)
0108 accType = 1;
0109
0110 carrySub(a, accType);
0111
0112 //movemos el resultado
0113 memcpy(va, a, sizeof(struct BigInteger));
0114
0115 //liberamos memoria
0116 free(a);
0117 free(b);
0118}
0119
0120/*
0121 * Función carrySub.
0122 *
0123 * Gestiona el acarreo de la resta. Si carryType = 0, el acarreo
0124 * se gestiona como a = 10 + a;
0125 * sino, se invierte el signo (excepto al último dígito)
0126 */
0127static void carrySub(void *va, int carryType){
0128 int i = 0;
0129 int acc = 0;
0130 struct BigInteger* a =
0131 (struct BigInteger*)malloc(sizeof(struct BigInteger));
0132
0133 if (a == NULL)
0134 showError(9);
0135
0136 memcpy(a, va, sizeof(struct BigInteger));
0137
0138 if(carryType == 0){
0139 for(;i <= a->count; i++){
0140 //restamos el acarreo al número
0141 a->n[i] -= acc;
0142
0143 if(a->n[i] < 0){
0144 //normalizamos el número
0145 a->n[i] += 10;
0146 acc = 1;
0147 }else
0148 acc = 0;
0149 }
0150 }else{
0151 //en esta opción, no es necesario pasar una segunda vez por acarreos.
0152 for(i = 0;i < a->count;i++)
0153 if(a->n[i] < 0)
0154 //normalizamos el número
0155 a->n[i] = a->n[i] * -1;
0156 }
0157
0158 //contamos de nuevo los dígitos
0159 recount(a);
0160
0161 //ajustamos el resultado
0162 memcpy(va, a, sizeof(struct BigInteger));
0163
0164 //liberamos memoria
0165 free(a);
0166}
0167
0168/*
0169 * Función recount.
0170 *
0171 * Recuenta las cifras, para ver si hay que disminuir el conteo.
0172 */
0173static void recount(void *va){
0174 struct BigInteger* a =
0175 (struct BigInteger*)malloc(sizeof(struct BigInteger));
0176
0177 if (a == NULL)
0178 showError(10);
0179
0180 memcpy(a, va, sizeof(struct BigInteger));
0181
0182 while(a->n[a->count--] == 0);
0183
0184 ++a->count;
0185
0186 if(a->count < 0)
0187 a->count = 0;
0188
0189 //ajustamos el resultado
0190 memcpy(va, a, sizeof(struct BigInteger));
0191
0192 //liberamos memoria
0193 free(a);
0194}
Como se puede ver, en este caso hay alguna que otra función más (recount, por ejemplo), y como ya anticipamos en su día, la comparación la vamos a obviar (ya que tiene su propia entrada reservada).
La distribución de puntos críticos sería la siguiente:
Seguidamente, podemos obtener el signum y comparar los operandos al mismo tiempo, para, tras pasar el semáforo, normalizar los datos.
Una vez llegados a este punto, haríamos la resta de dígitos comunes (no hace falta revisar los no comunes ya que "a >= b"), y pasados el semáforo, haríamos el acarreo y recuento de dígitos.
A tener en cuenta, el acarreo lo hemos marcado de manera distinta ya que en ciertas ocasiones sí se puede paralelizar (cuando carryType == 1), por lo que será decisión de la función "carrySub" aplicar la lógica necesaria y los semáforos pertinentes.
Con estos cambios, el coste de la resta se reduciría significativamente, ya que podemos asumir que la resta dígito a dígito tendrá el coste de la resta de un dígito; también se reduce el coste de validación, de momento, a la mitad (y ya veremos el análisis de su función), y podemos obviar el coste de calcular el signum (ya que la comparación tendrá un coste mayor, aunque el coste del cálculo del signum es marginal).
Y con esto nos despedimos por ahora. En próximas entregas, pasamos a las operaciones complejas (multiplicación y división), ¡que serán súper entretenidas de analizar!
¡Nos vemos!
No hay comentarios:
Publicar un comentario