{ }codelper
小数の割り算平方根P0168

センサー値の平均と標準偏差

工場のセンサーが同じ時間に測った値を集めた。
センサーごとに値の平均と標準偏差を求め、値がどれだけ揺れたかを見る。
一行目にセンサーの数 s が与えられる。
続く s 行に、センサーごとに値の個数 n と整数の値 n 個が空白で区切って一行で与えられる。
平均 m は値の合計を n で割った実数である。
標準偏差は、値ごとに(値 - m)を二乗してすべて足し、その合計を n で割ってから平方根をとったものである(n - 1 ではなく n で割る)。
センサーごとに一行に、平均と標準偏差を小数第三位まで四捨五入し、空白一つで区切って入力の順に出力せよ。
整数になっても小数三桁を書く(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)のように、割る前に片方を実数に変えます。
二つ目の落とし穴は合計の大きさです。
10^9 の値 1,000 個の合計は 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));}