Heute drei graue Haare mehr. Grund: Rechnen in JS
Eigentlich sollte die Sache recht einfach sein: Schreibe ein kleines Skript für eine Seite, auf der Schüler:innen das Umrechnen von Einheiten üben können.
Die Seite ist in ihrem Aufbau trivial: jeweils eine Checkbox für die Größen „Masse“ und „Volumen“, dann ein Text-Input für die Anzahl der zu generierenden Aufgaben, ein Button, der die Aufgabengenerierung startet und ein zweiter Button, mit dem man zwischen Aufgaben und Musterlösungen hin und her springen kann.
Die Einheiten zu den Größen habe ich in einem Dictionary zusammengefasst, das für jede Größe ein weiteres Dictionary mit den Umrechnungen der Einheiten enthält:
var groessen = {"m": {"kg": 1, "g": 1000, "mg":1000000, "t":0.001}, "v": {"m³": 1, "dm³": 1000, "cm³": 1000000, "l": 1000, "ml": 1000000}}
Der erste Eintrag in jedem „Einheiten-Dictionary“ ist die Basiseinheit (beim m ist es z.B. kg). Dabei gibt der Zahlenwert das Äquivalent von der Grundeinheit an (1 kg = 1000 g).
Nach ein paar Verrenkungen, die im verhindern sollen, dass man von einer Einheit in sie selbst umrechnen soll und die geeignete Zahlen für die Umrechnungen per Zufall generiert wird, erfolgt die Umrechung des Zahlenwertes. Dazu wird der generierte Zahlenwert erst in die Grundeinheit umgerechnet und dieser Wert dann in die Zieleinheit.
Und hier beginnen die Probleme.
Das erste Problem ist, dass das Ergebnis in englischer Schreibweise angegeben wird („.“ statt „,“). Hier ist die Lösung trivial:
//tv steht für "target value" ist schon ein String
var commapos = tv.indexOf(".");
if (commapos > 0){
var tvsv = tv.substring(0,commapos);
var tvsh = tv.substring(commapos+1);
//Some more shit happens
tv = tvsv + "," + tvsh;
Finde die Position des Kommas (Zeile 2). indexOf() gibt als Wert -1 zurück, wenn das Zeichen nicht in der Zeichenkette ist, somit erklärt sich die Bedingung in Zeile 3. tvsv ist die Zeichenkette vor dem Punkt, tvsh ist der String dahinter. In Zeile 9 wird dann der Wert zusammengeführt.
Das zweite Problem kenne ich schon von Python: Gleitkommazahlen mit der Basis 10 (also die, die wir alle kennen), können im Rechner schlecht abgebildet werden. Das hängt damit zusammen, wie Gleitkommazahlen im Computer repräsentiert werden. Wer möchte, kann sich mal mit dem IEEE-Standard 754 beschäftigen, der das regelt. Somit kann es dazu kommen, dass die Zahl 17,802 z.B. als 17,80200000000000001 dargestellt wird. Hier hätte man eine kurze Erklärung einfügen können, aber Schüler:innen, die eh Angst vor Mathe haben und geradezu in eine Duldungsstarre verfallen, wenn sie solche Aufgaben lösen müssen, überlesen so etwas eher und verwenden die Seite nicht zum Üben. Noch größer wird die Verwirrung in einigen anderen Fällen, in denen 0,893 als 0,892999999999999994 oder ähnlich ausgegeben wird. Hier ist das Problem in der Rundung, die man durchführen muss, um auf das richtige Ergebnis zu kommen.
Die Lösung für das Problem war gar nicht schwierig, ich hatte eh schon die Zeichenkette des Nachkommawertes bestimmt. Zunächst kümmere ich mich um den Fall mit den vielen 0 (17,80200000000000001). Hier fiel mir auf, dass nach den vielen Nullen nur eine weitere Ziffer kam. Daher iteriere ich durch den String mit den Nachkommastellen bis einschließlich der vorletzten Stelle (Zeile 44). Lese ich eine Ziffer ungleich 0 (Zeile 45), nehme ich an, dass die Nullen beim nächsten Zeichen anfangen (Zeile 47). Ist dieser Index deutlich kleiner als die Länge des Nachkomma-Strings, dann kann ich hier abschneiden (Zeile 51-53). In Zeile 46 zähle ich die Neunen für den zweiten Fall.
for (var j = 0; j < tvsh.length-1; j++){
if (["1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(tvsh[j])){
if (tvsh[j] == "9"){nc++;}
si = j+1;
}
}
if (si < tvsh.length - 5){
tvsh = tvsh.substring(0,si);
}
Ob dieser Fall (z.B. 0,892999999999999994 statt 0,892) eingetreten ist, kann ich über die Anzahl der Neunen erkennen. Ist dieser z.B. größer als 5, dann wird äquivalent zum vorherigen Fall mit den vielen Nullen vorgegangen:
if (nc > 5){
si = 0;
for (var j = 0; j <tvsh.length-1; j++){
if (["1", "2", "3", "4", "5", "6", "7", "8", "0"].includes(tvsh[j])){
si = j;
}
}
if (si < tvsh.length - 5){
tvsh = tvsh.substring(0,si)+(parseInt(tvsh[si], 10)+1).toString();
}
}
Es wird zunächst nach dem Index gesucht, wo die ganzen Neunen anfangen (Zeilen 60-64). Liegt die typische Häufung von Neunen vor, nehme ich nur die Ziffern bis einschließlich der vorletzten Ziffer vor der 9. Die letzte Ziffer muss aufgerundet werden, dazu wandel ich diese in eine Zahl um, die ich mit 1 addiere und dann wieder in einen String verwandle. Diese Ziffer hänge ich an den String mit einschließlich der vorletzten Ziffer an (passiert alles in Zeile 66).
Ein zweites Problem ist die wissenschaftliche Schreibweise mit Exponentialdarstellung: 0,000000078 wird dann eben als 7,8e-8 ausgegeben. Vereinfacht wird das Problem zum einen durch den Fakt, dass bei meinen Fällen nur negative Exponenten vorkommen, zum anderen dadurch, dass es vor dem Komma nur eine Ziffer gab. Um das Problem habe ich mich parallel gekümmert, weswegen die Zeilennummern etwas springen:
var ioe = tvsh.indexOf("e");
var tvshb = "";
if (ioe > -1) {
tvshb = tvsh.substring(ioe+1);
tvsh = tvsh.substring(0,ioe)
}
if (ioe > -1){
tvsh = "0".repeat((parseInt(tvshb,10)*(-1))-1) + tvsv + tvsh;
tvsv = "0";
}
Der Algorithmus läuft so:
- Suche das Exponentenzeichen („e“) im Nachkommastring. (Z. 37)
- Speichere den Zehnerexponenten in der Variable tvshb. (Z. 40)
- Verkürze den Nachkommastring bis zum Zeichen vor dem „e“. (Z. 41).
- Der Nachkommstring wird neu zusammengebaut aus einer Anzahl Nullen (Betrag des Exponenten minus 1), der Ziffer vor dem Komma und dem bisherigen Nachkommastring.(Z. 55)
- Zum Abschluss (Z. 56) muss jetzt die Vorkommazahl nur noch auf null gesetzt werden. (Z. 56)
Richtig ins Bein geschossen habe ich mir auch. All diese Akrobatik war in einer for-Schleife eingebunden, da ich ja eine wählbare Anzahl an Aufgaben generieren wollte. Für diese Zählschleife habe ich die Zählvariable i gewählt (ich liebe Traditionen). Dummerweise habe ich i auch allerdings auch für die Zählschleifen in Zeile 60 z.B. verwendet. Das sorgte dafür, dass ich 20 Aufgaben wollte und je nachdem mal 4, 6 oder auch 32 bekam.
Die Verrenkungen haben Spaß gemacht. Und ihr wisst ja: Alles für die Kinder!