AI Library · 知识库

# 数字与运算:平均值不是整数商

学习目标:计算平均值和完成比例,识别整数除法、浮点误差和数值范围。前置知识:变量、函数、条件;只需知道可空返回值表示“没有平均值”。

# 问题与边界

统计任务接收已经校验的非负整数 total 和 count,二者均不超过 1,000,000。返回 total / count 的浮点平均值;count == 0 时报告 empty,不能返回 0 假装有样本。输入类型、负数和超出上述业务范围的校验属于入口,不在本函数重复实现。

mean(7, 2) 为 3.5;mean(0, 1) 为 0;mean(1, 4) 为 0.25,可用于已完成数除以总数的比例;mean(0, 0) 无结果。驱动还覆盖范围上界。统一输出两行 3.5、empty。

本篇不承诺任意实数的精确小数表示,不实现财务舍入或任意精度平均值。限制输入范围,保证整数转双精度前没有丢失这些输入的精度;除法结果仍可能近似。

# 关键差异

语言 整数操作数的除法 默认整数范围注意点
Rust / Go / Java / Kotlin / C++ 向零截断,如 -7 / 2 = -3 固定宽度或平台相关;先溢出再转浮点无法补救
JavaScript / TypeScript number 的 / 是浮点除法 连续安全整数范围为 ±(2^53−1);bigint 是另一类型
Python / 是真除法;// 向负无穷取整 int 可增长,但浮点仍有限

先转成浮点再除,而非将整数商再转换。若平均值来自多个大整数相加,必须另行解决求和溢出;本例直接接收已存在的总量。固定宽度整数溢出的语言语义也不同:Java/Kotlin 的普通整数运算会回绕,C++ 有符号溢出是未定义行为,Rust 是否触发溢出检查与构建选项有关,不能靠“最后都会变负”推理。

# 各语言实现

各程序独立,均是完整程序;断言失败即中止,empty 是由调用者消费的业务失败。

# Rust

Rustfn mean(total: u32, count: u32) -> Option<f64> {
    if count == 0 { None } else { Some(f64::from(total) / f64::from(count)) }
}
fn main() {
    assert_eq!(-7 / 2, -3);
    assert_eq!(mean(0, 1), Some(0.0));
    assert_eq!(mean(1, 4), Some(0.25));
    assert_eq!(mean(1_000_000, 1_000_000), Some(1.0));
    println!("{}", mean(7, 2).unwrap());
    match mean(0, 0) { Some(x) => println!("{x}"), None => println!("empty") }
}

这里 unwrap 仅用于已知非零分母的示范;可变输入用下面的 match。整数除法与溢出条件见 Rust 运算符规则。

# Go

Gopackage main
import "fmt"
func mean(total, count int) (float64, bool) {
    if count == 0 { return 0, false }
    return float64(total) / float64(count), true
}
func main() {
    for _, c := range [][2]int{{0, 1}, {1, 4}, {1000000, 1000000}} {
        x, ok := mean(c[0], c[1])
        if !ok || x != float64(c[0])/float64(c[1]) { panic("mean") }
    }
    if -7/2 != -3 { panic("division") }
    x, ok := mean(7, 2)
    if !ok || x != 3.5 { panic("mean") }
    fmt.Println(x)
    if _, ok := mean(0, 0); !ok { fmt.Println("empty") } else { panic("zero") }
}

bool 明确区分“没有结果”和合法的 0;int 的位宽不应当被默认为 64。参见 Go 整数运算。

# Java

Javaimport java.util.OptionalDouble;
public class Main {
    static OptionalDouble mean(int total, int count) {
        return count == 0 ? OptionalDouble.empty()
            : OptionalDouble.of((double) total / count);
    }
    public static void main(String[] args) {
        assert -7 / 2 == -3;
        assert mean(0, 1).orElseThrow() == 0;
        assert mean(1, 4).orElseThrow() == 0.25;
        assert mean(1000000, 1000000).orElseThrow() == 1;
        System.out.println(mean(7, 2).orElseThrow());
        var result = mean(0, 0);
        if (result.isPresent()) throw new AssertionError("zero");
        System.out.println("empty");
    }
}

只转换一个操作数就会触发浮点运算;把 (double) 放到整个整数除法外面已经太晚。参见 JLS 除法。

