Errores de redondeo durante la transición

En Baviera, una calificación promedio general de \(2{,}33\) certifica la idoneidad para el Gymnasium \(2{,}34\) no. En este punto de corte tan marcado, el orden de dos operaciones de redondeo puede determinar a qué nivel pertenece un alumno. Günther Felbinger analiza las posibles consecuencias de este efecto de redondeo en una entrada de blog . Por mero interés, calculé yo mismo los efectos para todas las combinaciones de calificaciones permitidas en el modelo.


Mi pregunta principal es: ¿Con qué frecuencia el redondeo inicial a las calificaciones de las asignaturas completas da lugar a un tipo de cálculo escolar diferente al de los cálculos que utilizan promedios de asignaturas sin redondear? Además, sería interesante observar qué sucede si los promedios de las asignaturas individuales (como se sugiere) se redondean inicialmente a una sola cifra decimal.

Según el artículo 6 del Reglamento de Educación Primaria de Baviera, la nota media se calcula a partir de las calificaciones en alemán, matemáticas y estudios generales y locales. El reglamento de aplicación especifica los límites como hasta \(2{,}33\) y hasta \(2{,}66\) . En el siguiente modelo de cálculo, cada media global se trunca a dos decimales y no se redondea. Así, por ejemplo...

\[
\frac{2{,}33+2{,}34+2{,}34}{3}=2{,}3366\ldots \longrightarrow 2{,}33.
\]

Esto es crucial para la clasificación: \(2{,}3366\ldots\) no se convierte en \(2{,}34\) , sino en \(2{,}33\) y, por lo tanto, sigue siendo suficiente para la escuela de gramática.

La entrada inicial ilustra el redondeo temprano con los valores medios específicos de la materia supuestos \(1{,}6\) , \(2{,}6\) y \(2{,}6\) . A partir de estos, se derivan primero las calificaciones completas \(2\) , \(3\) y \(3\) . Su media es

\[
\frac{2+3+3}{3}=2{,}6666\ldots \longrightarrow 2{,}66,
\]

lo que significa que el estudiante no reúne los requisitos para ingresar a la escuela de gramática. Sin embargo, sin redondear a calificaciones enteras, el resultado en el ejemplo hipotético sería...

\[
\frac{1{,}6+2{,}6+2{,}6}{3}=2{,}2666\ldots \longrightarrow 2{,}26.
\]

En el segundo ejemplo, el redondeo funciona en sentido contrario: \(2{,}4\) , \(3{,}4\) y \(2{,}4\) se convierten en los valores enteros \(2\) , \(3\) y \(2\) , que oficialmente \(2{,}33\) . Sin embargo, los valores sin redondear dan como resultado \(2{,}73\) . Los ejemplos demuestran claramente el efecto. No obstante, aún no revelan con qué frecuencia ocurre ni cuáles serían las consecuencias de un método de redondeo diferente.

Por lo tanto, para mi cálculo utilizo un espacio de resultados claro. La media de cada sujeto puede ser uno de los \(501\) valores dentro de él.

\[
1{,}00,\, 1{,}01,\, 1{,}02,\, \ldots, \, 5{,}99, \, 6{,}00
\]

Supongamos que peso por igual todas las combinaciones ordenadas de alemán, matemáticas y estudios locales y generales. Por lo tanto, hay

\[
501^3=125\,751\,501
\]

Casos. Por lo tanto, el modelo es inequívoco, pero artificial: presupone promedios de sujetos distribuidos uniformemente, la independencia de los tres sujetos y pasos de exactamente una centésima. No refleja la distribución real de los niños de primaria bávaros.

Para tres sujetos significa \(d,m,h\) escribimos

\[
A(d,m,h)=\frac{\left\lfloor 100\cdot\frac{d+m+h}{3}\right\rfloor}{100}
\]

para el promedio general truncado a dos decimales. Ahora comparamos tres métodos de cálculo.:

\[
\begin{aligned}
A_0 &= A(\operatorname{round}(d),\operatorname{round}(m),\operatorname{round}(h)),\\
A_1 &= A(\operatorname{round}_1(d),\operatorname{round}_1(m),\operatorname{round}_1(h)),\\
A_\mathrm{ref} &= A(d,m,h).
\end{aligned}
\]

