jueves, 25 de agosto de 2016

Solver y el método de multiplicación egipcio

En la entrada del blog anterior vimos/aprendimos el método ruso de multiplicación.
Hoy toca el método egipcio sobre el que se basaba aquel.

Este antiguo método egipcio, donde sólo se requiere saber sumar consiste en que para multiplicar A x B:
1-En la primera columna se escribe la serie: f(n)=2n , partiendo desde n=0 continuando mientras 2n <A. Los primeros números de la serie quedarían de la siguiente manera: 1,2,4,8...
2-En la segunda columna se escribe la serie: f(n)=2nxB, o bien f(n)=2xf(n-1) siendo f(0)=B. El resultado es el mismo y obtendremos la siguiente serie: B, 2xB, 4xB...
3-En una tercera columna se marcan las cifras de la primera columna cuya suma resulte igual a A (de mayor a menor)
4-El resultado es la suma de las cifras marcadas de la segunda columna.

Puedes leer algo más en Wikipedia


Partimos de la siguiente plantilla:

Solver y el método de multiplicación egipcio



La disposición de las fórmulas en nuestro modelo.
En C2 y en D2 el primer y segundo valor de nuestro producto.

En B4:B15 la primera parte del cálculo, cumpliendo la condición dada, para lo que insertamos la fórmula:
=SI(POTENCIA(2;A4)<$D$2;POTENCIA(2;A4);"")

En C4:C15 incorporamos como primer valor en C4 el segundo importe a multiplicar (=313) y en C5 y siguientes multiplicamos por dos el anterior:
=+C4*2


En el rango D4:D15 no añadimos nada, ya que es en este rango donde trabajará Solver.
Sí añadimos en D16 la fórmula:
=SUMAPRODUCTO(B4:B15;D4:D15)
que será la celda objetivo de nuestra herramienta Solver.
Con Solver conseguiremos que este rango D4:D15 se complete con valores 0 y 1 hasta que D16 sume el valor del segundo importe (=313).Con lo que cumpliríamos el tercer paso de los indicados al inicio del post.

Antes de configurar Solver terminamos nuestra plantilla, rellenando el rango E4:E15 con el producto:
=C4*D4
sumando todo ello en la celda E16.
Este resultado final corresponde a la suma de los importes de la segunda columna para aquellas cantidades de la primera columna cuya suma resulte igual al primer importe.


Con el método claro (más o menos) y la plantilla montada, estamos listos para configurar Solver; así pues accedemos a la Ficha Datos > Análisis > Solver:

Solver y el método de multiplicación egipcio



De manera muy sencilla definimos como celda objetivo la celda D16, con el valor 313 (el importe del segundo importe del producto).
Las celdas cambiantes que deseamos se modifiquen serán las del rango D4:D15.
Definiendo la restricción de este mismo rango D4:D15 como números binarios.


Al resolver nuestra configuración obtendremos la confirmación:

Solver y el método de multiplicación egipcio


Y el resultado plasmado en la hoja de cálculo, con el que conseguimos el producto de 13 x 313 buscado:

Solver y el método de multiplicación egipcio



Notemos como los valores de la primera columna que corresponden con los valores 1 del rango D4:D15 suman los 313 necesarios(=256+32+16+8+1)... cumpliendo la tercera condición del método.

martes, 23 de agosto de 2016

VBA: Multiplicación por duplicación - la multiplicación rusa.

Quizá este método de multiplicación rusa (o por duplicación) te suene a 'chino'...
pero es tan antiguo como la cultura de los faraones.
Es un método de multiplicación basado en la suma y división por dos.
Puedes echar un vistazo en Wikipedia.

Pero se resume básicamente en:
1-Escribir los números (A y B) que se desea multiplicar en la parte superior de sendas columnas.
2-Dividir A entre 2, sucesivamente, ignorando el resto, hasta llegar a la unidad. Escribir los resultados en la columna A.
3-Multiplicar B por 2 tantas veces como veces se ha dividido A entre 2. Escribir los resultados sucesivos en la columna B.
4-Sumar todos los números de la columna B que estén al lado de un número impar de la columna A. Éste es el resultado.


Veamos un ejemplo del desarrollo en Excel.
Tenemos dos valores a multiplicar en C2:=13 y en D2:=313
En el primer rango C5:C14 hemos añadido el valor de C2 (=13) y los siguientes (C6 y siguientes) la fórmula:
=ENTERO(C5/2)
con la que conseguimos los valores enteros necesarios.


