Existe uma maneira padrão de obter o enésimo valor de ponto flutuante `nextafter` em C ++

8

C ++ possui std::nextafter(), que retorna o próximo valor representável após um determinado valor de ponto flutuante f . No meu caso, eu gostaria de permitir n bits de slop nos bits inferiores da mantissa, portanto, 3 bits de slop exigiriam obter o 8º próximo valor após um determinado valor f . Eu poderia ligar nextafter()oito vezes, mas existe uma maneira melhor de lidar com isso?

Para a maioria dos valores, é possível converter o valor FP em uint_64, adicionar a tolerância ( 1<<3para 3 bits de slop) e depois converter novamente double, graças ao layout do IEEE 754. No entanto, isso depende do ponto flutuante do IEEE 754 ( uma boa suposição, mas também não sólida como uma rocha).

(Como pano de fundo, quero usar isso para içar pontos de interseção da superfície dos raios, que ocasionalmente estão localizados dentro da superfície devido à imprecisão do FP. Aqueles familiarizados com o ponto flutuante robusto entenderão por que epsiloné uma solução terrível.)

Steve Hollasch
fonte
Não parece que você precisa exatamente do 8º próximo valor. Multiplicar f (assumindo que seja positivo) por 1,00 ... 001 seria bom o suficiente?
Marc Glisse
Você também pode usar o valor de std::numeric_limits<T>::is_iec559para verificar se o IEEE 754 é usado e especializar a função de acordo.
IlCapitano

Respostas:

2

Uma abordagem ingênua poderia ser multiplicar por 8 a distância entre um valor e o próximo ponto flutuante representável, em vez de chamar 8 vezes std::nextafter

double advance_float(double x, int d)
{
    double step = std::copysign((std::nextafter(x, x + d) - x) * d, d);
    return x + step;
}

Aqui existem alguns testes, mas depende de você determinar se isso é adequado para o seu caso de uso.

Editar

Como observado por Steve Hollash , xpode ser tão grande que x + d == d. Daniel Jour sugeriu aproveitar frexp(e ldexp), mas na tentativa a seguir, usarei uma abordagem diferente para determinar a direção.

double advance_float(double x, int d)
{
    const double to = std::copysign(std::numeric_limits<double>::infinity(), d);
    const double next = std::nextafter(x, to);
    return x + std::copysign(d * (next - x), d);
}

Note-se que ele assume que std::numeric_limits<double>::has_infinity == true, caso contrário, ::lowest()e ::max()deve ser utilizado.

Esses são alguns resultados

         xd anterior x próximo
-------------------------------------------------- ----------------------------------------
           1 1 0x1.fffffffffffffp-1 0x1p + 0 0x1.0000000000001001p + 0
           1 8 0x1.ffffffffffff8p-1 0x1p + 0 0x1.0000000000008p + 0
     3.14159 8 0x1.921fb54442d1p + 1 0x1.921fb54442d18p + 1 0x1.921fb54442d2p + 1
      100.01 8 0x1.900a3d70a3d69p + 6 0x1.900a3d70a3d71p + 6 0x1.900a3d70a3d79p + 6
     -100.01 8 -0x1.900a3d70a3d79p + 6 -0x1.900a3d70a3d71p + 6 -0x1.900a3d70a3d69p + 6
       1e + 67 8 0x1.7bd29d1c87a11p + 222 0x1.7bd29d1c87a19p + 222 0x1.7bd29d1c87a21p + 222
       1e-59 8 0x1.011c2eaabe7dp-196 0x1.011c2eaabe7d8p-196 0x1.011c2eaabe7ep-196
           0 8 -0x0.0000000000008p-1022 0x0p + 0 0x0.0000000000008p-1022
4.94066e-324 8 -0x0.0000000000007p-1022 0x0.0000000000001001-1022 0x0.0000000000009p-1022
Prumo__
fonte
Abordagem interessante. Como está escrito, x+dnão é o que você está procurando, no entanto. Se x é grande, então (x + d) == x. Mas eu gosto da idéia: calcule o valor igual ao bit de ordem mais baixa com um expoente correspondente, escalado pelo "slop" e depois adicionado ao valor original.
Steve Hollasch
@SteveHollasch É verdade que esse é o problema nextafter, como passar a "direção".
Bob__
Adicionando frexpantes e ldexpdepois deve funcionar, não?
Daniel Jour
@DanielJour Você quer dizer isso ? Parece "funcionar", mas certamente estou perdendo muitos casos de canto.
Bob__
Maravilhoso. Não é apenas uma solução, mas várias ferramentas novas que eu não conhecia. Em frexpparticular, eu posso mover mundos. Obrigado!
Steve Hollasch
4

Tanto quanto eu sei, não há função padrão para isso. O Boost já cobriu você: Veja boost::math::float_advance. Se você estiver usando isso para comparar dois carros alegóricos, provavelmente desejará boost::math::float_distance.

eerorika
fonte
O link não é para a versão mais recente. (Isso provavelmente não importa muito.)
Keith Thompson