¿Existe una forma estándar de obtener el enésimo valor de punto flotante `nextafter` en C ++

8

C ++ tiene std::nextafter(), que devuelve el siguiente valor representable después de un valor de punto flotante dado f . En mi caso, me gustaría permitir n bits de slop en los bits de mantisa inferiores, por lo que 3 bits de slop requerirían obtener el octavo valor siguiente después de cierto valor dado f . Podría llamar nextafter()ocho veces, pero ¿hay una mejor manera de manejar esto?

Para la mayoría de los valores, podría pasar con la conversión del valor FP a uint_64, agregando la tolerancia ( 1<<3para 3 bits de pendiente) y luego volviendo a double, gracias al diseño de IEEE 754. Sin embargo, eso se basa en el punto flotante IEEE 754 ( una buena suposición, pero tampoco sólida como una roca).

(Para el fondo, quiero usar esto para izar puntos de intersección de la superficie del rayo, que ocasionalmente se encuentran dentro de la superficie debido a la imprecisión de FP. Aquellos familiarizados con el punto flotante robusto entenderán por qué epsilones una solución terrible).

Steve Hollasch
fuente
No parece que necesite exactamente el octavo valor siguiente. ¿Multiplicar f (suponiendo que sea positivo) con 1.00 ... 001 sería lo suficientemente bueno?
Marc Glisse
También puede usar el valor de std::numeric_limits<T>::is_iec559para verificar si se usa IEEE 754 y especializar la función en consecuencia.
IlCapitano

Respuestas:

2

Un enfoque ingenuo podría ser multiplicar por 8 la distancia entre un valor y el siguiente flotante representable, en lugar de llamar 8 veces std::nextafter

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

Aquí hay algunas pruebas, pero depende de usted determinar si esto es adecuado para su caso de uso.

Editar

Como señaló Steve Hollash , xpuede ser tan grande que x + d == d. Daniel Jour sugirió aprovechar frexp(y ldexp), pero en el siguiente intento, usaré un enfoque diferente para determinar la dirección.

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);
}

Tenga en cuenta que se supone que std::numeric_limits<double>::has_infinity == true, de lo contrario ::lowest(), y ::max()debe ser utilizado.

Esos son algunos resultados

         xd anterior x siguiente
-------------------------------------------------- ----------------------------------------
           1 1 0x1.fffffffffffffp-1 0x1p + 0 0x1.0000000000001p + 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.0000000000001p-1022 0x0.0000000000009p-1022
Beto__
fuente
Enfoque interesante x+dSin embargo, como está escrito, no es lo que estás buscando. Si x es grande, entonces (x + d) == x. Pero me gusta la idea: calcule el valor igual al bit de orden más bajo con un exponente coincidente, escalado por la "pendiente", y luego agregado al valor original.
Steve Hollasch
@SteveHollasch Es cierto, ese es el problema con nextaftercómo pasar la "dirección".
Bob__
Agregar frexpantes de esto y ldexpdespués debería funcionar, ¿no?
Daniel Jour
@DanielJour ¿Quieres decir esto ? Parece que "funciona", pero seguramente me faltan muchos casos de esquina.
Bob__
Maravilloso. No es solo una solución, sino un montón de nuevas herramientas que no conocía. Con frexpen particular, puedo mover mundos. ¡Gracias!
Steve Hollasch
4

Hasta donde yo sé, no hay una función estándar para esto. Sin embargo, Boost te tiene cubierto: Mira boost::math::float_advance. Si estás usando esto para comparar dos flotadores, probablemente quieras en su boost::math::float_distancelugar.

eerorika
fuente
El enlace no es a la versión más nueva. (Eso probablemente no importa mucho.)
Keith Thompson