\(A_0\) corresponde al cálculo con grados enteros, \(A_1\) a la propuesta con un decimal y \(A_\mathrm{ref}\) a la referencia sin redondear dentro del modelo. El cálculo se puede implementar directamente en Rust , por ejemplo.:

const SCHOOL_GYMNASIUM: i32 = 0;
const SCHOOL_REALSCHULE: i32 = 1;
const SCHOOL_MITTELSCHULE: i32 = 2;
const FLOAT_TOLERANCE: f64 = 1e-9;

#[derive(Clone, Copy, Default)]
struct Statistic {
    conflict_count: u64,
    total_count: u64,
}

fn determine_school(average: f64) -> i32 {
    if average <= 2.33 {
        return SCHOOL_GYMNASIUM;
    }
    if average <= 2.66 {
        return SCHOOL_REALSCHULE;
    }
    SCHOOL_MITTELSCHULE
}

fn calculate_average(note_deutsch: f64, note_mathe: f64, note_hsu: f64) -> f64 {
    let average = (note_deutsch + note_mathe + note_hsu) / 3.0;
    ((average + FLOAT_TOLERANCE) * 100.0).floor() / 100.0
}

fn round_to_whole_grade(grade: f64) -> f64 {
    grade.round()
}

fn round_to_one_decimal(grade: f64) -> f64 {
    (grade * 10.0).round() / 10.0
}

fn normalize_grade(grade: f64) -> f64 {
    (grade * 100.0).round() / 100.0
}

fn percentage(count: u64, total: u64) -> f64 {
    count as f64 / total as f64 * 100.0
}

fn main() {
    let mut combination_count = 0;
    let mut official_reference_conflicts = 0;
    let mut official_proposal_conflicts = 0;
    let mut proposal_reference_conflicts = 0;
    let mut statistics = [Statistic::default(); 4];

    let mut note_deutsch = 1.0;
    while note_deutsch <= 6.0 {
        let mut note_mathe = 1.0;
        while note_mathe <= 6.0 {
            let mut note_hsu = 1.0;
            while note_hsu <= 6.0 {
                combination_count += 1;

                let official_average = calculate_average(
                    round_to_whole_grade(note_deutsch),
                    round_to_whole_grade(note_mathe),
                    round_to_whole_grade(note_hsu),
                );
                let proposed_average = calculate_average(
                    round_to_one_decimal(note_deutsch),
                    round_to_one_decimal(note_mathe),
                    round_to_one_decimal(note_hsu),
                );
                let reference_average =
                    calculate_average(note_deutsch, note_mathe, note_hsu);
                let official_school = determine_school(official_average);
                let proposed_school = determine_school(proposed_average);
                let reference_school = determine_school(reference_average);

                if official_average == 3.0 {
                    statistics[0].total_count += 1;
                    if reference_school != SCHOOL_MITTELSCHULE {
                        statistics[0].conflict_count += 1;
                    }
                }
                if official_average == 2.66 {
                    statistics[1].total_count += 1;
                    if reference_school == SCHOOL_GYMNASIUM {
                        statistics[1].conflict_count += 1;
                    }
                }
                if official_average == 2.33 {
                    statistics[2].total_count += 1;
                    if reference_school != SCHOOL_GYMNASIUM {
                        statistics[2].conflict_count += 1;
                    }
                }
                if official_average == 2.66 {
                    statistics[3].total_count += 1;
                    if reference_school == SCHOOL_MITTELSCHULE {
                        statistics[3].conflict_count += 1;
                    }
                }

                if official_school != reference_school {
                    official_reference_conflicts += 1;
                }
                if official_school != proposed_school {
                    official_proposal_conflicts += 1;
                }
                if proposed_school != reference_school {
                    proposal_reference_conflicts += 1;
                }

                note_hsu = normalize_grade(note_hsu + 0.01);
            }
            note_mathe = normalize_grade(note_mathe + 0.01);
        }
        note_deutsch = normalize_grade(note_deutsch + 0.01);
    }

    println!(
        "official/reference: {}%",
        percentage(official_reference_conflicts, combination_count)
    );
    println!(
        "official/proposal: {}%",
        percentage(official_proposal_conflicts, combination_count)
    );
    println!(
        "proposal/reference: {}%",
        percentage(proposal_reference_conflicts, combination_count)
    );
    for (index, statistic) in statistics.iter().enumerate() {
        println!(
            "#{}: {}% ({}/{})",
            index + 1,
            percentage(statistic.conflict_count, statistic.total_count),
            statistic.conflict_count,
            statistic.total_count
        );
    }
}

