{ }codelper
浮点除法平方根P0168

传感器读数的平均值与标准差

工厂收集了各传感器在同一段时间内测得的值。
对每个传感器求出这些值的平均值和标准差,看看值波动了多少。
第一行给出传感器数 s。
接下来 s 行,每行给出一个传感器的值的个数 n 和 n 个整数值,用空格分开。
平均值 m 是值的总和除以 n 得到的实数。
标准差是把每个值的(值 - m)平方后全部相加,再把和除以 n,然后开平方(除以 n,而不是 n - 1)。
按输入顺序,每个传感器输出一行:平均值和标准差,都四舍五入到小数点后第三位,用一个空格分开。
即使是整数也写三位小数(80 → 80.000)。
为了让四舍五入不落在边界上,不会给出平均值或标准差乘以 1000 后小数部分在 0.49 到 0.51 之间的输入。

输入
1 ≤ s ≤ 20,1 ≤ n ≤ 1,000,0 ≤ 值 ≤ 1,000,000,000,没有平均值或标准差 × 1000 的小数部分在 0.49~0.51 之间的输入
输出
每个传感器一行:平均值和标准差(保留三位小数)
样例 1 · stdin
4
5 70 80 90 85 75
1 1000000000
4 999999999 1000000000 1000000001 1000000000
3 1 2 2
stdout
80.000 7.071
1000000000.000 0.000
1000000000.000 0.707
1.667 0.471
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

第一个陷阱在除法上。
Python 3 的 /、JS/TS 的 /、Craykon 的 / 总是实数除法,所以 sum / n 直接得到 1.667。
在 C、C++、Java、C# 中两个整数相除是整数除法,5 / 3 是 1,放进 double 也还是 1.0。
所以要像 (double)sum / n(C++ static_cast<double>(sum) / n)这样,在除之前把一边变成实数。
第二个陷阱是总和的大小。
1,000 个 10^9 的值之和是 10^12,超过了 C、C++、Java、C# 的 int(约 21 亿),所以要用 long long、long 累加。
Python 的整数和 JS、TS、Craykon 的 double 在这个范围内是精确的。
第三个陷阱是公式。
如果用「平方的平均 - 平均的平方」一次求方差,当值接近 10^9 且波动很小时,就要相减两个约 10^18 的实数。
double 只有约 16 位有效数字,差值(比如 0.5)会被几百的误差淹没,甚至变成负数,让平方根成为 NaN。
八种语言用的都是同样的 double,所以都会同样地出错。
安全的做法是遍历两遍:先求平均值,再把(值 - 平均值)的平方加起来。
平方根:Python math.sqrt,JS/TS Math.sqrt,Java Math.sqrt,C# Math.Sqrt,C sqrt(<math.h>,gcc 要加 -lm),C++ std::sqrt(<cmath>),Craykon 数学.平方根。
三位小数:Python f"{x:.3f}",JS/TS toFixed(3),Java String.format("%.3f"),C# ToString("F3"),C/C++ printf("%.3f"),Craykon 字符.保留小数(x, 3)。
Java 的 String.format 和 C# 的 ToString 会按电脑的区域设置输出,小数点可能变成逗号,所以 Java 传入 Locale.ROOT,C# 传入 CultureInfo.InvariantCulture。

