Trabalhando com Números Gigantes

Aqui você poderá oferecer suas Contribuições, Dicas e Tutoriais (Texto ou Vídeo) que sejam de interesse de todos.

Moderador: Moderadores

yugi386
Usuário Nível 2
Usuário Nível 2
Mensagens: 82
Registrado em: 24 Jul 2008 10:36
Localização: Minas Gerais

Trabalhando com Números Gigantes

Mensagem por yugi386 »

Saudações!

Algumas vezes necessitamos de realizar operações com números inteiros gigantes. É o caso de algumas rotinas de criptografia.
Alguns deste números. por exemplo, podem ser vistos em http://en.wikipedia.org/wiki/RSA_numbers.

Algumas linguagens como Ruby, Python ou Julia possuem nativamente o suporte ao BigInt (inteiros Longos). Mas como Harbour vem do Clipper e no Clipper o "dinheiro da galera" não chega a cifras desta magnitude a linguagem não dá suporte nativo a tais operações. A bem da verdade nem o C faz isto nativamente.

Há algum tempo atrás escrevi algumas funções de soma, subtração, multiplicação e divisão de inteiros longos. As operações de soma, subtração e multiplicação foram adaptadas para números fracionários também.

Suponha que você queira calcular:

Código: Selecionar todos

v1 = 37975227936943673922808872755445627854565536638199
v2 = 40094690950920881030683735292761468389214899724061
? v1
? v2
? v1*v2
Você terá a seguinte saída:

37975227936943670000000000000000000000000000000000
40094690950920880000000000000000000000000000000000
********************


Evidentemente esta não é a resposta esperada. Mas utilizando esta biblioteca você terá:

1522605027922533360535618378132637429718068114961380688657908494580122963258952897654000350692006139

Que é o resultado da multiplicação.

Segue o código fonte e alguns exemplos de operação:

Código: Selecionar todos

function main()
local n1, n2

  cls

	v1 = 37975227936943673922808872755445627854565536638199
	v2 = 40094690950920881030683735292761468389214899724061
  
	? v1
	? v2
	? v1*v2

	v3 = multiplicacao("37975227936943673922808872755445627854565536638199","40094690950920881030683735292761468389214899724061")

	? v3

	if v3 == "1522605027922533360535618378132637429718068114961380688657908494580122963258952897654000350692006139"
		? "OK"
	endif

	v4 = divisao(v3,"37975227936943673922808872755445627854565536638199")		

	if v4[1] == "40094690950920881030683735292761468389214899724061" .and. v4[2] = "0"
		? "OK"
	endif
		

	n1 = "   1.2343241324312"
	n2 = "9999.23412342"
	? soma(n1,n2)
	n1 = " 100"
	n2 = "9999"
	? soma(n1,n2)


	n1 = "1.23"
	n2 = "2.345"
	? multiplicacao(n1,n2)
	n1 = " -100"
	n2 = " 234.4"
	? multiplicacao(n1,n2)
	n1 = " 100"
	n2 = " 2.34257"
	? subtracao(n1,n2)
	n2 = " 100"
	n1 = " 2.34257"
	? subtracao(n1,n2)

	n2 = "-100"
	n1 = "-2.34257"
	? soma(n1,n2)

	n1 = "-5"
	n2 = "10"
	? soma(n1,n2)

	n2 = "-5"
	n1 = "10"
	? soma(n1,n2)

	n1 = "60"
	n2 = "20"
	vet= divisao(n1,n2)
	? vet[1]
	? vet[2]

	n1 = "20"
	n2 = "60"
	vet= divisao(n1,n2)
	? vet[1]
	? vet[2]

	n1 = "20"
	n2 = "-60"
	vet= divisao(n1,n2)
	? vet[1]
	? vet[2]

	n1 = "-20"
	n2 = "60"
	vet= divisao(n1,n2)
	? vet[1]
	? vet[2]

	n1 = "-60"
	n2 = "20"
	vet= divisao(n1,n2)
	? vet[1]
	? vet[2]

	n1 = "-63"
	n2 = "20"
	vet= divisao(n1,n2)
	? vet[1]
	? vet[2]
	
n1 = "114381625757888867669235779976146612010218296721242362562561842935706935245733897830597123563958705058989075147599290026879543541"
n2 = "3490529510847650949147849619903898133417764638493387843990820577"

	vet = divisao(n1,n2)
	? vet[1]
	? vet[2]

return nil

/**
-----------------------------------------------------------------------------------------------------------------------
Função para efetuar a soma de números inteiros positivos grandes em base 10

Parâmetros:
1. n1 = primeiro número
2. n2 = segundo número
Retorno: o resultado da soma entre os dois números

-----------------------------------------------------------------------------------------------------------------------
*/

