Precisão de "algoritmos de calculadora"

5

Estou criando uma classe de biblioteca que fornece funcionalidade para operações matemáticas em BigDecimals (e alguns em BigIntegers ) . Agora, os BigIntegers são muito fáceis de dominar e agradáveis de usar. BigDecimals pode ser complicado, mas no final, finalmente vale a pena.

Eu quero que minha classe forneça resultados precisos até um nível de precisão especificado (como, o número de lugares após o ponto decimal). É aqui que Math me falha porque suporta operações em% código%. Agora double suporta 15-16 dígitos significativos (não 15-16 lugares depois do decimal). Por isso, decidi atualizar para double .

Mas agora, o problema é onde posso encontrar algoritmos que suportam cálculos arbitrários de precisão? Além disso, gostaria de fazer meu Bigdecimal (assim eu o chamo) análogo ao seu irmão no Pacote java.lang . Então me ocorreu que, se eu optar por algoritmos usados por calculadoras científicas, eu poderia alcançar o nível de precisão exigido.

Então, aqui estão minhas perguntas (relacionadas):

  1. Posso atingir o nível de precisão exigido usando algoritmos de calculadora?
  2. Se sim, então onde posso encontrar os algoritmos necessários?
  3. Há alguma advertência à espreita na minha abordagem?

UPDATE: No momento, acabei de adicionar suporte ao método BigMath . Faz uso do método newtoniano (e um truque especial para gerar uma estimativa boa ).

Então, aqui está a classe inteira sqrt(BigDecimal) :

package in.blogspot.life_on_the_heap.bigmath;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;

/**
 * @author ambigram_maker
 */
public class BigMath {

    public static void main(String[] args) {
        BigDecimal number = new BigDecimal("91.91");
        BigDecimal sqrt = sqrt(number);
        System.out.println("number = " + number);
        System.out.println("sqrt(number) = " + sqrt);
        System.out.println("number = " +
                                   sqrt.multiply(sqrt, getContext(sqrt, DEFAULT_PREC))
                                           .stripTrailingZeros());
    }

    public static final BigDecimal TWO = BigDecimal.valueOf(2L);

    /*
     * The default number of places after the decimal. This is also the
     * *minimum* precision provided by this class.
     */
    private static final int DEFAULT_PREC = 20;
    /*
     * Maintains the "precision" or the number of digits after the decimal.
     */
    private static int precision = DEFAULT_PREC;

    public static void setPrecision(int precision_) {
        if (precision_ < DEFAULT_PREC) {
            precision = DEFAULT_PREC;
        } else {
            precision = precision_;
        }
    }

    public static int getPrecision() {
        return precision;
    }

    public static BigDecimal sqrt(BigDecimal decimal) {
        return sqrt(decimal, precision);
    }

    public static BigDecimal sqrt(BigDecimal decimal, int P_A_D) {
        // quick exit checks:
        if (decimal.compareTo(BigDecimal.ZERO) < 0) {
            return BigDecimal.valueOf(Double.NaN);
        }
        BigDecimal
                answer,     // The storage for the guesses.
                original,   // The result of squaring the guesses
                epsillon;   // The tolerance of this method.
        MathContext context = getContext(decimal, P_A_D);
        {
        /*
         * Obtain a good estimate of the square-root for the initial guess.
         * This is done by obtaining the "top" half of the bits of the decimal.
         */
            BigInteger integer = decimal.toBigInteger();
            answer = new BigDecimal
                             (integer.shiftRight(integer.bitLength() >>> 1));
        }
        original = answer.multiply(answer);
        epsillon = getEpsillon(P_A_D);
        while (original.subtract(decimal).abs().compareTo(epsillon) > 0) {
            answer = answer.subtract(original.subtract(decimal)
                                             .divide(TWO.multiply(answer),
                                                            context));
            original = answer.multiply(answer);
        }
        return answer.round(context);
    }

    public static BigDecimal getEpsillon(int precision) {
        return new BigDecimal("1E-" + precision);
    }

    private static MathContext getContext(BigDecimal decimal, int precision) {
        int beforePoint = (decimal.toString()).indexOf('.');
        if (beforePoint == -1) beforePoint = decimal.toString().length();
        return new MathContext(beforePoint + precision);
    }
}

A saída é a desejada:

number = 91.91
sqrt(number) = 9.586970324351692703533
number = 91.91

Porque estou muito feliz com o resultado, estou determinado a seguir em frente. Por isso, procuro conselhos.

    
por Astrobleme 30.09.2014 / 14:51
fonte

1 resposta

6

Como as pessoas estão insinuando nos comentários, se você quiser fazer isso pela diversão de fazer isso, é uma coisa, mas se você quiser uma biblioteca de funções científicas para usar , você deve usar um que já foi escrito por especialistas. Há uma lista no link

As bibliotecas de código aberto listadas aqui também são um local para procurar (se não encontrar!) código legível.

Algoritmos como esse fazem parte da análise numérica ; veja Wikipedia para isso também. Os famosos livros "Receitas Numéricas" fornecem tanto códigos quanto discussões para muitos tipos de cálculos.

Se você estiver falando de calculadoras eletrônicas físicas (em oposição a programas que também são chamados de "calculadoras"), há três razões pelas quais duvido que você possa usar o código da calculadora como um protótipo para sua biblioteca matemática de precisão arbitrária.

  • Não conheço nenhuma empresa de calculadoras que tenha lançado os projetos de software e hardware de suas calculadoras.

  • Eu nunca ouvi falar de uma calculadora eletrônica com aritmética de precisão arbitrária. Uma calculadora científica típica tem uma precisão fixa. Não haverá nenhuma pista dentro do código de como estender uma dada função para uma dada precisão maior (ao passo que os escritores, por exemplo, do GNU MPFR, terão resolvido estes problemas). Por exemplo, que resultados intermediários de precisão serão necessários? Quantas vezes mais você precisará passar pelos loops? O algoritmo é um que funcionará razoavelmente rápido ou diminuirá muito, à medida que você adiciona precisão?

  • Uma determinada calculadora pode ter hardware matemático muito avançado (para fazer séries de Taylor, por exemplo) ou hardware muito primitivo (não pode nem multiplicar números de dois dígitos). O código provavelmente será código de máquina escrito à mão sem código-fonte mais legível. Você precisa entender e emular as funções do hardware e do código no seu software. Um passatempo interessante, mas talvez não o que você estava pensando.

por 01.10.2014 / 19:59
fonte