Pergunta

estou a usar cpp_dec_float para precisão arbitrária, e é ótimo, mas estou tendo problemas para descobrir como imprimir todos os dígitos significativos.

Por exemplo, com este código para configurar

using boost::multiprecision::cpp_dec_float;
typedef boost::multiprecision::number<cpp_dec_float<100>> mp_type;

mp_type test_num("7.0710678118654752440084436210484903928483593768847403658833986900e-01");

e se eu simplesmente imprimir com

std::cout << std::scientific << test_num << std::endl;

o resultado é 7.071068e-01, então está fora.

Se eu quebrar com

std::cout << std::setprecision(std::numeric_limits<mp_type>::digits) << std::scientific << test_num << std::endl;

eu recebo 7.0710678118654752440084436210484903928483593768847403658833986900000000000000000000000000000000000000e-01.Fico feliz em não perder a precisão, mas não é muito conservador de espaço.

Existe uma maneira de remover os zeros à direita sem perder a precisão das ferramentas existentes?Caso contrário, como os zeros finais podem ser removidos da sequência resultante?

Se as ferramentas existentes podem ser usadas para satisfazer a minha intenção, como posso cpp_dec_float ser gerado em notação científica sem perda de precisão e zeros à direita removidos de uma string?Eu só consigo encontrar o exemplos de fluxo.

Mais perto

Graças ao mockinterface, estou muito mais perto.

Eu mudei o código para este:

using boost::multiprecision::cpp_dec_float;
typedef boost::multiprecision::number<cpp_dec_float<0>> mp_type;
mp_type test_num("7.0710678118654752440084436210484903928483593768847403658833986900e-01");
std::cout << test_num.str(0, std::ios_base::scientific) << std::endl;

Ter comprimento potencialmente ilimitado;no entanto, isso é impresso:

7.0710678118654752440084436210484903928480e-01

O que está perto, mas parece estranho.No fonte mockinterface tão gentilmente me apontou, encontrei essas linhas

if(number_of_digits == 0)
    number_of_digits = cpp_dec_float_total_digits10;

o que me sugere que deveria levar em conta todos os dígitos significativos, basicamente exibindo o que foi inserido por causa do comprimento ilimitado.

Eu verifiquei o fonte para cpp_dec_float_total_digits10, e não consigo determinar exatamente o que é;embora eu tenha encontrado esta seção de código que parece defini-lo.

private:
   static const boost::int32_t cpp_dec_float_elem_digits10 = 8L;
   static const boost::int32_t cpp_dec_float_elem_mask     = 100000000L;

   BOOST_STATIC_ASSERT(0 == cpp_dec_float_max_exp10 % cpp_dec_float_elem_digits10);

   // There are three guard limbs.
   // 1) The first limb has 'play' from 1...8 decimal digits.
   // 2) The last limb also has 'play' from 1...8 decimal digits.
   // 3) One limb can get lost when justifying after multiply,
   //    as only half of the triangle is multiplied and a carry
   //    from below is missing.
   static const boost::int32_t cpp_dec_float_elem_number_request = static_cast<boost::int32_t>((cpp_dec_float_digits10 / cpp_dec_float_elem_digits10) + (((cpp_dec_float_digits10 % cpp_dec_float_elem_digits10) != 0) ? 1 : 0));

   // The number of elements needed (with a minimum of two) plus three added guard limbs.
   static const boost::int32_t cpp_dec_float_elem_number = static_cast<boost::int32_t>(((cpp_dec_float_elem_number_request < 2L) ? 2L : cpp_dec_float_elem_number_request) + 3L);

public:
   static const boost::int32_t cpp_dec_float_total_digits10 = static_cast<boost::int32_t>(cpp_dec_float_elem_number * cpp_dec_float_elem_digits10);

O número de dígitos significativos pode ser determinado e usado como primeiro argumento para boost::multiprecision::cpp_dec_float::str()?

Foi útil?

Solução

Isso acabou sendo difícil.

O conto é:não existe tal funcionalidade em cpp_dec_float.O que é pior, cpp_dec_float não rastreia o número de dígitos significativos que foram definidos, portanto não há uma maneira “barata” de encontrar o comprimento necessário para imprimir a fração.

Ideias:

  • Para alguns casos fronteiriços (por ex.123,000000000000001) pode-se pegar o log10 do recíproco da parte fracionária + log10 da parte inteira.Isto falha completamente em ser genericamente aplicável.

  • Se quiser usar detalhes de implementação, você pode encontrar o elemento 'último habitado' na matriz de back-end e fazer as contas.No entanto, isso é bastante complicado (requer modificação cpp_dec_float.hpp e muitos testes).

  • Por último, observei que a implementação actual para .str() claramente faz zero esforço para ser eficiente.De forma alguma.

Portanto, apesar de tudo, tenho as seguintes sugestões.Qualquer

  1. mudar para o gmp back-end (se você puder pagar).Observação

    • esta não é uma representação decimal flutuante AFAICT
    • isso requer que uma biblioteca adicional (libgmp) seja vinculada
    • gmp_float faz tem precisão arbitrária, porém, e
    • isso é str() implementação faz leve em consideração o significado dos zeros na mantissa

    Veja Ao vivo no Coliru

    #include <boost/multiprecision/number.hpp>
    #include <boost/multiprecision/gmp.hpp>
    #include <iostream>
    
    namespace mp = boost::multiprecision;
    
    int main()
    {
        typedef mp::number<mp::gmp_float<100>> mp_type;
        mp_type test_num("7.071067811865475244008443621048490392848359376884740365883398690000000000000000000e-01");
    
        std::cout << test_num.str(0, std::ios_base::scientific) << '\n';
    }
    

    Impressões 7.071067811865475244008443621048490392848359376884740365883398690e-01 sem necessidade de ações adicionais.

  2. Se isso não for uma opção, bastaria pós-processar a saída, removendo os zeros à direita:

    template <typename T>
    std::string to_pretty_string(T const& v)
    {
        std::string s = v.str(0, std::ios_base::scientific);
        assert(s.length()>3); // min: 0.e
        switch (s[0])
        { // normalized scientific always has #.####### form of mantissa
            case '-':
            case '+': assert(s[2] == '.'); break;
            default:  assert(s[1] == '.'); break;
        }
    
        auto exp = s.find('e');
        if (std::string::npos != exp && exp > 0)
        {
            for(size_t pos = exp-1; pos; --pos)
            {
                if (s[pos] != '0')
                {
                    // remove run of 0s if applicable
                    s.erase(pos+1, exp-pos-1); 
                    break;
                }
            }
        }
        return std::move(s);
    }
    

Veja Ao vivo no Coliru de novo

Outras dicas

Você pode declarar explicitamente o número de dígitos que precisa gerar com o cpp_dec_float::str() método:

std::cout << std::scientific << test_num.str(75) << std::endl;
// output: 0.707106781186547524400844362104849039284835937688474036588339869
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top