La pequeña tolerancia en calculate_average impide un valor como \(2{,}67\) Debido a su representación de punto flotante binario, se consideró erróneamente \(2{,}669999\ldots\) está cortado. normalize_grade Además, reduce cada valor del bucle a una cuadrícula de orden centésimo. Existen al menos dos posibles valores medios diferentes. \(1/300\) aparte; la tolerancia de \(10^{-9}\) es, por lo tanto, lo suficientemente pequeño como para no cambiar ningún caso límite real.

El programa se compila y se inicia con

rustc -O calc.rs && ./calc

El programa itera a través de todas las combinaciones \(125\,751\,501\) y devuelve:

ComparaciónParte
Calificaciones globales frente a promedios de asignaturas sin redondear\(10{,}4411\,\%\)
Calificaciones completas comparadas con la propuesta con un decimal.\(10{,}1521\,\%\)
Propuesta con un decimal en comparación con los promedios de expertos sin redondear.\(0{,}6615\,\%\)

El valor central para mí es \(10{,}4411\,\%\): En más de una de cada diez combinaciones, el redondeo temprano a grados enteros da como resultado un tipo de escuela calculado diferente al de la referencia sin redondear. Esta proporción describe exclusivamente el efecto del redondeo dentro del modelo elegido.

Cambiar a promedios de expertos con un decimal daría lugar a una clasificación diferente en el \(10{,}1521\,\%\) de todos los casos del modelo en comparación con el método anterior. Sin embargo, la tercera comparación es crucial: en comparación con la referencia sin redondear, el método con un decimal deja solo \(0{,}6615\,\%\) casos desviados. La desviación de la referencia disminuye así en

\[
1-\frac{0{,}6615}{10{,}4411}\approx 93{,}7\,\%.
\]

Para mí, esta es precisamente la consecuencia cuestionable del método de cálculo actual: los logros específicos de cada materia se mantienen inalterados; solo el momento en que se realiza el redondeo puede alterar el tipo de escuela calculado. Si bien calcular con una cifra decimal no elimina por completo este efecto, lo reduce significativamente dentro del modelo.

Sin embargo, estas cifras no nos indican cuántos niños se ven afectados realmente. Las calificaciones de los boletines no se generan necesariamente como promedios redondeados mecánicamente, el rendimiento académico no se distribuye de manera uniforme ni es independiente, y la elección del tipo de escuela no depende únicamente de estas tres cifras. Mi cálculo responde a una pregunta más específica: ¿Con qué frecuencia el redondeo modifica la calificación calculada cuando todas las combinaciones de valores de centésimas son igualmente probables?

Finalmente, una comparación con la publicación original: la tasa general mencionada allí, aproximadamente \(10\,\%\) está cerca de mi \(10{,}4411\,\%\) . Sin embargo, mis cálculos no confirman los cuatro casos individuales, cada uno reportado como \(1/6 \approx 17\,\%\) Para "no cursó la escuela secundaria con \(3{,}00\) ", "no cursó la escuela de gramática con \(2{,}33\) ", "logró ingresar a la escuela de gramática con \(2{,}66\) " y "logró cursar la escuela secundaria con \(2{,}66\) \(1{,}4615\,\%\) \(59{,}6096\,\%\) \(1{,}2023\,\%\) \(62{,}9395\,\%\) .

Los cuatro valores son probabilidades condicionales con grupos de referencia de distinto tamaño. Sin un espacio muestral definido de forma diferente \(1/6\) a partir de ellos. Por lo tanto, mi cálculo confirma el efecto de redondeo, pero no estas probabilidades individuales. En cualquier caso, para afirmar el número real de niños afectados se necesitarían distribuciones de grados reales y sus dependencias.

Atrás