====== std.dist — Abstandsmaße ====== Abstände zwischen dichten ''f64''-Vektoren: Euklid, Manhattan, Chebyshev, Minkowski, Kosinus, Hamming, Jaccard — durchgehend über einen überlaufsicheren ''hypot''. → [[lyx_-_programmiersprache:units:mathematik|Mathematik-Units]] · [[lyx_-_programmiersprache:units:sparse|std.sparse]] · [[lyx_-_programmiersprache:units:grid|std.grid]] · [[lyx_-_programmiersprache:units:sum|std.sum]] import std.dist; Schicht 0, hängt an ''std.math'' und ''std.alloc''. 20 Funktionen. Alle Beispiele mit ''lyxc 1.0.21A'' übersetzt und ausgeführt. ---- ===== Darstellung ===== Ein Vektor ist ein Speicherblock aus ''nd'' ''f64''-Werten — ''alloc(nd * 8)'', beschrieben mit ''pokef64''. Eine Punktmenge ist dasselbe hintereinander: ''count'' Punkte zu je ''nd'' Werten. > **Drei Units bringen ''Euclid'' und ''Manhattan'' mit — das ist Absicht.** ''std.dist'' rechnet auf **dichten** ''f64''-Vektoren, [[lyx_-_programmiersprache:units:sparse|std.sparse]] auf **dünnbesetzten** (sortierte Index/Wert-Paare), [[lyx_-_programmiersprache:units:grid|std.grid]] auf **ganzzahligen** Gitterkoordinaten. Drei Darstellungen, drei Implementierungen — wer den falschen Nachbarn wählt, zahlt mit Speicher oder Genauigkeit. ---- ===== Die Maße ===== unit main; import std.io; import std.string; import std.alloc; import std.dist; fn ZF(t: pchar, v: f64): void { Print(t); PrintF64(v); } fn main(): int64 { var nd: int64 := 3; var a: int64 := alloc(nd * 8); var b: int64 := alloc(nd * 8); pokef64(a, 0.0); pokef64(a + 8, 3.0); pokef64(a + 16, 4.0); pokef64(b, 0.0); pokef64(b + 8, 0.0); pokef64(b + 16, 0.0); ZF("Euclid(a,0) : ", DistEuclid(a, b, nd)); ZF("Manhattan(a,0) : ", DistManhattan(a, b, nd)); ZF("Chebyshev(a,0) : ", DistChebyshev(a, b, nd)); ZF("Minkowski p=3 : ", DistMinkowski(a, b, nd, 3.0)); ZF("Norm(a) : ", DistNorm(a, nd)); return 0; } Euclid(a,0) : 5.000000 Manhattan(a,0) : 7.000000 Chebyshev(a,0) : 4.000000 Minkowski p=3 : 4.497941 Norm(a) : 5.000000 Für ''(0, 3, 4)'': Euklid 5, Manhattan 3+4 = 7, Chebyshev max(3,4) = 4. ''Minkowski'' mit ''p'' verbindet die drei — ''p=1'' ist Manhattan, ''p=2'' ist Euklid, große ''p'' nähern sich Chebyshev. ^ Funktion ^ Maß ^ | ''DistEuclid'' / ''DistEuclidSq'' | euklidisch, mit und ohne Wurzel | | ''DistManhattan'' | Summe der Beträge | | ''DistChebyshev'' | größte Einzelabweichung | | ''DistMinkowski(a, b, nd, p)'' | verallgemeinert, ''p'' frei | | ''DistNorm'' / ''DistNormSq'' | Länge eines einzelnen Vektors | | ''DistHamming'' | Anzahl abweichender Stellen | | ''DistHammingBits'' | dasselbe auf Bitmustern | | ''DistJaccard'' | Mengenähnlichkeit | ''DistEuclidSq'' spart die Wurzel — für **Vergleiche** genügt das quadrierte Maß, und die Reihenfolge bleibt dieselbe. ---- ===== Überlaufsicherheit: der eigentliche Punkt ===== Die naheliegende Formel ''√(x² + y²)'' überläuft, lange bevor das Ergebnis es täte. ''std.dist'' rechnet durchgehend über ''DistHypot'': unit main; import std.io; import std.string; import std.feq; import std.dist; fn main(): int64 { var h: f64 := DistHypot(1.0e200, 1.0e200); Print("Bits(hypot(1e200,1e200)) = "); PrintLn(IntToStr(FeqBits(h))); Print("hypot ist endlich : "); PrintLn(IntToStr(FeqIsFinite(h) as int64)); var big: f64 := 1.0e200; var naive: f64 := big * big + big * big; Print("Bits(naiv x*x + y*y) = "); PrintLn(IntToStr(FeqBits(naive))); Print("naiv ist unendlich : "); PrintLn(IntToStr(FeqIsInf(naive) as int64)); var q: f64 := h / 1.0e200; Print("hypot / 1e200 = "); PrintF64(q); return 0; } Bits(hypot(1e200,1e200)) = 7601389629357644631 hypot ist endlich : 1 Bits(naiv x*x + y*y) = 9218868437227405312 naiv ist unendlich : 1 hypot / 1e200 = 1.414213 **''DistHypot'' liefert ein endliches Ergebnis, die naive Formel ''inf''.** Das Bitmuster ''7601389629357644631'' entspricht ''1,4142135623730951e200'' — bitgleich mit der Referenzrechnung. ''9218868437227405312'' ist das Bitmuster von ''+inf''. **''PrintF64'' zeigt für dieses Ergebnis ''overflow''.** Das ist eine Grenze der **Ausgabe**, nicht der Rechnung — ab etwa 1e19 kann ''PrintF64'' den Wert nicht mehr darstellen. Wer große Zwischenergebnisse prüft, gibt das Bitmuster über ''FeqBits'' aus oder teilt vorher herunter (oben ''h / 1e200''). Andernfalls sieht eine korrekte Rechnung wie ein Fehler aus. ''DistHypot3(x, y, z)'' macht dasselbe für drei Achsen. ---- ===== Kosinus: Ähnlichkeit oder Abstand ===== ZF("CosineSimilarity(x, 2x) : ", DistCosineSimilarity(c, d, nd)); ZF("Cosine(x, 2x) : ", DistCosine(c, d, nd)); CosineSimilarity(x,2x) : 1.000000 Cosine(x,2x) : 0.000000 Für zwei Vektoren, die in dieselbe Richtung zeigen und sich nur in der Länge unterscheiden: **Ähnlichkeit 1, Abstand 0.** ^ Funktion ^ Wertebereich ^ Bedeutung ^ | ''DistCosineSimilarity'' | −1 … 1 | 1 = gleiche Richtung | | ''DistCosine'' | 0 … 2 | ''1 − Ähnlichkeit'' | | ''DistAngular'' | 0 … π | der Winkel selbst | Die Verwechslung der ersten beiden ist der häufigste Fehler bei Ähnlichkeitssuchen: wer nach dem **kleinsten** Wert sortiert, will ''DistCosine''; wer nach dem **größten** sortiert, ''DistCosineSimilarity''. ''DistAngular'' ist im Gegensatz zu beiden eine echte Metrik und erfüllt die Dreiecksungleichung — nötig, sobald ein Suchbaum darauf aufsetzt. ---- ===== Über Punktmengen ===== ^ Funktion ^ Zweck ^ | ''DistNearest(point, set, count, nd, …)'' | nächster Punkt der Menge | | ''DistFarthest(point, set, count, nd, …)'' | entferntester | | ''DistMatrix(set, count, nd, out)'' | alle paarweisen Abstände | | ''DistCentroid(set, count, nd, out)'' | Schwerpunkt | | ''DistWithin(a, b, nd, radius)'' | liegt ''b'' im Radius um ''a''? | ''DistMatrix'' schreibt ''count × count'' Werte — der Zielpuffer muss entsprechend groß sein. Bei 10 000 Punkten sind das 800 MB; für große Mengen ist ''DistNearest'' der richtige Weg. ''DistWithin'' vergleicht ohne Wurzelziehen und ist damit billiger als ''DistEuclid(…) <= radius''. ---- Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit ''lyxc 1.0.21A'' übersetzt und ausgeführt; die Überlaufsicherheit von ''DistHypot'' bitgenau gegen eine unabhängige Referenzrechnung geprüft.