Se il codice non è in grado di utilizzare hypot()
né tipi di precisione più ampi, un metodo lento esamina gli esponenti utilizzando frexp()
e ridimensiona gli argumnet @greggo.
#include <math.h>
double nibot_norm(double w, double x, double y, double z) {
// Sort the values by some means
if (fabs(x) < fabs(w)) return nibot_norm(x, w, y, z);
if (fabs(y) < fabs(x)) return nibot_norm(w, y, x, z);
if (fabs(z) < fabs(y)) return nibot_norm(w, x, z, y);
if (z == 0.0) return 0.0; // all zero case
// Scale z to exponent half-way 1.0 to MAX_DOUBLE/4
// and w,x,y the same amount
int maxi;
frexp(DBL_MAX, &maxi);
int zi;
frexp(z, &zi);
int pow2scale = (maxi/2 - 2) - zi;
// NO precision loss expected so far.
// except w,x,y may become 0.0 if _far_ less than z
w = ldexp(w, pow2scale);
x = ldexp(x, pow2scale);
y = ldexp(y, pow2scale);
z = ldexp(z, pow2scale);
// All finite values in range of squaring except for values
// greatly insignificant to z (e.g. |z| > |x|*1e300)
double norm = sqrt(((w * w + x * x) + y * y) + z * z);
// Restore scale
return ldexp(norm, -pow2scale);
}
codice di prova
#include <float.h>
#include <stdio.h>
#ifndef DBL_TRUE_MIN
#define DBL_TRUE_MIN DBL_MIN*DBL_EPSILON
#endif
void nibot_norm_test(double w, double x, double y, double z, double expect) {
static int dig = DBL_DECIMAL_DIG - 1;
printf(" w:%.*e x:%.*e y:%.*e z:%.*e\n", dig, w, dig, x, dig, y, dig, z);
double norm = nibot_norm(w, x, y, z);
printf("expect:%.*e\n", dig, expect);
printf("actual:%.*e\n", dig, norm);
if (expect != norm) puts("Different");
}
int main(void) {
nibot_norm_test(0, 0, 0, 0, 0);
nibot_norm_test(10/7., 4/7., 2/7., 1/7., 11/7.);
nibot_norm_test(DBL_MAX, 0, 0, 0, DBL_MAX);
nibot_norm_test(DBL_MAX/2, DBL_MAX/2, DBL_MAX/2, DBL_MAX/2, DBL_MAX);
nibot_norm_test(DBL_TRUE_MIN, 0, 0, 0, DBL_TRUE_MIN);
nibot_norm_test(DBL_TRUE_MIN, DBL_TRUE_MIN, DBL_TRUE_MIN,
DBL_TRUE_MIN, DBL_TRUE_MIN * 2);
return 0;
}
Risultati
w:0.00000000000000000e+00 x:0.00000000000000000e+00 y:0.00000000000000000e+00 z:0.00000000000000000e+00
expect:0.00000000000000000e+00
actual:0.00000000000000000e+00
w:1.42857142857142860e+00 x:5.71428571428571397e-01 y:2.85714285714285698e-01 z:1.42857142857142849e-01
expect:1.57142857142857140e+00
actual:1.57142857142857140e+00
w:1.79769313486231571e+308 x:0.00000000000000000e+00 y:0.00000000000000000e+00 z:0.00000000000000000e+00
expect:1.79769313486231571e+308
actual:1.79769313486231571e+308
w:8.98846567431157854e+307 x:8.98846567431157854e+307 y:8.98846567431157854e+307 z:8.98846567431157854e+307
expect:1.79769313486231571e+308
actual:1.79769313486231571e+308
w:4.94065645841246544e-324 x:0.00000000000000000e+00 y:0.00000000000000000e+00 z:0.00000000000000000e+00
expect:4.94065645841246544e-324
actual:4.94065645841246544e-324
w:4.94065645841246544e-324 x:4.94065645841246544e-324 y:4.94065645841246544e-324 z:4.94065645841246544e-324
expect:9.88131291682493088e-324
actual:9.88131291682493088e-324
L'implementazione ingenuo non riesce per 'hypot3 (DBL_MAX, 0.0, 0.0)' (overflow), 'hypot3 (DBL_MIN, 0.0, 0.0) '(underflow), ecc. Come indicato nella domanda, voglio evitare questo. – nibot
Qual è il tuo intervallo di input? –
Tutti i valori in virgola mobile rappresentabili. – nibot