function soma(n1,n2)
local tam1:=0, tam2:=0, tmp:=0, aux:=0, ct:=0, soma:=0, resultado:="", fracao:=.f., vet_int:=array(3), sinal := ""

	n1 := alltrim(n1)
	n2 := alltrim(n2)

	if (substr(n1,1,1)=="-" .and. substr(n2,1,1)=="-" ) // soma de dois números negativos
		n1 = substr(n1,2)
		n2 = substr(n2,2)
		sinal := "-"
	elseif (substr(n1,1,1)<>"-" .and. substr(n2,1,1)=="-" ) // subtracao normal
		resultado = subtracao(n1,substr(n2,2))
		return resultado
	elseif (substr(n1,1,1)=="-" .and. substr(n2,1,1)<>"-" ) // subtracao normal (negativo - positivo)
		resultado = subtracao(n2,substr(n1,2))
		return resultado
	else
		sinal := ""
	endif

	// rotina para permitir operação com números fracionários
	if (at(".",n1)==0 .and. at(".",n2)==0)
		fracao := .f.
	else
		fracao := .t.
		vet_int =  transforma_para_inteiro(n1,n2)		
		n1 = vet_int[1]
		n2 = vet_int[2]
	endif

    tam1 := len(n1)
    tam2 := len(n2)

    tmp := max(tam1,tam2)

    // tornando o tamanho dos numeros iguais
    n1 = replicate("0",tmp-tam1) + n1
    n2 = replicate("0",tmp-tam2) + n2

    for ct:= tmp to 1 step -1
        soma += val(substr(n1,ct,1)) + val(substr(n2,ct,1))
        aux:=soma % 10
        resultado = str(int(aux),1) + resultado
        soma = (soma - aux)/10
    next

    if soma <> 0
        resultado = alltrim(str(int(soma))) + resultado
    endif

	if fracao == .t.
		resultado = transforma_para_fracionario(resultado,vet_int[3])
	endif

	resultado = sinal + resultado

return resultado

/**
-----------------------------------------------------------------------------------------------------------------------
Função para efetuar a soma de multiplicacao inteiros positivos grandes em base 10
Parâmetros:
1. n1 = primeiro número
2. n2 = segundo número
Retorno: A função retorna o resultado da multiplicação
-----------------------------------------------------------------------------------------------------------------------
*/

function multiplicacao(n1,n2)
local tam2:=0, tmp:=0, aux:=0, ct:=0, resultado:="0", mat:=array(9), fator:=.f.,vet_inc:=array(3), fracao2:=.f., sinal:=""

	n1 := alltrim(n1)
	n2 := alltrim(n2)

	if (substr(n1,1,1)<>"-" .and. substr(n2,1,1)<>"-")
		sinal := ""
	elseif (substr(n1,1,1)=="-" .and. substr(n2,1,1)<>"-")
		sinal := "-"
	elseif (substr(n1,1,1)<>"-" .and. substr(n2,1,1)=="-")
		sinal := "-"
	else
		sinal := ""	
	endif

	n1 = strtran(n1,"-","")
	n2 = strtran(n2,"-","")

	// rotina para permitir operação com números fracionários
	if (at(".",n1)==0 .and. at(".",n2)==0)
		fracao2 := .f.
	else
		fracao2 := .t.
		vet_int =  transforma_para_inteiro(n1,n2)		
		n1 = vet_int[1]
		n2 = vet_int[2]
		? n1
		? n2
	endif

    do while len(n1) > 1
        if substr(n1,1,1)=="0"
            n1 = substr(n1,2)
        else
            exit
        endif
    enddo

    do while len(n2) > 1
        if substr(n2,1,1)=="0"
            n2 = substr(n2,2)
        else
            exit
        endif
    enddo

    if n1 == "0" .or. n2 == "0"
        return "0"
    endif

    tam2 := len(n2)

    // aqui temos as operações de multiplicação do número 2 de 1 até 9 para facilitar as operações posteriores
    mat[1] := n1
    tmp := n1

    for ct:= 2 to 9
        tmp := soma(tmp,n1)
        mat[ct] := tmp    
    next

    // Multiplicação propriamente dita
    tmp := ""
    for ct:= tam2 to 1 step -1
        aux := val(substr(n2,ct,1))
        if aux <> 0
            resultado = soma(resultado,(mat[aux]+tmp))
        endif
        tmp+="0"
    next

	if fracao2 == .t.
		resultado = transforma_para_fracionario(resultado,vet_int[3]*2)
	endif

	resultado = sinal + resultado

