Задача из E-Olymp:Площадь и объем пирамиды

Ссылка на задачу

#include <stdio.h>  
#include <math.h>  
int main(void)  
{  
float p,d,H,h,S,V;  
scanf("%f %f",&d,&p);  
S=d*d + 2*d*sqrt(p*p-(d*d)/4);  
H=sqrt(p*p - (d*d)/2);  
V=d*d*H/3;  
printf("%.3f %.3f",S,V);  
}  

Много раз перепроверял,ошибку не вижу вообще.Помогите!


Ответы (2 шт):

Автор решения: Harry

Исправьте float на double и вы будете приятно удивлены.

И вообще, в общем случае при вычислениях с плавающей точкой используйте double, а не float.

→ Ссылка
Автор решения: Stanislav Volodarskiy

Оценим может ли тип float выдать нужную точность в этой задаче. В условиях сказано что сторона основания и длина бокового ребра не превосходят 100. Тогда площадь поверхности и объём будут 27320.508 и 235702.250 соответственно. Я воспользовался вашей программой чтобы их получить.

Хватит ли точности float чтобы представить объём с точностью до третьего знака после запятой?

Чтобы проверить выведем последовательные значения типа float:

#include <stdio.h>
#include <math.h>

int main() {
    float x = 235702.250;
    printf("%.10f\n%.10f\n%.10f\n", nextafterf(x, x - 1), x, nextafterf(x, x + 1));
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror next.c -lm && ./a.out 
235702.2343750000
235702.2500000000
235702.2656250000

Разница между соседними значениеми 0.015625. Больше одной тысячной. float просто не способен хранить такие числа с нужной точностью.

Заменим float на double:

#include <stdio.h>
#include <math.h>

int main() {
    double p, d;
    if (scanf("%lf %lf", &d, &p) != 2) {
        return 1;
    }
    const double s = d * d + 2 * d * sqrt(p * p - (d * d) / 4);
    const double h = sqrt(p * p - (d * d) / 2);
    const double v = d * d * h / 3;
    printf("%.3lf %.3lf\n", s, v);
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror double.c -lm 
$ echo 100 100 | ./a.out 
27320.508 235702.260

Объём отличается! Проверим точность:

#include <stdio.h>
#include <math.h>

int main() {
    double x = 235702.260;
    printf("%.20lf\n%.20lf\n%.20lf\n", nextafter(x, x - 1), x, nextafter(x, x + 1));
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror next-double.c -lm && ./a.out 
235702.25999999998020939529
235702.26000000000931322575
235702.26000000003841705620

Разница между последовательными значениями 2.9103830456733704e-11. Этого достаточно чтобы представить ответ точно.

Важно понимать что число выше не означает что ответ найден с точностью до 3*10^-11. Наоборот, это оценка снизу: ответ не может быть найден с точностью лучше чем 3*10^-11. Реальная точность программы всегда будет ниже. Насколько ниже - предмет для анализа, который выходит за рамки этого ответа. Можно надеяться что точности double хватит: она на семь порядков выше нужной.

Никогда не считайте во float без веских причин. У вас нет точного способа оценить точность расчётов. В такой ситуации лишних разрядов не бывает.

Зачем нужен float если им не пользоваться?

  1. Чтобы считать большие матрицы из чисел, а памяти недостаточно. Нейронные сети попадают в этот пункт.

  2. Чтобы считать на очень слабом железе без аппаратной поддержки вещественной арифметики. Например, на микроконтроллере вычисления на float будут заметно быстрее чем на double.

Во всех остальных случаях только double.

→ Ссылка