En la columna de al lado, rango D5:D14, completamos con:
En D5 recuperamos el valor de D2 (=313), y en D6 y siguientes añadimos la fórmula:
=SI(C6>0;D5*2;0)
obteniendo el doble del valor anterior...


Finalmente en el rango E5:E14 incorporamos el condicional:
=SI(ES.IMPAR(C5);D5;0)
que nos retornará los importes necesarios a sumar, sólo para los valores impares del primer rango(C5:C14).
Acabamos sumando en E15 los importes del rango superior:
=SUMA(E5:E14)

VBA: Multiplicación por duplicación - la multiplicación rusa.



Obviamente, por suerte, no tendremos que hacer estas operaciones tan laboriosas para conseguir nuestro producto... pero sirve de introducción para el ejercicio siguiente, que consiste en replicar el método en VBA para Excel.
Construiremos una UDF, un procedimiento Function, como el que sigue:

Function MultRusa(pdto1 As Integer, pdto2 As Double) As Double
'definimos dos matrices
Dim Valores1() As Variant
Dim Valores2() As Variant
'calculamos el número de lineas a trabajar
N = 0
Do
    'tantas lineas necesarias
    'hasta que superemso el valor del primer valor a multiplicar
    Eltos = 2 ^ N
    N = N + 1
Loop Until Eltos > pdto1

'redefinimos la dimensión de nuestras matrices
'ajustadas al númer de líneas necesarias
ReDim Valores1(1 To N) As Variant
ReDim Valores2(1 To N) As Variant

'asignamos valores a las matrices
Valores1(1) = Int(pdto1)    'el primer rango será siempre entero
Valores2(1) = pdto2
'completamos las matrices con el siguiente bucle
N = 2
Do
    'a partir del valor anterior calculamos el siguiente dividiendo por dos
    Valores1(N) = Int(Valores1(N - 1) / 2)
    'a partir del valor anterior calculamos el siguiente multiplicando por dos
    Valores2(N) = Valores2(N - 1) * 2
    N = N + 1
Loop Until Valores1(N - 1) = 1

'recorremos las matrices
Dim dato As Variant
For Each dato In Valores1
    x = x + 1
    If (dato Mod 2) <> 0 Then
        'acumulamos los importes de la segunda matriz
        'solo cuando el valor de la primera sea impar
        Suma = Suma + Valores2(x)
    End If
Next dato

'devolvemos el sumando a la celda
MultRusa = Suma
End Function



El resultado, claro está, es el esperado:

VBA: Multiplicación por duplicación - la multiplicación rusa.



Lo interesante de este post, me atrevo a decir, es el uso de las Arrays y de los loops tipo DO...LOOP y FOR EACH..NEXT, empleados para la construcción de los rangos necesarios (divididos y multiplicados por dos), así como para obtener el número de líneas en esos rangos....

jueves, 18 de agosto de 2016

VBA: Cuándo y Cómo pasar argumentos por ByVal o por ByRef

Si llevas un tiempo programando, seguro que te ha surgido la duda en determinados momentos de cómo y cuándo emplear y definir tus argumentos (de procedimientos Sub o Function) como ByVal o como ByRef.
Trataré hoy de aclarar este aspecto importante para el comportamiento de nuestras macros.


Lo primero a indicar es que la omisión, esto es, por defecto, en VBA para Excel, se pasan los argumentos Por Referencia (ByRef). (OJO por que lo normal en otros lenguajes es que sea por valor!).

1 - ByVal - "Por Valor":
Si tenemos distintos procedimientos (Sub y/o Function) y en todos ellos empleamos la misma variable ByVal, entonces tomamos un valor en un primer procedimiento y cuando este valor cambie en otro procedimiento, sólo cambiará en el segundo y no en el primero.
Dicho de otro modo, pasamos un valor numérico, este número se copia y se usa, por ejemplo, en una función.... si cambiamos el número, se cambiará en la función.

2 - ByRef - "Por Referencia":
Si tenemos distintos procedimientos (Sub y/o Function) y en ambos se emplea la misma variable, con ByRef toma un valor en un primer proceso, si este valor cambia en el segundo, cambiará también en el primero.
Las referencias son instancias de la misma variable; es como la misma variable usada en muchos lugares. Por tanto si la modificamos en la función, lo hará en el resto de nuestra programación.