return resultado

/**
-----------------------------------------------------------------------------------------------------------------------
Função para efetuar a comparação de dois números inteiros positivos grandes em base 10
Parâmetros:
1. n1 = primeiro número
2. n2 = segundo número
Retorno:
A função retorna [0] se os números são iguais
                 [1] se o primeiro número é o maior
                 [2] se o segundo número é o maior
-----------------------------------------------------------------------------------------------------------------------
*/

function comparar(n1,n2)
local tam1:=0, tam2:=0, ct:=0, ret := 0

	n1 := alltrim(n1)
	n2 := alltrim(n2)

    do while  len(n1) > 1
        if substr(n1,1,1)=="0"
            n1 = substr(n1,2)
        else
            exit
        endif
    enddo

    do while len(n2) > 1
        if substr(n2,1,1)=="0"
            n2 = substr(n2,2)
        else
            exit
        endif
    enddo

    tam1 := len(n1)
    tam2 := len(n2)

    do case
        case tam1 > tam2
            return 1
        case tam2 > tam1
            return 2
    endcase

    if n1 == n2 // verifica se os números são iguais
        return 0
    endif

    // se os números tem o mesmo tamanho então temos que verificar dígito a digito
    for ct:= 1 to tam1
        if val(substr(n1,ct,1)) > val(substr(n2,ct,1))
            ret := 1
            exit
        elseif val(substr(n2,ct,1)) > val(substr(n1,ct,1))
            ret:= 2
            exit
        endif
    next

return ret

/**
-----------------------------------------------------------------------------------------------------------------------
Função para efetuar a subtração de números inteiros positivos grandes em base 10
Parâmetros:
1. n1 = primeiro número
2. n2 = segundo número
Retorno:
A função retorna o resultado da subtração (Número maior - Número Menor)
-----------------------------------------------------------------------------------------------------------------------
*/

function subtracao(n1,n2)
local tam1:=0, tam2:=0, tmp:=0, aux:=0, ct:=0, aux2:=0, subtracao:=0, resultado:="", aux3:="", fracao:=.f., vet_inc:=array(3), inverte:=.f.

	n1 := alltrim(n1)
	n2 := alltrim(n2)

	if (substr(n1,1,1)=="-" .or. substr(n2,1,1)=="-" )
		? "Não é possível definir uma operação de subtração com números com sinal!!!"
		quit
	endif

	// rotina para permitir operação com números fracionários
	if (at(".",n1)==0 .and. at(".",n2)==0)
		fracao := .f.
	else
		fracao := .t.
		vet_int =  transforma_para_inteiro(n1,n2)		
		n1 = vet_int[1]
		n2 = vet_int[2]
	endif


    tmp := comparar(n1,n2)  // comparando os números

    if tmp == 0
        return "0"      // números iguais
    elseif tmp == 2     // inverte os números para facilitar a operação
		inverte := .t.
        aux3 = n1
        n1 = n2
        n2 = aux3
    endif

    // Iniciando a operação para a subtracao    
    do while  len(n1) > 1
        if substr(n1,1,1)=="0"
            n1 = substr(n1,2)
        else
            exit
        endif
    enddo

    do while len(n2) > 1
        if substr(n2,1,1)=="0"
            n2 = substr(n2,2)
        else
            exit
        endif
    enddo

    tam1 := len(n1)
    tam2 := len(n2)
    tmp := max(tam1,tam2)

    // tornando o tamanho dos numeros iguais
    n1 = replicate("0",tmp-tam1) + n1
    n2 = replicate("0",tmp-tam2) + n2

    for ct:= tmp to 1 step -1
        subtracao = val(substr(n1,ct,1)) - (val(substr(n2,ct,1)) + aux2)
        aux2 := 0
        
        if subtracao < 0
            subtracao += 10
            aux2 ++
        endif

        resultado = str(int(subtracao),1) + resultado
    next

    do while.t.
        if substr(resultado,1,1)=="0"
            resultado = substr(resultado,2)
        else
            exit
        endif
    enddo

	if fracao == .t.
		resultado = transforma_para_fracionario(resultado,vet_int[3])
	endif

	if inverte == .t.
		resultado = "-" + resultado
	endif

return resultado

/**
-----------------------------------------------------------------------------------------------------------------------
Função para efetuar a divisao de números inteiros positivos grandes em base 10
Parâmetros:
1. n1 = primeiro número
2. n2 = segundo número
Retorno:
A função retorna o resultado da divisao com um vetor {a,b} onde [a] é o resultado da divisao inteira e [b] = resto
Assim só é possível dividir um número A por um número B quando A for maior que B
-----------------------------------------------------------------------------------------------------------------------
*/