# JavaScript

JavaScriptfunction mean(total, count) {
    return count === 0 ? null : total / count;
}
function check(ok) { if (!ok) throw new Error("check"); }
check(mean(0, 1) === 0 && mean(1, 4) === 0.25);
check(mean(1000000, 1000000) === 1);
check(-7 / 2 === -3.5 && -7n / 2n === -3n);
check(Number.isSafeInteger(2 ** 53 - 1));
check(!Number.isSafeInteger(2 ** 53));
check(2 ** 53 + 1 === 2 ** 53);
console.log(mean(7, 2));
const result = mean(0, 0);
if (result === null) console.log("empty"); else throw new Error("zero");

bigint 整数除法向零截断,不能与 number 直接混算。上面的安全整数断言说明“值可表示”不等于“相邻整数都能区分”。Number 规则见 ECMAScript Number 类型,bigint 除法见 BigInt::divide。

# TypeScript

TypeScriptfunction mean(total: number, count: number): number | null {
    return count === 0 ? null : total / count;
}
function check(ok: boolean): void { if (!ok) throw new Error("check"); }
check(mean(0, 1) === 0 && mean(1, 4) === 0.25);
check(mean(1000000, 1000000) === 1);
check(-7 / 2 === -3.5 && -7n / 2n === -3n);
check(Number.isSafeInteger(2 ** 53 - 1));
check(!Number.isSafeInteger(2 ** 53));
check(2 ** 53 + 1 === 2 ** 53);
console.log(mean(7, 2));
const result = mean(0, 0);
if (result === null) console.log("empty"); else throw new Error("zero");

number 不编码“安全整数”,运行时仍是 JS 的精度规则,见 TypeScript 基本类型。

# Kotlin

Kotlinfun mean(total: Int, count: Int): Double? =
    if (count == 0) null else total.toDouble() / count
fun main() {
    check(-7 / 2 == -3)
    check(mean(0, 1) == 0.0 && mean(1, 4) == 0.25)
    check(mean(1000000, 1000000) == 1.0)
    println(mean(7, 2))
    val result = mean(0, 0)
    if (result == null) println("empty") else error("zero")
}

可空 Double? 不用 0 当哨兵;转换位置与 Java 相同。参见 Kotlin 数字及除法。

# Python

不要使用会关闭断言的 -O。

Pythondef mean(total: int, count: int) -> float | None:
    return None if count == 0 else total / count

assert -7 // 2 == -4 and int(-7 / 2) == -3
assert mean(0, 1) == 0 and mean(1, 4) == 0.25
assert mean(1_000_000, 1_000_000) == 1
print(mean(7, 2))
result = mean(0, 0)
if result is None:
    print("empty")
else:
    raise AssertionError("zero")

// 不是其他语言整数除法的直接替换,负数反例尤其重要。无限精度 int 也不会让 / 返回无限精度有理数。参见 Python 数值运算。

# C++

C++#include <cassert>
#include <iostream>
#include <optional>
std::optional<double> mean(int total, int count) {
    if (count == 0) return std::nullopt;
    return static_cast<double>(total) / count;
}
int main() {
    assert(-7 / 2 == -3);
    assert(mean(0, 1) == 0.0 && mean(1, 4) == 0.25);
    assert(mean(1000000, 1000000) == 1.0);
    std::cout << *mean(7, 2) << '\n';
    if (!mean(0, 0)) std::cout << "empty\n";
    else return 1;
}

optional 的解引用要求有值,调用者不能直接解引用失败结果。整数除法见 C++ 工作草案乘除规则;该在线草案会更新,本例只使用 C++17 已有机制。代码假设 int 能容纳 1,000,000,常见桌面工具链满足这一条件;面向更窄平台应改用足够宽的整数类型。

# 迁移误区与适用限制

# 运行与验证

版本沿用主题基线,实际运行情况见验证记录。本文预期输出均为 3.5\nempty\n,并包含零值、空样本、比例、输入范围上界和负数除法断言。预期不等于已执行结果。Rust/C++ 生成程序在 PowerShell 用 .\main.exe,POSIX 用 ./main;各语言使用独立目录,避免同名输出覆盖。

# 相关阅读

数值转换 · 相等性 · 字符串转端口号