Hablemos de cuándo emplear uno y otro...
La ventaja de pasar un argumento con ByRef es que el procedimiento puede devolver un valor al código de llamada por medio del argumento.
La ventaja de pasarlo con ByVal es que protege a la variable de los cambios que sobre ella pueda efectuar el procedimiento.

Aunque el mecanismo que usemos para pasar argumentos afecta al rendimiento de nuestras macros, la diferencia es insignificante. Una excepción es cuando se pasa un tipo de valor grande con ByVal (copia todo el contenido de los datos del argumento), en cuyo caso, lo más eficiente es pasarlo como ByRef.

En definitiva, cuando el elemento del código de llamada subyacente al argumento es un elemento no modificable, declararemos el parámetro correspondiente ByVal, ya que ningún código podrá cambiar el valor de un elemento no modificable.
Por contra, si el procedimiento necesita realmente modificar el valor subyacente en el código de llamada, declararemos el parámetro correspondiente ByRef.


Mejor vemos el comportamiento con un ejemplo.
Plantearemos un par de procedimientos iguales, con la única diferencia que en el primer caso se pasará como ByVal y en el segundo como ByRef.


Primer caso: Pasando un argumento Por Referencia (ByRef).

Sub PorReferencia()
Dim dato As Integer
dato = 13

Debug.Print "Dato pto1:=" & dato
'Pasamos la variable Por Valor (ByRef) con valor 13
'a través del procedimeinto SumarPorValor
Call SumarPorReferencia(dato)

'Muestra que el valor= 1013
'ya que se modificó al usar la función SumarPorReferencia
Debug.Print "Dato pto3:=" & dato

End Sub
Sub SumarPorReferencia(ByRef Valor As Integer)
'Modifica la variable
Valor = Valor + 1000
Debug.Print "Dato pto2:=" & Valor
End Sub


Al ejecutar el procedimiento 'PorReferencia' observamos en la ventana de Inmediato las tres etapas por las que pasa la variable:
Dato pto1:=13
Dato pto2:=1013
Dato pto3:=1013

Es decir, observamos la transformación del dato.. al cambiar en el segundo procedimiento 'SumarPorReferencia' lo modifica en el primero...


Veamos la diferencia ahora si empleamos y pasamos la variable Por Valor (ByVal).

Sub PorValor()
Dim dato As Integer
dato = 13

Debug.Print "Dato pto1:=" & dato
'Pasamos la variable Por Valor (ByVal) con valor 13
'a través del procedimeinto SumarPorValor
Call SumarPorValor(dato)

'Muestra que el valor= 13
'no se modificó al usar la función SumarPorValor
Debug.Print "Dato pto3:=" & dato

End Sub
Sub SumarPorValor(ByVal Valor As Long)
'Modifica la variable
Valor = Valor + 1000
Debug.Print "Dato pto2:=" & Valor
End Sub



Al ejecutar el procedimiento 'PorValor' observamos en la ventana de Inmediato las tres etapas por las que pasa la variable:
Dato pto1:=13
Dato pto2:=1013
Dato pto3:=13

Es decir, observamos la NO transformación del dato.. al cambiar en el segundo procedimiento 'SumarPorValor' NO se modifica en el primero...


Un último ejemplo...

Sub PasandoArgumentos()
Dim Valor1 As Integer, Valor2 As Integer

'Damos valores a las variables
Valor1 = 888: Valor2 = 999        'por defecto ambos pasados

Debug.Print "Antes = Valor1: " & CStr(Valor1), "Valor2: " & CStr(Valor2)

'cambiamos el modo en que pasamos los argumentos
Call Cambio(X:=Valor1, Y:=Valor2)
Debug.Print "Después =  Valor1: " & CStr(Valor1), "Valor2: " & CStr(Valor2)
End Sub

Sub Cambio(ByRef X As Integer, ByVal Y As Integer)
'valores después de la llamada
X = 222     'pasado por referencia
Y = 444     'pasado por valor
End Sub



Aquí se observa, en la ventana de inmediato:
Antes = Valor1: 888 Valor2: 999
Después = Valor1: 222 Valor2: 999
como la variable que se ha pasado como valor (ByVal) no cambia (=999)... mientras que la que pasamos como referencia toma el valor modificado por el segundo procedimiento (de 111 a 222).