function divisao(n1,n2)
local tam1:=0, tam2:=0, tmp:=0, aux:=0, aux2:="", divisao:=0, resultado:={"",""}, aux3:="", operador:="", ct:= 0, ret:=0, sinal:=""

	n1 := alltrim(n1)
	n2 := alltrim(n2)

	if (substr(n1,1,1)<>"-" .and. substr(n2,1,1)<>"-")
		sinal := ""
	elseif (substr(n1,1,1)=="-" .and. substr(n2,1,1)<>"-")
		sinal := "-"
	elseif (substr(n1,1,1)<>"-" .and. substr(n2,1,1)=="-")
		sinal := "-"
	else
		sinal := ""	
	endif

	n1 = strtran(n1,"-","")
	n2 = strtran(n2,"-","")

    do while  len(n1) > 1
        if substr(n1,1,1)=="0"
            n1 = substr(n1,2)
        else
            exit
        endif
    enddo

    do while  len(n1) > 2
        if substr(n2,1,1)=="0"
            n2 = substr(n2,2)
        else
            exit
        endif
    enddo

    if (n2=="1")
        resultado[1] := n1
        resultado[2] := "0"
        return resultado
    endif

    tmp := comparar(n1,n2)  // comparando os números

    if tmp == 0
        return {sinal+"1","0"}      // números iguais - divisao == 1, resto 0
    elseif tmp == 2     	  // verifica se n2 é maior que n1 porque tratamos de inteiros positivos
        return {"0",sinal + n1}
    endif

    tam1 := len(n1)
    tam2 := len(n2)
    tmp := max(tam1,tam2)

    aux := len(n2)  // numero de digitos do divisor
    operador := n1 + "#"
    aux2 := substr(operador,1,aux)

        if comparar(aux2,n2) <> 2
            operador := substr(operador,aux+1)   
        else
            aux++
            aux2 := substr(operador,1,aux)
            operador := substr(operador,aux+1)   
        endif


    do while.t.
        // verificado o resultado da divisao

        ret := 1
        do while.t.
            aux3 := multiplicacao(n2,str(int(ret),1))
            divisao := comparar(aux2,aux3)
        
            if divisao == 2
                ret:=ret-1    
                aux3 := multiplicacao(n2,str(int(ret),1))
                exit
            elseif divisao == 0
                exit
            endif
    
            if ret == 9
                exit
            endif

            ++ret
        enddo

        resultado[1] += str(int(ret),1)

        aux2 = subtracao(aux2,aux3)     // controla o resto

        if aux2 == "0" .and. substr(operador,1,1)=="#" //  divisao exata
            exit
        endif

        do while.t.
            if substr(operador,1,1)=="#"
                exit
            endif

            aux2 := aux2 + substr(operador,1,1)

            do while.t.
                if len(aux2) == 1
                    exit
                endif
                if substr(aux2,1,1)=="0"
                    aux2 = substr(aux2,2)
                else
                    exit
                endif
            enddo

            operador := substr(operador,2)

            divisao = comparar(aux2,n2)
            if divisao <> 2                
                exit
            else
                resultado[1] += "0"
            endif
        enddo

            if substr(operador,1,1)=="#" 
                exit
            endif

    enddo

    // verificando o resto final
    aux := 0
    if aux2 <> "0"
        do while.t.
            if comparar(aux2,n2) < 2
                ++aux
                aux2 = subtracao(aux2,n2)
            else
                exit    
            endif
        enddo
    endif

    if aux <> 0
        resultado[1] = resultado[1] + str(int(aux),1)
    endif

    aux := 0
    do while.t.
        if len(aux2) == 1
            exit
        endif
        if substr(aux2,1,1)=="0"
            ++aux
            aux2 = substr(aux2,2)
        else
            exit
        endif
    enddo

    resultado[1] += replicate("0",aux)
	resultado[1] = sinal + resultado[1] 
    resultado[2] = sinal + aux2 	// resto da divisao inteira

return resultado