Pythonmain.py · 9 行
import math s = int(input())for _ in range(s):    n, *xs = map(int, input().split())    mean = sum(xs) / n  # Python 3의 /는 늘 실수 나눗셈이다    # 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다    var = sum((x - mean) ** 2 for x in xs) / n    print(f"{mean:.3f} {math.sqrt(var):.3f}")
JavaScriptmain.js · 12 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const s = Number(lines[0]);const out = [];for (let i = 1; i <= s; i++) {  const [n, ...xs] = lines[i].trim().split(/\s+/).map(Number);  // JS의 수는 모두 double이라 /는 늘 실수 나눗셈이다(합 10^12도 2^53 안이라 정확하다)  const mean = xs.reduce((a, b) => a + b, 0) / n;  // 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다  const variance = xs.reduce((acc, x) => acc + (x - mean) ** 2, 0) / n;  out.push(`${mean.toFixed(3)} ${Math.sqrt(variance).toFixed(3)}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 13 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const s = Number(lines[0]);const out: string[] = [];for (let i = 1; i <= s; i++) {  const [n, ...xs] = lines[i].trim().split(/\s+/).map(Number);  // JS의 수는 모두 double이라 /는 늘 실수 나눗셈이다(합 10^12도 2^53 안이라 정확하다)  const mean = xs.reduce((a, b) => a + b, 0) / n;  // 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다  const variance = xs.reduce((acc, x) => acc + (x - mean) ** 2, 0) / n;  out.push(`${mean.toFixed(3)} ${Math.sqrt(variance).toFixed(3)}`);}console.log(out.join("\n"));
JavaMain.java · 27 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.Locale; public class Main {    public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int s = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int k = 0; k < s; k++) {            String[] p = in.readLine().trim().split("\\s+");            int n = Integer.parseInt(p[0]);            long[] xs = new long[n];            long sum = 0; // 10^9 × 1,000 = 10^12: int로는 넘친다            for (int i = 0; i < n; i++) {                xs[i] = Long.parseLong(p[i + 1]);                sum += xs[i];            }            double mean = (double) sum / n; // long / int는 정수 나눗셈이라 한쪽을 double로 바꾼다            // 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다            double sq = 0;            for (long x : xs) sq += (x - mean) * (x - mean);            out.append(String.format(Locale.ROOT, "%.3f %.3f", mean, Math.sqrt(sq / n))).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 27 行
using System;using System.Globalization;using System.Linq;using System.Text; class Program{    static void Main()    {        int s = int.Parse(Console.ReadLine());        var sb = new StringBuilder();        for (int k = 0; k < s; k++)        {            long[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(long.Parse).ToArray();            int n = (int)p[0];            long[] xs = p.Skip(1).ToArray();            long sum = xs.Sum(); // 10^9 × 1,000 = 10^12: int로는 넘친다            double mean = (double)sum / n; // long / int는 정수 나눗셈이라 한쪽을 double로 바꾼다            // 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다            double sq = xs.Sum(x => (x - mean) * (x - mean));            double sd = Math.Sqrt(sq / n);            sb.Append(mean.ToString("F3", CultureInfo.InvariantCulture)).Append(' ')              .Append(sd.ToString("F3", CultureInfo.InvariantCulture)).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 24 行
#include <cmath>#include <cstdio>#include <iostream>#include <vector> int main() {    int s;    std::cin >> s;    for (int k = 0; k < s; k++) {        int n;        std::cin >> n;        std::vector<long long> xs(n);        long long sum = 0; // 10^9 × 1,000 = 10^12: int로는 넘친다        for (auto& x : xs) {            std::cin >> x;            sum += x;        }        double mean = static_cast<double>(sum) / n; // 한쪽을 double로 바꿔야 실수 나눗셈이 된다        // 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다        double sq = 0;        for (long long x : xs) sq += (x - mean) * (x - mean);        std::printf("%.3f %.3f\n", mean, std::sqrt(sq / n));    }}
Cmain.c · 26 行
#include <math.h>#include <stdio.h> int main(void) {    int s;    scanf("%d", &s);    static long long xs[1000];    for (int k = 0; k < s; k++) {        int n;        scanf("%d", &n);        long long sum = 0; /* 10^9 × 1,000 = 10^12: int(약 21억)로는 넘친다 */        for (int i = 0; i < n; i++) {            scanf("%lld", &xs[i]);            sum += xs[i];        }        double mean = (double)sum / n; /* 한쪽을 double로 바꿔야 실수 나눗셈이 된다 */        /* 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다 */        double sq = 0;        for (int i = 0; i < n; i++) {            double d = xs[i] - mean;            sq += d * d;        }        printf("%.3f %.3f\n", mean, sqrt(sq / n));    }    return 0;}
Craykonmain.cray · 20 行
s = 控制台.读行().转数字();循环 (k = 0; k < s; k++){    xs = 控制台.读数字();    n = xs[0]; // 맨 앞은 개수    sum = 0;    循环 (i = 1; i <= n; i++)    {        sum += xs[i];    }    // 크레이콘의 /는 늘 실수 나눗셈이다(정수 몫은 수학.몫)    mean = sum / n;    // 평균을 먼저 구하고 평균과의 차를 제곱해 더한다(두 번 훑기): 큰 수끼리 빼는 일이 없다    sq = 0;    循环 (i = 1; i <= n; i++)    {        sq += (xs[i] - mean) * (xs[i] - mean);    }    控制台.输出(字符.保留小数(mean, 3) + " " + 字符.保留小数(数学.平方根(sq / n), 3));}