/**
-----------------------------------------------------------------------------------------------------------------------
Função para transformar números fracionários em inteiros proporcionalmente
Parâmetros:
1. n1 = primeiro número
2. n2 = segundo número
Retorno:
A função retorna um vetor com 3 valores
			v[1] = primeiro número convertido para inteiros
			v[2] = segundo número convertido para inteiros
			v[3] = Número de zeros acrescentados para converter os dois números para inteiros proporcionalmente

OBS: separador decimal é o ponto!!!
-----------------------------------------------------------------------------------------------------------------------
*/
function transforma_para_inteiro(n1,n2)
	local vet:=array(3), dec1:=0, dec2:= 0, tam1:=0, tam2:=0, fator:=0, posicao := 0, guarda:=""

	n1 := alltrim(n1)
	n2 := alltrim(n2)

	tam1 = len(n1)
	tam2 = len(n2)

	dec1 = at(".",n1)
	dec2 = at(".",n2)

	if dec1 == 0 .and. dec2 == 0	// números inteiros
		vet[1] = n1
		vet[2] = n2
		vet[3] = 1
		return vet
	endif

	if dec1 <> 0
		dec1 = tam1 - dec1
	endif
	if dec2 <> 0
		dec2 = tam2 - dec2
	endif

	fator:= max(dec1,dec2)
	posicao = fator

	do while posicao > 0
		if (at(".",n1) <> len(n1) .and. at(".",n1) <> 0 )
			guarda = substr(n1, at(".",n1) + 1, 1)
			n1 = stuff(n1, at(".",n1) + 1, 1, ".")
			n1 = stuff(n1, at(".",n1), 1, guarda)
		else 
			n1 := strtran(n1,".","")
			n1 = n1 + "0"
		endif

		if (at(".",n2) <> len(n2)  .and. at(".",n2) <> 0 )
			guarda = substr(n2, at(".",n2) + 1, 1)
			n2 = stuff(n2, at(".",n2) + 1, 1, ".")
			n2 = stuff(n2, at(".",n2), 1, guarda)
		else 
			n2 := strtran(n2,".","")
			n2 = n2 + "0"
		endif

		--posicao
	enddo
	
	n1 = strtran(n1,".","")
	n2 = strtran(n2,".","")

	vet[1] = alltrim(n1)
	vet[2] = alltrim(n2)
	vet[3] = fator

return vet

/**
-----------------------------------------------------------------------------------------------------------------------
Função para transformar números inteiros em decimais-fracionários proporcionalmente ao fator
Parâmetros:
1. n1 = primeiro número inteiro
2. fator = Número de zeros necessários para transformação
Retorno:
A função retorna o número no formato decimal-fracionário

OBS: separador decimal é o ponto!!!
-----------------------------------------------------------------------------------------------------------------------
*/
function transforma_para_fracionario(n1,fator)
	local ct:=0, guarda:="", posicao:=0

	n1 := alltrim(n1)
	if fator > (len(n1)-2)
		n1 = replicate("0",fator-len(n1) + 4) + n1
	else
		n1 = replicate("0",2) + n1
	endif

	for ct:= 1 to fator
		if (at(".",n1)==0)
			n1 = substr(n1,1,len(n1)-1) + "." + substr(n1,len(n1),1)
		else
			posicao = at(".",n1)
			guarda = substr(n1, posicao - 1, 1)
			n1 = stuff(n1, posicao - 1, 1, ".")
			n1 = stuff(n1, posicao, 1, guarda)
		endif
	next

	do while.t.
		if (substr(n1,1,1)=="0")
			n1 = substr(n1,2)
		else
			exit
		endif
	enddo

	n1 = alltrim(n1)

	if (substr(n1,1,1)==".")
		n1 = "0" + n1
	endif	

	do while.t.
		if (substr(n1,len(n1),1)=="0")
			n1 = substr(n1,1,len(n1)-1)
		else
			exit
		endif
	enddo

	if (substr(n1,len(n1),1)==".")
		n1 = substr(n1,1,len(n1)-1)
	endif

return alltrim(n1)
// END *********************************************************************************************************************************
Este código não possui um tempo de processamento bom porque trabalha com strings simulando números e eu não tive tempo de otimizá-lo. Mas para realizar operações isoladas ele pode ser útil. Pode ser que contenha alguns bugs...

Abraço,

Yugi
Avatar do usuário
rochinha
Administrador
Administrador
Mensagens: 4664
Registrado em: 18 Ago 2003 20:43
Localização: São Paulo - Brasil
Contato:

Trabalhando com Números Gigantes

Mensagem por rochinha »

Amiguinho,

Muito boa a sua postagem, mas seria interessante voce colocar nas tags as expressões que possam fazer alguém chegar até ela, inclusive o velhinho aqui.
OPS! LINK QUEBRADO? Veja ESTE TOPICO antes e caso não encontre ENVIE seu email com link do tópico para [url=mailto://fivolution@hotmail.com]fivolution@hotmail.com[/url]. Agradecido.

@braços : ? )

A justiça divina tarda mas não falha, enquanto que a justiça dos homens falha porque tarda.
Responder