Artikel

5.4: Darstellbare Funktionen und Computerprogramme - Mathematik


In diesem Abschnitt untersuchen wir die Beziehung zwischen darstellbaren Funktionen und Computerprogrammen. Unsere Diskussion wird eher informell sein und sich auf Ihre Intuition für Computer und Berechnungen verlassen.

Einer der Gründe, warum wir bei der Diskussion von Berechnungen eher informell sein müssen, besteht darin, dass die Idee einer Berechnung ziemlich vage ist. Mitte der 1930er Jahre entwickelten viele Mathematiker theoretische Konstrukte, die versuchten, die Idee einer berechenbaren Funktion zu erfassen. Die rekursiven Funktionen von Kurt Gödel (heute oft berechenbare Funktionen genannt), die Turing-Maschinen von Alan Turing und der (lambda)-Kalkül von Alonzo Church sind drei der bekanntesten Modelle der Berechenbarkeit.

Einer der Gründe dafür, dass Mathematiker diese formalen Konstrukte als akkurate Modellierung des intuitiven Begriffs der Berechenbarkeit akzeptieren, ist, dass sich alle vorgeschlagenen formalen Analoga der Berechnung als äquivalent erwiesen haben, und jeder von ihnen ist auch äquivalent zum Begriff der Repräsentierbarkeit die wir im letzten Abschnitt definiert haben. Somit ist bekannt, dass eine Funktion genau dann nach Turing berechenbar ist, wenn sie genau dann allgemein rekursiv ist, wenn sie (lambda)-berechenbar ist. Es ist auch bekannt, dass diese formalen Begriffe äquivalent zu der Idee sind, dass eine Funktion auf einem Computer berechenbar ist, wobei wir sagen werden, dass eine Funktion (f) auf einem idealisierten Computer berechenbar ist, wenn es ein Computerprogramm (P ), so dass, wenn das Programm (P) mit Eingabe (n) ausgeführt wird, das Programm den Computer veranlasst, (f left(n ight)) auszugeben und anzuhalten.

Die Situation ist also so: Einerseits haben wir eine intuitive Vorstellung davon, was es bedeutet, dass eine Funktion effektiv berechenbar ist. Auf der anderen Seite haben wir eine Reihe von formalen Berechnungsmodellen, von denen jedes als äquivalent zu allen anderen bekannt ist:

[egin{array}{c||c} ext{Intuitiver Begriff} & ext{Formale Modelle} hline hline & ext{Darstellbare Funktion} & ext{Berechenbare Funktion} ext{Berechenbare Funktion} & lambda- ext{Berechenbare Funktion} & ext{Turing-berechenbare Funktion} & ext{Computer-berechenbare Funktion} & vdots end{array}]

Warum also sagen wir, dass die Idee einer Berechnung vage ist? Obwohl alle Strom Definitionen sind gleichwertig, nach allem, was wir wissen, könnte es eine neue Definition von Berechnung geben, die Sie heute Abend bei einem Bier finden werden. Diese neue Definition wird intuitiv richtig sein, in dem Sinne, dass die Leute, die Ihre Definition hören, zustimmen, dass es die "richtige" Definition dafür ist, was es für eine Person bedeutet, etwas zu berechnen, aber Ihre Definition kann durchaus sein nicht entsprechen den aktuellen Definitionen. Dies wird eine weltbewegende Entwicklung sein und wird Logikern und Informatikern noch viele Jahre zu denken geben.

Sie werden als brillanter Mensch mit großem Einblick in die Funktionsweise des menschlichen Geistes in die Geschichte eingehen!

Sie werden viele Preise gewinnen und reich und berühmt sein!

Alles klar, wir geben es zu. Arm. Einfach berühmt.

OK. Vielleicht nicht berühmt. Aber zumindest in Logik- und Informatikkreisen bekannt.

Aber bis Sie dieses Bier haben, müssen wir uns der aktuellen Situation anpassen, in der wir mehrere gleichwertige Definitionen haben, die unserem derzeitigen Verständnis des Wortes zu entsprechen scheinen Berechnung. Unsere Vorstellung davon, was eine Berechnung ausmacht, ist also ungenau, obwohl es eine Präzision in dem Sinne gibt, dass viele Leute darüber nachgedacht haben, was die Definition sein sollte, und jede bisher vorgeschlagene Definition hat sich (genau) als richtig erwiesen äquivalent zu jeder anderen vorgeschlagenen Definition.

Churchs These ist einfach ein Ausdruck der Überzeugung, dass die formalen Berechnungsmodelle die intuitive Idee einer berechenbaren Funktion genau wiedergeben. Wir werden die These in Bezug auf die Darstellbarkeit formulieren, da wir mit darstellbaren Funktionen und darstellbaren Mengen gearbeitet haben.

Thesen der Kirche

Eine Gesamtfunktion (f) ist genau dann berechenbar, wenn (f) darstellbar ist.

Nun ist es wichtig zu verstehen, dass Churchs These eine „These“ im Gegensatz zu einem „Theorem“ ist und niemals ein Theorem sein wird. Als Versuch, einen intuitiven Begriff (Berechenbarkeit) und einen formalen Begriff (Darstellbarkeit) zu verknüpfen, ist dies nicht der Fall je bewiesen werden. Beweise erfordern formale Definitionen, und wenn wir eine formale Definition einer berechenbaren Funktion aufschreiben, haben wir den Sinn der These untergraben.

Um dieser Diskussion eine weitere Ebene hinzuzufügen, betrachten wir die Funktion (f), die jeder natürlichen Zahl ihre Quadratwurzel zuordnet, falls sie eine hat. Wir werden sagen, dass (fleft( n ight)) nicht definiert ist, wenn (n) kein Quadrat ist. Also ist (fleft(9 ight) = 3) und (fleft( 10 ight)) nicht definiert. Diese Teilfunktion scheint berechenbar zu sein, und tatsächlich ist hier ein Pseudocode, der die Ausgabewerte für (f) berechnet:

Etwas genauer sagen wir, dass eine Partialfunktion (f : A subseteq mathbb{N} ightarrow mathbb{N}) ist) kalkulierbar wenn es einen Algorithmus oder eine Berechnung gibt, die bei gegebener Eingabe (ninmathbb{N}) genau eine der folgenden Aktionen ausführt:

  • Wenn (f left( n ight)) definiert ist, berechnet der Algorithmus den korrekten Wert von (f left( n ight)), gibt (f left( n ight)) aus und hält dann an;
  • Wenn (f left( n ight)) nicht definiert ist, läuft der Algorithmus ewig ohne anzuhalten.

Wenn also die Funktion (f) total und berechenbar ist, gibt es einen Algorithmus, der (fleft( n ight)) für jede Eingabe (n) berechnet, aber wenn (g) . ist partiell und berechenbar, dann wird der Algorithmus von (g) angehalten, wenn (g left( n ight)) definiert ist, läuft aber ewig, wenn (g left( n ight)) nicht ist definiert.

Wir werden sagen, dass eine Menge (Ssubseteqmathbb{N}) berechenbar ist, wenn ihre charakteristische Funktion (chi_S) berechenbar ist. (Siehe Übung 5 in Abschnitt 5.3 für die Definition von (chi_S).)

Da das Studium des Rechnens ganz natürlich zur Untersuchung von Teilfunktionen führt, wird Churchs These oft in Form von Teilfunktionen formuliert:

Abschlussarbeit der Kirche

Eine Partialfunktion (f) ist genau dann berechenbar, wenn (f) schwach darstellbar ist.

Die Verbindung zwischen den beiden Versionen von Churchs These liegt in Proposition 5.3.6. Wenn man diesen Satz verwendet, ist es leicht zu erkennen, dass die Gesamtfunktionsversion von Churchs These unmittelbar aus der Teilfunktionsversion folgt.

Als Beispiel für die Art von Frage, die durch Nachdenken über berechenbare Mengen und Funktionen beantwortet werden kann, betrachten Sie Folgendes:

Die Klasse der berechenbaren Mengen von (mathbb{N}) lässt sich erweitern, indem man sich die Mengensammlung so ansieht, dass es ein Computerprogramm gibt, das dir sagt, ob eine Zahl ein Element der Menge ist, aber kein überhaupt etwas zu tun, wenn die Zahl kein Element der Menge ist. Informell heißt eine Menge (Asubseteqmathbb{N}) halb berechenbar wenn es ein Computerprogramm (P) gibt, so dass wenn (a in A), Programm (P) 0 bei Eingabe (a) zurückgibt, und wenn (a otin A), Programm (P) hält nicht an, wenn die Eingabe (a) gegeben wird. Sie können überprüfen, ob dies äquivalent zu der Aussage ist, dass die Teilfunktion (overset{smallsmile}{chi}_A : A ightarrowmathbb{N}), die für jedes Element ihres Definitionsbereichs den Wert 0 annimmt, kalkulierbar.

Wenn wir Churchs These akzeptieren, ist es leicht zu argumentieren, dass die Menge (A) genau dann darstellbar ist, wenn sowohl (A) als auch (mathbb{N} - A) halb berechenbar sind, wie Sie es sind Aufgabe in Übung 6. Andere Übungen bieten ein wenig mehr Übung im Umgang mit halb berechenbaren Mengen.

Das Studium berechenbarer Funktionen ist ein wichtiger Bereich der mathematischen Logik und betont die Verbindung zwischen Logik und Informatik. Kapitel 7 widmet sich einer Einführung in berechenbare Funktionen, die zum Beweis des Gödelschen Unvollständigkeitssatzes führt. In dieser Einstellung läuft die Aussage des Unvollständigkeitssatzes auf die Aussage hinaus, dass die Sammlung von Sätzen, die aus-(Sigma) beweisbar sind, wobei (Sigma) eine Erweiterung von (N) ist, also entscheidbar und wahr-in-(mathfrak{N}), ist eine semi-berechenbare Menge, die nicht berechenbar ist. Somit gibt es einen signifikanten Unterschied zwischen der Sammlung von berechenbaren Mengen und der Sammlung von semi-berechenbaren Mengen. Ein anderer Text, der die Berechenbarkeit bei der Behandlung des Satzes von Gödel betont, ist [Keisler und Robbin 96].

Ist Churchs These also wahr? Wir können sagen, dass alle bisherigen Beweise darauf hindeuten, dass Churchs These wahr ist, aber wir befürchten, dass dies die einzige Gewissheit ist, die wir in diesem Punkt haben können. Wir haben über 70 Jahre Erfahrung seit der Veröffentlichung der Dissertation und über 3000 Jahre, seit wir mit der Berechnung von Funktionen begonnen haben, aber das zählt nur als anekdotische Beweise. Trotzdem akzeptieren die meisten, wenn nicht alle, der mathematischen Gemeinschaft die Identifizierung von "berechenbar" mit "repräsentierbar" und damit akzeptiert die Gemeinschaft die These der Kirche als Glaubensartikel.

Übungen

  1. Wir haben in diesem Abschnitt berechenbare Funktionen und halbberechenbare Mengen definiert, aber die Definitionen werden nicht in einem eigenen Block abgesetzt und mit ausgefallenen Zahlen versehen, wie "Definition 5.4.2." Warum haben wir die Definitionen nicht offiziell gemacht?
  2. Zeigen Sie mit Churchs These, dass (Asubseteqmathbb{N}) genau dann berechenbar ist, wenn (A) darstellbar ist. Zeigen Sie dann, dass (A) genau dann semi-berechenbar ist, wenn (A) schwach darstellbar ist.
  3. (a) Zeigen Sie, dass (Asubseteqmathbb{N}) genau dann semi-berechenbar ist, wenn (A) aufzählbar ist, wobei eine Menge auflistbar wenn es ein Computerprogramm (L) gibt, so dass (L) in irgendeiner Reihenfolge die Elemente von (A) ausgibt.
    (b) Zeigen Sie, dass (Asubseteqmathbb{N}) genau dann berechenbar ist, wenn (A) in aufsteigender Reihenfolge aufzählbar ist.
  4. Nehmen Sie an, dass (Asubseteqmathbb{N}) unendlich und halb berechenbar ist und zeigen Sie, dass es eine unendliche Menge (Bsubseteq A) gibt, so dass (B) berechenbar ist.
  5. Zeigen Sie, dass (Asubseteqmathbb{N}) genau dann semi-berechenbar ist, wenn es eine (Sigma)-Formel (phileft( x ight)) gibt mit ( phi) definiert (A).
  6. Zeigen Sie mit Churchs These, dass eine Menge (A) genau dann darstellbar ist, wenn sowohl (A) als auch (mathbb{N} - A) semi-berechenbar sind. [Vorschlag: Nehmen Sie zunächst an, dass (A) darstellbar ist. Diese Richtung ist einfach. Für die andere Richtung garantiert die Annahme die Existenz von zwei Programmen. Denken Sie darüber nach, ein neues Programm zu schreiben, das diese beiden Programme gleichzeitig ausführt - zuerst führen Sie ein Programm eine Minute lang aus, dann führen Sie das zweite Programm eine Minute lang aus....]

5.4: Darstellbare Funktionen und Computerprogramme - Mathematik

Wenn ein Unterlauf auftritt, signalisieren einige Sprachimplementierungen einen Fehler, andere verwenden 0, um das Ergebnis anzunähern. Die Verwendung von 0 zum ungefähren Unterlauf unterscheidet sich qualitativ von der Auswahl einer ungefähren Darstellung einer Zahl in Inex. Konkret lässt die Annäherung an 1 2 5 0 mit ( create-inex 12 1 2 ) signifikante Stellen von der Mantisse ab, aber das Ergebnis liegt immer innerhalb von 10 % der darzustellenden Zahl. Die Annäherung an einen Unterlauf bedeutet jedoch, dass die gesamte Mantisse fallengelassen wird, was bedeutet, dass das Ergebnis nicht innerhalb eines vorhersagbaren Prozentbereichs des wahren Ergebnisses liegt.

Übung 416. ISL+ verwendet #i0.0, um den Unterlauf anzunähern. Bestimmen Sie die kleinste ganze Zahl n, so dass (expt #i10.0 n) immer noch eine ungenaue ISL+-Zahl ist und (expt #i10. (-n 1 )) mit 0 angenähert wird. Hinweis Verwenden Sie eine Funktion, um n zu berechnen. Betrachten Sie die Abstraktion über diese Funktion und die Lösung von Aufgabe 415.

*SL-Nummern

Die meisten Programmiersprachen unterstützen nur ungefähre Zahlendarstellungen. Ungenaue reelle Darstellungen gibt es in verschiedenen Varianten: float , double , extflonum und so weiter. und Arithmetik für Zahlen. Eine typische Sprache beschränkt ihre Ganzzahlen auf ein Intervall, das sich auf die Größe der Blöcke der Hardware bezieht, auf der sie ausgeführt wird. Seine Darstellung der reellen Zahlen basiert lose auf der Skizze in den vorherigen Abschnitten, jedoch mit größeren Teilen als den vier Ziffern, die Inex verwendet, und mit Ziffern aus dem 2-basierten Zahlensystem.

Die Unterrichtssprachen unterstützen sowohl genaue als auch ungenaue Zahlen. Ihre ganzen Zahlen und rationalen Zahlen sind beliebig groß und genau und werden nur durch die absolute Größe des gesamten Speichers des Computers begrenzt. Für Berechnungen dieser Zahlen verwenden unsere Unterrichtssprachen die zugrunde liegende Hardware, solange die beteiligten rationalen Zahlen in die unterstützten Datenblöcke passen, schaltet sie automatisch auf eine andere Darstellung und auf eine andere Version der arithmetischen Operationen für Zahlen außerhalb dieses Intervalls um. Ihre reellen Zahlen gibt es in zwei Varianten: exakt und ungenau. Eine exakte Zahl stellt wirklich eine reelle Zahl dar, eine ungenaue nähert sich einer reellen Zahl im Sinne der vorhergehenden Abschnitte an. Arithmetische Operationen bewahren die Genauigkeit, wenn möglich, und führen bei Bedarf zu einem ungenauen Ergebnis. Daher gibt sqrt sowohl für die genaue als auch für die ungenaue Darstellung von 2 eine ungenaue Zahl zurück. Im Gegensatz dazu erzeugt sqrt eine exakte 2, wenn eine exakte 4 gegeben wird, und #i2.0 für eine Eingabe von #i4.0 . Schließlich wird eine numerische Konstante in einem Lehrprogramm als exakt rational verstanden, sofern ihr nicht #i vorangestellt ist.

Plain Racket interpretiert alle Dezimalzahlen als ungenaue Zahlen und gibt auch alle reellen Zahlen als Dezimalzahlen wieder, unabhängig davon, ob sie exakt oder ungenau sind. Daraus folgt, dass alle diese Zahlen gefährlich sind, weil sie wahrscheinlich ungenaue Annäherungen an die wahre Zahl sind. Ein Programmierer kann Racket zwingen, Zahlen mit einem Punkt als exakt zu interpretieren, indem er numerischen Konstanten #e voranstellt.

An dieser Stelle fragen Sie sich vielleicht, wie sehr die Ergebnisse eines Programms von den tatsächlichen Ergebnissen abweichen können, wenn diese ungenauen Zahlen verwendet werden. Diese Frage war eine Frage, mit der frühe Informatiker viel zu kämpfen hatten, und in der Vergangenheit für eine zugängliche Einführung — mit Racket— lesen Sie Practically Accurate Floating-Point Math , einen Artikel über Fehleranalyse von Neil Toronto und Jay McCarthy. Es macht auch Spaß, Debugging Floating-Point Math in Racket, Neil Torontos Vortrag zur RacketCon 2011, auf YouTube zu sehen. Einige Jahrzehnte haben diese Studien ein separates Gebiet geschaffen, das als numerische Analysis bezeichnet wird. Jeder Informatiker und tatsächlich jeder, der Computer und Software verwendet, sollte sich ihrer Existenz und einiger ihrer grundlegenden Einsichten in die Funktionsweise numerischer Programme bewusst sein. Als ersten Vorgeschmack veranschaulichen die folgenden Übungen, wie schlimm es werden kann. Arbeiten Sie sie durch, um die Probleme mit ungenauen Zahlen nie aus den Augen zu verlieren.

Übung 417. Werten Sie ( expt 1.001 1e-12 ) in Racket und in ISL+ aus. Erklären Sie, was Sie sehen.


Lua ist eine dynamisch typisierte Sprache. Das bedeutet, dass Variablen keine Typen haben, sondern nur Werte. Es gibt keine Typdefinitionen in der Sprache. Alle Werte haben ihren eigenen Typ.

Alle Werte in Lua sind erstklassige Werte. Das bedeutet, dass alle Werte in Variablen gespeichert, als Argumente an andere Funktionen übergeben und als Ergebnisse zurückgegeben werden können.

Es gibt acht Grundtypen in Lua: Null, boolesch, Nummer, Schnur, Funktion, Benutzerdaten, Faden, und Tabelle. Der Typ Null hat einen einzigen Wert, Null, deren Haupteigenschaft darin besteht, sich von jedem anderen Wert zu unterscheiden, repräsentiert oft das Fehlen eines nützlichen Wertes. Der Typ boolesch hat zwei Werte, falsch und wahr. Beide Null und falsch machen eine Bedingung falsch, sie werden kollektiv genannt falsche Werte. Jeder andere Wert macht eine Bedingung wahr. Trotz seines Namens falsch wird häufig als Alternative zu Null, mit dem entscheidenden Unterschied, dass falsch verhält sich wie ein regulärer Wert in einer Tabelle, während a Null in einer Tabelle steht für einen fehlenden Schlüssel.

Der Typ Nummer stellt sowohl ganze Zahlen als auch reelle (Gleitkomma-)Zahlen dar, wobei zwei Untertypen verwendet werden: ganze Zahl und schweben. Standard-Lua verwendet 64-Bit-Ganzzahlen und Gleitkommazahlen mit doppelter Genauigkeit (64-Bit), aber Sie können Lua auch so kompilieren, dass es 32-Bit-Ganzzahlen und/oder Gleitkommazahlen mit einfacher Genauigkeit (32-Bit) verwendet. Die Option mit 32 Bit sowohl für Integer als auch für Floats ist besonders attraktiv für kleine Maschinen und eingebettete Systeme. (Siehe Makro LUA_32BITS in der Datei luaconf.h .)

Sofern nicht anders angegeben, jeder Überlauf bei der Manipulation von Integer-Werten umwickeln, nach den üblichen Regeln der Zwei-Komplement-Arithmetik. (Mit anderen Worten, das tatsächliche Ergebnis ist die eindeutige darstellbare ganze Zahl, die gleich modulo . ist 2 n zum mathematischen Ergebnis, wobei nein ist die Anzahl der Bits des Integer-Typs.)

Lua hat explizite Regeln darüber, wann jeder Untertyp verwendet wird, aber es konvertiert auch automatisch zwischen ihnen, wenn nötig (siehe §3.4.3). Daher kann der Programmierer wählen, ob er den Unterschied zwischen ganzen Zahlen und Gleitkommazahlen größtenteils ignoriert oder die vollständige Kontrolle über die Darstellung jeder Zahl übernimmt.

Der Typ Schnur stellt unveränderliche Sequenzen von Bytes dar. Lua ist 8-Bit-sauber: Strings können jeden 8-Bit-Wert enthalten, einschließlich eingebetteter Nullen (''). Lua ist auch codierungsagnostisch und macht keine Annahmen über den Inhalt eines Strings. Die Länge einer beliebigen Zeichenfolge in Lua muss in eine Lua-Ganzzahl passen.

Lua kann in Lua geschriebene Funktionen und in C geschriebene Funktionen aufrufen (und manipulieren) (siehe §3.4.10). Beide werden durch den Typ Funktion.

Der Typ Benutzerdaten wird bereitgestellt, um das Speichern beliebiger C-Daten in Lua-Variablen zu ermöglichen. Ein Userdata-Wert repräsentiert einen Block des Rohspeichers. Es gibt zwei Arten von Benutzerdaten: vollständige Benutzerdaten, ein Objekt mit einem Speicherblock, der von Lua verwaltet wird, und leichte Benutzerdaten, was einfach ein C-Zeigerwert ist. Userdata hat keine vordefinierten Operationen in Lua, außer Zuweisung und Identitätstest. Durch die Nutzung Metatabellen, kann der Programmierer Operationen für vollständige Benutzerdatenwerte definieren (siehe §2.4). Benutzerdatenwerte können in Lua nur über die C-API erstellt oder geändert werden. Dies garantiert die Integrität der Daten, die dem Hostprogramm und den C-Bibliotheken gehören.

Der Typ Faden stellt unabhängige Ausführungs-Threads dar und wird verwendet, um Coroutinen zu implementieren (siehe §2.6). Lua-Threads haben nichts mit Betriebssystem-Threads zu tun. Lua unterstützt Coroutinen auf allen Systemen, auch auf denen, die Threads nicht nativ unterstützen.

Der Typ Tabelle implementiert assoziative Arrays, d. h. Arrays, die als Indizes nicht nur Zahlen haben können, sondern jeden beliebigen Lua-Wert außer Null und NaN. (Keine Zahl ist ein spezieller Gleitkommawert, der vom IEEE 754-Standard verwendet wird, um undefinierte numerische Ergebnisse darzustellen, wie z. B. 0/0 .) Tabellen können sein heterogen das heißt, sie können Werte aller Typen enthalten (außer Null). Jeder dem Wert zugeordnete Schlüssel Null wird nicht als Teil der Tabelle betrachtet. Umgekehrt hat jeder Schlüssel, der nicht Teil einer Tabelle ist, einen zugehörigen Wert Null.

Tabellen sind der einzige Datenstrukturierungsmechanismus in Lua. Sie können verwendet werden, um gewöhnliche Arrays, Listen, Symboltabellen, Mengen, Datensätze, Grafiken, Bäume usw. darzustellen. Um Datensätze darzustellen, verwendet Lua den Feldnamen als Index. Die Sprache unterstützt diese Darstellung, indem sie a.name als syntaktischen Zucker für a["name"] bereitstellt. Es gibt mehrere praktische Möglichkeiten, Tabellen in Lua zu erstellen (siehe §3.4.9).

Wie bei Indizes können die Werte von Tabellenfeldern von beliebigem Typ sein. Da Funktionen erstklassige Werte sind, können Tabellenfelder insbesondere Funktionen enthalten. Somit können auch Tische tragen Methoden (siehe §3.4.11).

Die Indexierung von Tabellen folgt der Definition der rohen Gleichheit in der Sprache. Die Ausdrücke a[i] und a[j] bezeichnen dasselbe Tabellenelement genau dann, wenn i und j roh gleich sind (dh gleich ohne Metamethoden). Insbesondere sind Gleitkommazahlen mit ganzzahligen Werten gleich ihren jeweiligen ganzen Zahlen (z. B. 1.0 == 1). Um Mehrdeutigkeiten zu vermeiden, wird jeder Float, der als Schlüssel verwendet wird und einer ganzen Zahl entspricht, in diese ganze Zahl umgewandelt. Wenn Sie beispielsweise a[2.0] = true schreiben, ist der tatsächliche Schlüssel, der in die Tabelle eingefügt wird, die ganze Zahl 2 .

Tabellen, Funktionen, Threads und (vollständige) Benutzerdatenwerte sind Objekte: Variablen nicht wirklich enthalten nur diese Werte Verweise zu ihnen. Zuweisungen, Parameterübergaben und Funktionsrückgaben manipulieren immer Verweise auf solche Werte. Diese Operationen implizieren keinerlei Kopie.

Die Bibliotheksfunktion type gibt einen String zurück, der den Typ eines gegebenen Wertes beschreibt (siehe type ).


Divisionsfunktionen

Die folgende Tabelle zeigt die Divisionsfunktionen, die von Julias primitiven numerischen Typen &minus . unterstützt werden

Es ist das Zitat aus der euklidischen Teilung. Auch abgeschnittene Division genannt. Es berechnet x/y und der Quotient wird gegen Null gerundet.

Es ist die Etagenabteilung. Der Quotient wird auf -Inf gerundet, d. h. die größte ganze Zahl kleiner oder gleich x/y. Es ist die Abkürzung für div(x, y, RoundDown).

Es ist Deckenteilung. Der Quotient wird auf +Inf gerundet, d. h. die kleinste ganze Zahl kleiner oder gleich x/y. Es ist die Abkürzung für div(x, y, RoundUp).

Rest erfüllt x == div(x,y)*y + rem(x,y) Vorzeichen passt zu x

Es ist der Modul nach der Bodenteilung. Diese Funktion erfüllt die Gleichung x == fld(x,y)*y + mod(x,y). Das Zeichen stimmt mit y überein.

Dies ist dasselbe wie mod mit Offset 1. Es gibt r&isin(0,y] für y>0 oder r&isin[y,0) für y<0 zurück, wobei mod(r, y) == mod(x, y).

Es ist der Modul in Bezug auf 2pi. Es erfüllt 0 <= mod2pi(x) < 2pi

Es ist der Quotient und der Rest aus der euklidischen Division. Es entspricht (div(x,y),rem(x,y)).

Es ist das Floored-Quote und der Modul nach der Division. Es ist äquivalent zu (fld(x,y),mod(x,y))

Es ist der größte positive gemeinsame Teiler von x, y.

Es stellt das kleinste positive gemeinsame Vielfache von x, y dar.

Beispiel

Das unten angegebene Beispiel stellt die Divisionsfunktionen &minus . dar


Testvariablen

C++ ermöglicht es uns, den Wert einer Variablen zu testen (z. B. um zu sehen, ob eine Integer-Variable den Wert 27 oder mindestens den Wert 18 hat). Das Ergebnis eines Vergleichs ist ein boolescher Wert (wahr oder falsch). Es gibt fünf "Vergleichsoperatoren" für Zahlen (wir werden diese in Kürze im Einsatz sehen).

  • x <= y - Dieser Ausdruck ist wahr, wenn x $le$ y.
  • x >= y - Dieser Ausdruck ist wahr, wenn x $ge$ y.
  • x < y - Dieser Ausdruck ist wahr, wenn x < y ist.
  • x > y - Dieser Ausdruck ist wahr, wenn x > y ist.
  • x == y - Dieser Ausdruck ist wahr, wenn x = y.
  • x != y - Dieser Ausdruck ist wahr, wenn x $ e$ y.

Nehmen wir zum Beispiel an, wir hätten eine Integer-Variable namens "Alter" deklariert. Der Ausdruck Alter >= 18 hat einen booleschen Wert und ist wahr, wenn $ge$ 18 Jahre alt ist, andernfalls ist es falsch. Äquivalent hätten wir schreiben können Alter > 17. Das Ergebnis dieses Ausdrucks können wir in einer booleschen Variablen speichern. Hier ist ein Beispielprogramm:

Versuchen Sie, das Programm auszuführen. Versuchen Sie, den Wert von zu ändern Alter, und versuchen Sie auch andere Vergleichsoperatoren (also versuchen Sie es zu ersetzen >= mit einem von <=, >, < ==).

Natürlich steht es uns frei, Variablen mit Variablen, Variablen mit Werten und Werte mit Werten zu vergleichen (z. 15 > 17 ist ein gültiger C++-Code und hat immer den Wert false). Wir können auch Vergleichsausdrücke auf die gleiche Weise kombinieren, wie wir boolesche Werte mit && und || kombiniert haben. Was macht der folgende Code?

Der Ausdruck Alter >= 13 hat den Wert wahr, und der Ausdruck Alter <= 19 hat auch Wert wahr. Deshalb istTeenager hat den Wert des Ausdrucks (Alter >= 13) && (Alter <= 19), die einen Wert hat Wahr, wahr, welches ist wahr.

Es gibt im Allgemeinen viele Möglichkeiten, einen booleschen Ausdruck mit demselben Wert zu schreiben. Zum Beispiel sind alle diese gleichwertig:


Was bedeutet hohe Präzision?

Die Sprachen C und C++ definieren drei Arten von Gleitkommazahlen: Wenn ein Programmierer möchte, dass eine Variable nicht nur ganzzahlige Werte, sondern auch Werte mit einem Bruchteil ungleich Null speichern kann, dann kann er diese Variable als float , double oder long definieren doppelt variabel. Wenn Sie diese Seite noch lesen, kennen Sie wahrscheinlich die übliche binäre Darstellung der Zahlen im Speicher und in der CPU eines Computers. Sie wissen, dass nur ein begrenzter Zahlenbereich dargestellt werden kann und dass nicht ganzzahlige Zahlen oft approximiert werden. Eine nicht ganzzahlige Zahl könnte auch einen nicht periodischen und unendlich langen Bruchteil haben, der abgeschnitten werden muss, um in den Speicher eines Computers zu passen: Selbst wenn Computer heutzutage viel Speicher haben, ist der Speicherplatz immer noch begrenzt. Wahrscheinlich wissen Sie, dass der Unterschied zwischen den drei Gleitkomma-Datentypen von C/C++ im unterschiedlichen Bereich darstellbarer Zahlen und in der Genauigkeit besteht, mit der diese Zahlen approximiert werden, wenn sie nicht exakt in binärer Notation dargestellt werden können. Wahrscheinlich wissen Sie auch, dass jedem Gleitkomma-Datentyp eine zugehörige Datengröße zugeordnet ist, die durch die Anzahl der Bytes im Computerspeicher angegeben wird, die zum Speichern des Wertes einer Variablen dieses Datentyps benötigt werden. Nach dem Standard IEEE754 beträgt die Größe einer Float-Variablen 4 Bytes, die einer Double-Variablen 8 Bytes und die einer Long-Double-Variablen mindestens 10 Bytes, auch wenn die ANSI-Regeln für C und C++ nur verlangen dass die Größe einer Long-Double-Variablen nicht kleiner ist als die einer Double-Variablen. Die allgemeine Regel für die Größe eines Gleitkomma-Datentyps lautet, dass eine größere Größe einen größeren Bereich darstellbarer Werte und eine höhere Genauigkeit bei der internen Darstellung der Zahlen impliziert.

Grob gesagt führt die HPA-Bibliothek einen neuen Gleitkomma-Datentyp ein, der eine größere Größe (mindestens 16 Byte) hat und es ermöglicht, ein größeres Zahlenintervall mit einer höheren Genauigkeit darzustellen als die Standard-Gleitkomma-Datentypen.

Die normale Konfiguration der Bibliothek definiert einen erweiterten Gleitkomma-Datentyp mit einer Größe von 16 Bytes, die so aufgeteilt werden:

  • 1 Bit für das Vorzeichen (0 -> positiv, 1 -> negativ),
  • 15 Bit für den (verzerrten) Exponenten,
  • 112 Bit (= 7 Wörter mit 16 Bit Länge) Mantisse, entsprechend etwa 32 Dezimalstellen Genauigkeit.

Der Bereich der darstellbaren Zahlen ist dann gegeben durch

Die HPA-Bibliothek definiert auch einen komplexen Typ mit erweiterter Genauigkeit, der verwendet wird, um Berechnungen mit komplexen Zahlen zu verarbeiten. Aus Sicht der HPA-Bibliothek ist eine komplexe Zahl einfach eine Struktur, die aus zwei erweiterten Gleitkommazahlen gebildet wird, die jeweils den Real- und den Imaginärteil der komplexen Zahl darstellen.

Die Genauigkeit, die durch den erweiterten Gleitkomma-Datentyp der HPA-Bibliothek bereitgestellt wird, kann durch Anpassen der Größe ihrer Mantisse vor dem Kompilieren und Installieren der Bibliothek gesteuert werden. Normalerweise entspricht die Größe der Mantisse 7 Wörtern mit einer Länge von 16 Bit, wie oben erwähnt. Sie kann jedoch durch entsprechendes Setzen des Makros XDIM erhöht werden. XDIM definiert die Größe der Mantisse eines erweiterten Gleitkommawerts in Worten von 16 Bit Länge. XDIM kann einen der folgenden Werte annehmen: 7, 11, 15, 19, 23, 27 und 31. XDIM = 7 bedeutet ungefähr 33 Dezimalstellen Genauigkeit, XDIM = 31 ungefähr 149.

Nachdem die HPA-Bibliothek erstellt und installiert wurde, kann die Größe ihres erweiterten Gleitkomma-Datentyps nicht mehr geändert werden, es sei denn, Sie erstellen die Bibliothek neu und installieren sie erneut. Die HPA-Bibliothek bietet keine dynamische Größenänderung der Zahlen entsprechend der aktuell angeforderten Genauigkeit und der tatsächlichen Anzahl von Binärziffern, die zur Darstellung eines bestimmten Wertes erforderlich sind (Zahlen wie 2.0 oder 4.0 benötigen nur eine Stelle der Mantisse zur Darstellung). Wenn Sie nach ähnlichen Funktionen suchen, möchten Sie wirklich eine Bibliothek für arithmetische Operationen mit beliebiger Genauigkeit, und Sie sollten sie an anderer Stelle suchen, zum Beispiel hier.


5.4. Das Zufallsmodul¶

Wir wollen oft verwenden zufällige Zahlen bei Programmen. Hier sind einige typische Anwendungen:

Um ein Glücksspiel zu spielen, bei dem der Computer einige Würfel werfen, eine Zahl wählen oder eine Münze werfen muss,

Um ein Kartenspiel zufällig zu mischen,

Um zufällig ein neues feindliches Raumschiff erscheinen zu lassen und auf dich zu schießen,

Um mögliche Niederschläge zu simulieren, wenn wir ein computergestütztes Modell zur Schätzung der Umweltauswirkungen des Baus eines Staudamms erstellen,

Zum Verschlüsseln Ihrer Banking-Sitzung im Internet.

Python bietet ein Modul random, das bei solchen Aufgaben hilft. Sie können es in der Dokumentation nachlesen. Hier sind die wichtigsten Dinge, die wir damit machen können.

Drücken Sie die Run-Taste mehrmals. Beachten Sie, dass sich die Werte jedes Mal ändern. Das sind Zufallszahlen.

Die Funktion randrange generiert eine ganze Zahl zwischen ihrem unteren und ihrem oberen Argument, wobei dieselbe Semantik wie für range verwendet wird – die Untergrenze wird also eingeschlossen, die Obergrenze jedoch ausgeschlossen. Alle Werte haben die gleiche Eintrittswahrscheinlichkeit (d. h. die Ergebnisse sind gleichmäßig verteilt).

Die Funktion random() gibt eine Gleitkommazahl im Bereich [0.0, 1.0) zurück – die eckige Klammer bedeutet „links geschlossenes Intervall“ und die runde Klammer bedeutet „rechts offenes Intervall“. Mit anderen Worten, 0.0 ist möglich, aber alle zurückgegebenen Zahlen sind strikt kleiner als 1.0. Es ist üblich, Rahmen die Ergebnisse nach dem Aufrufen dieser Methode, um sie in einen für Ihre Anwendung geeigneten Bereich zu bringen.

Im hier gezeigten Fall haben wir das Ergebnis des Methodenaufrufs in eine Zahl im Bereich [0.0, 5.0] umgewandelt. Auch hier handelt es sich um gleichverteilte Zahlen – Zahlen nahe 0 sind genauso wahrscheinlich wie Zahlen nahe 0,5 oder Zahlen nahe 1,0. Wenn Sie die Run-Taste weiter drücken, werden zufällige Werte zwischen 0.0 und bis 5.0 angezeigt.

Es ist wichtig zu beachten, dass Zufallszahlengeneratoren auf a . basieren deterministisch Algorithmus — wiederholbar und vorhersehbar. Also heißen sie pseudozufällig Generatoren — sie sind nicht wirklich zufällig. Sie beginnen mit a Samen Wert. Jedes Mal, wenn Sie nach einer anderen Zufallszahl fragen, erhalten Sie eine basierend auf dem aktuellen Seed-Attribut, und der Zustand des Seeds (das eines der Attribute des Generators ist) wird aktualisiert. Die gute Nachricht ist, dass der Startwert jedes Mal, wenn Sie Ihr Programm ausführen, wahrscheinlich anders ist, was bedeutet, dass Sie bei jeder Ausführung wahrscheinlich ein zufälliges Verhalten erhalten, obwohl die Zufallszahlen algorithmisch erstellt werden.

Sinuswelle In dieser geführten Laborübung lassen wir die Schildkröte eine Sinuswelle zeichnen.

Überprüfen Sie Ihr Verständnis

module-4-3: Welche der folgenden Methoden ist der richtige Weg, um den Wert pi innerhalb des Mathematikmoduls zu referenzieren. Angenommen, Sie haben das Mathematikmodul bereits importiert.

module-4-4: Welches Modul würden Sie am ehesten verwenden, wenn Sie eine Funktion schreiben würden, um das Würfeln zu simulieren?

module-4-5: Der richtige Code zum Generieren einer Zufallszahl zwischen 1 und 100 (einschließlich) lautet:

module-4-6: Ein Grund dafür, dass Lotterien keine Computer verwenden, um Zufallszahlen zu generieren, ist:

Dieser Arbeitsbereich wird Ihnen zur Verfügung gestellt. Sie können dieses Activecode-Fenster verwenden, um alles auszuprobieren, was Sie möchten.


Inhalt

Das Konzept der Closures wurde in den 1960er Jahren für die mechanische Auswertung von Ausdrücken im λ-Kalkül entwickelt und 1970 erstmals vollständig als Sprachfeature in der Programmiersprache PAL implementiert, um erstklassige Funktionen mit lexikalischem Geltungsbereich zu unterstützen. [2]

Peter J. Landin definierte den Begriff Schließung im Jahr 1964 als mit einem Umweltteil und ein Steuerteil wie es von seiner SECD-Maschine zum Auswerten von Ausdrücken verwendet wird. [3] Joel Moses schreibt Landin die Einführung des Begriffs zu Schließung auf einen Lambda-Ausdruck verweisen, dessen offene Bindungen (freie Variablen) durch die lexikalische Umgebung geschlossen (oder darin gebunden) wurden, was zu a . führt geschlossener Ausdruck, oder Schließung. [4] [5] Diese Verwendung wurde später von Sussman und Steele übernommen, als sie 1975 Scheme definierten, [6] eine lexikalisch abgegrenzte Variante von Lisp, und wurde weit verbreitet.

Sussman und Abelson verwenden auch den Begriff Schließung in den 1980er Jahren mit einer zweiten, nicht verwandten Bedeutung: der Eigenschaft eines Operators, der Daten zu einer Datenstruktur hinzufügt, um auch verschachtelte Datenstrukturen hinzufügen zu können. This usage of the term comes from the mathematics usage rather than the prior usage in computer science. The authors consider this overlap in terminology to be "unfortunate." [7]

Der Begriff closure is often used as a synonym for anonymous function, though strictly, an anonymous function is a function literal without a name, while a closure is an instance of a function, a value, whose non-local variables have been bound either to values or to storage locations (depending on the language see the lexical environment section below).

For example, in the following Python code:

the values of a and b are closures, in both cases produced by returning a nested function with a free variable from the enclosing function, so that the free variable binds to the value of parameter x of the enclosing function. The closures in a and b are functionally identical. The only difference in implementation is that in the first case we used a nested function with a name, g , while in the second case we used an anonymous nested function (using the Python keyword lambda for creating an anonymous function). The original name, if any, used in defining them is irrelevant.

A closure is a value like any other value. It does not need to be assigned to a variable and can instead be used directly, as shown in the last two lines of the example. This usage may be deemed an "anonymous closure".

The nested function definitions are not themselves closures: they have a free variable which is not yet bound. Only once the enclosing function is evaluated with a value for the parameter is the free variable of the nested function bound, creating a closure, which is then returned from the enclosing function.

Lastly, a closure is only distinct from a function with free variables when outside of the scope of the non-local variables, otherwise the defining environment and the execution environment coincide and there is nothing to distinguish these (static and dynamic binding cannot be distinguished because the names resolve to the same values). For example, in the below program, functions with a free variable x (bound to the non-local variable x with global scope) are executed in the same environment where x is defined, so it is immaterial whether these are actually closures:

This is most often achieved by a function return, since the function must be defined within the scope of the non-local variables, in which case typically its own scope will be smaller.

This can also be achieved by variable shadowing (which reduces the scope of the non-local variable), though this is less common in practice, as it is less useful and shadowing is discouraged. In this example f can be seen to be a closure because x in the body of f is bound to the x in the global namespace, not the x local to g :

The use of closures is associated with languages where functions are first-class objects, in which functions can be returned as results from higher-order functions, or passed as arguments to other function calls if functions with free variables are first-class, then returning one creates a closure. This includes functional programming languages such as Lisp and ML, as well as many modern, multi-paradigm languages, such as Python and Rust. Closures are also frequently used with callbacks, particularly for event handlers, such as in JavaScript, where they are used for interactions with a dynamic web page.

Closures can also be used in a continuation-passing style to hide state. Constructs such as objects and control structures can thus be implemented with closures. In some languages, a closure may occur when a function is defined within another function, and the inner function refers to local variables of the outer function. At run-time, when the outer function executes, a closure is formed, consisting of the inner function's code and references (the upvalues) to any variables of the outer function required by the closure.

First-class functions Edit

Closures typically appear in languages with first-class functions—in other words, such languages enable functions to be passed as arguments, returned from function calls, bound to variable names, etc., just like simpler types such as strings and integers. For example, consider the following Scheme function:

In this example, the lambda expression (lambda (book) (>= (book-sales book) threshold)) appears within the function best-selling-books . When the lambda expression is evaluated, Scheme creates a closure consisting of the code for the lambda expression and a reference to the threshold variable, which is a free variable inside the lambda expression.

The closure is then passed to the filter function, which calls it repeatedly to determine which books are to be added to the result list and which are to be discarded. Because the closure itself has a reference to threshold , it can use that variable each time filter calls it. The function filter itself might be defined in a completely separate file.

Here is the same example rewritten in JavaScript, another popular language with support for closures:

The function keyword is used here instead of lambda , and an Array.filter method [8] instead of a global filter function, but otherwise the structure and the effect of the code are the same.

A function may create a closure and return it, as in the following example:

Because the closure in this case outlives the execution of the function that creates it, the variables f and dx live on after the function derivative returns, even though execution has left their scope and they are no longer visible. In languages without closures, the lifetime of an automatic local variable coincides with the execution of the stack frame where that variable is declared. In languages with closures, variables must continue to exist as long as any existing closures have references to them. This is most commonly implemented using some form of garbage collection.

State representation Edit

A closure can be used to associate a function with a set of "private" variables, which persist over several invocations of the function. The scope of the variable encompasses only the closed-over function, so it cannot be accessed from other program code. These are analogous to private variables in object-oriented programming, and in fact closures are analogous to a type of object, specifically function objects, with a single public method (function call), and possible many private variables (the bound variables).

In stateful languages, closures can thus be used to implement paradigms for state representation and information hiding, since the closure's upvalues (its closed-over variables) are of indefinite extent, so a value established in one invocation remains available in the next. Closures used in this way no longer have referential transparency, and are thus no longer pure functions nevertheless, they are commonly used in impure functional languages such as Scheme.

Other uses Edit

  • Because closures delay evaluation—i.e., they do not "do" anything until they are called—they can be used to define control structures. For example, all of Smalltalk's standard control structures, including branches (if/then/else) and loops (while and for), are defined using objects whose methods accept closures. Users can easily define their own control structures also.
  • In languages which implement assignment, multiple functions can be produced that close over the same environment, enabling them to communicate privately by altering that environment. In Scheme:

Note: Some speakers call any data structure that binds a lexical environment a closure, but the term usually refers specifically to functions.

Closures are typically implemented with a special data structure that contains a pointer to the function code, plus a representation of the function's lexical environment (i.e., the set of available variables) at the time when the closure was created. The referencing environment binds the non-local names to the corresponding variables in the lexical environment at the time the closure is created, additionally extending their lifetime to at least as long as the lifetime of the closure itself. When the closure is entered at a later time, possibly with a different lexical environment, the function is executed with its non-local variables referring to the ones captured by the closure, not the current environment.

A language implementation cannot easily support full closures if its run-time memory model allocates all automatic variables on a linear stack. In such languages, a function's automatic local variables are deallocated when the function returns. However, a closure requires that the free variables it references survive the enclosing function's execution. Therefore, those variables must be allocated so that they persist until no longer needed, typically via heap allocation, rather than on the stack, and their lifetime must be managed so they survive until all closures referencing them are no longer in use.

This explains why, typically, languages that natively support closures also use garbage collection. The alternatives are manual memory management of non-local variables (explicitly allocating on the heap and freeing when done), or, if using stack allocation, for the language to accept that certain use cases will lead to undefined behaviour, due to dangling pointers to freed automatic variables, as in lambda expressions in C++11 [10] or nested functions in GNU C. [11] The funarg problem (or "functional argument" problem) describes the difficulty of implementing functions as first class objects in a stack-based programming language such as C or C++. Similarly in D version 1, it is assumed that the programmer knows what to do with delegates and automatic local variables, as their references will be invalid after return from its definition scope (automatic local variables are on the stack) – this still permits many useful functional patterns, but for complex cases needs explicit heap allocation for variables. D version 2 solved this by detecting which variables must be stored on the heap, and performs automatic allocation. Because D uses garbage collection, in both versions, there is no need to track usage of variables as they are passed.

In strict functional languages with immutable data (z.B. Erlang), it is very easy to implement automatic memory management (garbage collection), as there are no possible cycles in variables' references. For example, in Erlang, all arguments and variables are allocated on the heap, but references to them are additionally stored on the stack. After a function returns, references are still valid. Heap cleaning is done by incremental garbage collector.

In ML, local variables are lexically scoped, and hence define a stack-like model, but since they are bound to values and not to objects, an implementation is free to copy these values into the closure's data structure in a way that is invisible to the programmer.

Scheme, which has an ALGOL-like lexical scope system with dynamic variables and garbage collection, lacks a stack programming model and does not suffer from the limitations of stack-based languages. Closures are expressed naturally in Scheme. The lambda form encloses the code, and the free variables of its environment persist within the program as long as they can possibly be accessed, and so they can be used as freely as any other Scheme expression. [ Zitat benötigt ]

Closures are closely related to Actors in the Actor model of concurrent computation where the values in the function's lexical environment are called acquaintances. An important issue for closures in concurrent programming languages is whether the variables in a closure can be updated and, if so, how these updates can be synchronized. Actors provide one solution. [12]

Closures are closely related to function objects the transformation from the former to the latter is known as defunctionalization or lambda lifting see also closure conversion. [ Zitat benötigt ]

Lexical environment Edit

As different languages do not always have a common definition of the lexical environment, their definitions of closure may vary also. The commonly held minimalist definition of the lexical environment defines it as a set of all bindings of variables in the scope, and that is also what closures in any language have to capture. However the meaning of a variable binding also differs. In imperative languages, variables bind to relative locations in memory that can store values. Although the relative location of a binding does not change at runtime, the value in the bound location can. In such languages, since closure captures the binding, any operation on the variable, whether done from the closure or not, are performed on the same relative memory location. This is often called capturing the variable "by reference". Here is an example illustrating the concept in ECMAScript, which is one such language:

Function foo and the closures referred to by variables f and g all use the same relative memory location signified by local variable x .

In some instances the above behaviour may be undesirable, and it is necessary to bind a different lexical closure. Again in ECMAScript, this would be done using the Function.bind() .

Example 1: Reference to an unbound variable Edit

Example 2: Accidental reference to a bound variable Edit

For this example the expected behaviour would be that each link should emit its id when clicked but because the variable 'e' is bound the scope above, and lazy evaluated on click, what actually happens is that each on click event emits the id of the last element in 'elements' bound at the end of the for loop. [14]

Again here variable e would need to be bound by the scope of the block using handle.bind(this) or the let keyword.

On the other hand, many functional languages, such as ML, bind variables directly to values. In this case, since there is no way to change the value of the variable once it is bound, there is no need to share the state between closures—they just use the same values. This is often called capturing the variable "by value". Java's local and anonymous classes also fall into this category—they require captured local variables to be final , which also means there is no need to share state.

Some languages enable you to choose between capturing the value of a variable or its location. For example, in C++11, captured variables are either declared with [&] , which means captured by reference, or with [=] , which means captured by value.

Yet another subset, lazy functional languages such as Haskell, bind variables to results of future computations rather than values. Consider this example in Haskell:

The binding of r captured by the closure defined within function foo is to the computation (x / y) —which in this case results in division by zero. However, since it is the computation that is captured, and not the value, the error only manifests itself when the closure is invoked, and actually attempts to use the captured binding.

Closure leaving Edit

Yet more differences manifest themselves in the behavior of other lexically scoped constructs, such as return , break and continue statements. Such constructs can, in general, be considered in terms of invoking an escape continuation established by an enclosing control statement (in case of break and continue , such interpretation requires looping constructs to be considered in terms of recursive function calls). In some languages, such as ECMAScript, return refers to the continuation established by the closure lexically innermost with respect to the statement—thus, a return within a closure transfers control to the code that called it. However, in Smalltalk, the superficially similar operator ^ invokes the escape continuation established for the method invocation, ignoring the escape continuations of any intervening nested closures. The escape continuation of a particular closure can only be invoked in Smalltalk implicitly by reaching the end of the closure's code. The following examples in ECMAScript and Smalltalk highlight the difference:

The above code snippets will behave differently because the Smalltalk ^ operator and the JavaScript return operator are not analogous. In the ECMAScript example, return x will leave the inner closure to begin a new iteration of the forEach loop, whereas in the Smalltalk example, ^x will abort the loop and return from the method foo .

Common Lisp provides a construct that can express either of the above actions: Lisp (return-from foo x) behaves as Smalltalk ^x , while Lisp (return-from nil x) behaves as JavaScript return x . Hence, Smalltalk makes it possible for a captured escape continuation to outlive the extent in which it can be successfully invoked. Consider:

When the closure returned by the method foo is invoked, it attempts to return a value from the invocation of foo that created the closure. Since that call has already returned and the Smalltalk method invocation model does not follow the spaghetti stack discipline to facilitate multiple returns, this operation results in an error.

Some languages, such as Ruby, enable the programmer to choose the way return is captured. An example in Ruby:

Both Proc.new and lambda in this example are ways to create a closure, but semantics of the closures thus created are different with respect to the return statement.

In Scheme, definition and scope of the return control statement is explicit (and only arbitrarily named 'return' for the sake of the example). The following is a direct translation of the Ruby sample.

Some languages have features which simulate the behavior of closures. In languages such as Java, C++, Objective-C, C#, VB.NET, and D, these features are the result of the language's object-oriented paradigm.

Callbacks (C) Edit

Some C libraries support callbacks. This is sometimes implemented by providing two values when registering the callback with the library: a function pointer and a separate void* pointer to arbitrary data of the user's choice. When the library executes the callback function, it passes along the data pointer. This enables the callback to maintain state and to refer to information captured at the time it was registered with the library. The idiom is similar to closures in functionality, but not in syntax. The void* pointer is not type safe so this C idiom differs from type-safe closures in C#, Haskell or ML.

Callbacks are extensively used in GUI Widget toolkits to implement Event-driven programming by associating general functions of graphical widgets (menus, buttons, check boxes, sliders, spinners, etc.) with application-specific functions implementing the specific desired behavior for the application.

Nested function and function pointer (C) Edit

With a gcc extension, a nested function can be used and a function pointer can emulate closures, providing the function does not exit the containing scope. The following example is invalid because adder is a top-level definition (depending by compiler version, it could produce a correct result if compiled without optimization, i.e. at -O0 ):

But moving adder (and, optionally, the typedef ) in main makes it valid:

If executed this now prints 11 as expected.

Local classes and lambda functions (Java) Edit

Java enables classes to be defined inside methods. These are called local classes. When such classes are not named, they are known as anonymous classes (or anonymous inner classes). A local class (either named or anonymous) may refer to names in lexically enclosing classes, or read-only variables (marked as final ) in the lexically enclosing method.

The capturing of final variables enables you to capture variables by value. Even if the variable you want to capture is non- final , you can always copy it to a temporary final variable just before the class.

Capturing of variables by reference can be emulated by using a final reference to a mutable container, for example, a single-element array. The local class will not be able to change the value of the container reference itself, but it will be able to change the contents of the container.

With the advent of Java 8's lambda expressions, [15] the closure causes the above code to be executed as:

Local classes are one of the types of inner class that are declared within the body of a method. Java also supports inner classes that are declared as non-static members of an enclosing class. [16] They are normally referred to just as "inner classes". [17] These are defined in the body of the enclosing class and have full access to instance variables of the enclosing class. Due to their binding to these instance variables, an inner class may only be instantiated with an explicit binding to an instance of the enclosing class using a special syntax. [18]

Upon execution, this will print the integers from 0 to 9. Beware to not confuse this type of class with the nested class, which is declared in the same way with an accompanied usage of the "static" modifier those have not the desired effect but are instead just classes with no special binding defined in an enclosing class.

As of Java 8, Java supports functions as first class objects. Lambda expressions of this form are considered of type Function<T,U> with T being the domain and U the image type. The expression can be called with its .apply(T t) method, but not with a standard method call.

Blocks (C, C++, Objective-C 2.0) Edit

Apple introduced blocks, a form of closure, as a nonstandard extension into C, C++, Objective-C 2.0 and in Mac OS X 10.6 "Snow Leopard" and iOS 4.0. Apple made their implementation available for the GCC and clang compilers.

Pointers to block and block literals are marked with ^ . Normal local variables are captured by value when the block is created, and are read-only inside the block. Variables to be captured by reference are marked with __block . Blocks that need to persist outside of the scope they are created in may need to be copied. [19] [20]

Delegates (C#, VB.NET, D) Edit

C# anonymous methods and lambda expressions support closure:

Visual Basic .NET, which has many language features similar to those of C#, also supports lambda expressions with closures:

In D, closures are implemented by delegates, a function pointer paired with a context pointer (e.g. a class instance, or a stack frame on the heap in the case of closures).

D version 1, has limited closure support. For example, the above code will not work correctly, because the variable a is on the stack, and after returning from test(), it is no longer valid to use it (most probably calling foo via dg(), will return a 'random' integer). This can be solved by explicitly allocating the variable 'a' on heap, or using structs or class to store all needed closed variables and construct a delegate from a method implementing the same code. Closures can be passed to other functions, as long as they are only used while the referenced values are still valid (for example calling another function with a closure as a callback parameter), and are useful for writing generic data processing code, so this limitation, in practice, is often not an issue.

This limitation was fixed in D version 2 - the variable 'a' will be automatically allocated on the heap because it is used in the inner function, and a delegate of that function can escape the current scope (via assignment to dg or return). Any other local variables (or arguments) that are not referenced by delegates or that are only referenced by delegates that do not escape the current scope, remain on the stack, which is simpler and faster than heap allocation. The same is true for inner's class methods that reference a function's variables.

Function objects (C++) Edit

C++ enables defining function objects by overloading operator() . These objects behave somewhat like functions in a functional programming language. They may be created at runtime and may contain state, but they do not implicitly capture local variables as closures do. As of the 2011 revision, the C++ language also supports closures, which are a type of function object constructed automatically from a special language construct called lambda-expression. A C++ closure may capture its context either by storing copies of the accessed variables as members of the closure object or by reference. In the latter case, if the closure object escapes the scope of a referenced object, invoking its operator() causes undefined behavior since C++ closures do not extend the lifetime of their context.

Inline agents (Eiffel) Edit

Eiffel includes inline agents defining closures. An inline agent is an object representing a routine, defined by giving the code of the routine in-line. For example, in

the argument to subscribe is an agent, representing a procedure with two arguments the procedure finds the country at the corresponding coordinates and displays it. The whole agent is "subscribed" to the event type click_event for a certain button, so that whenever an instance of the event type occurs on that button – because a user has clicked the button – the procedure will be executed with the mouse coordinates being passed as arguments for x and y .

The main limitation of Eiffel agents, which distinguishes them from closures in other languages, is that they cannot reference local variables from the enclosing scope. This design decision helps in avoiding ambiguity when talking about a local variable value in a closure - should it be the latest value of the variable or the value captured when the agent is created? Only Current (a reference to current object, analogous to this in Java), its features, and arguments of the agent itself can be accessed from within the agent body. The values of the outer local variables can be passed by providing additional closed operands to the agent.

C++Builder __closure reserved word Edit

Embarcadero C++Builder provides the reserve word __closure to provide a pointer to a method with a similar syntax to a function pointer. [21]

In standard C you could write a typedef for a pointer to a function type using the following syntax:


22 Answers 22

In GNU libm, the implementation of sin is system-dependent. Therefore you can find the implementation, for each platform, somewhere in the appropriate subdirectory of sysdeps.

One directory includes an implementation in C, contributed by IBM. Since October 2011, this is the code that actually runs when you call sin() on a typical x86-64 Linux system. It is apparently faster than the fsin assembly instruction. Source code: sysdeps/ieee754/dbl-64/s_sin.c, look for __sin (double x) .

This code is very complex. No one software algorithm is as fast as possible and also accurate over the whole range of x values, so the library implements several different algorithms, and its first job is to look at x and decide which algorithm to use.

Wann x is very sehr close to 0, sin(x) == x is the right answer.

A bit further out, sin(x) uses the familiar Taylor series. However, this is only accurate near 0, so.

When the angle is more than about 7°, a different algorithm is used, computing Taylor-series approximations for both sin(x) and cos(x), then using values from a precomputed table to refine the approximation.

When |x| > 2, none of the above algorithms would work, so the code starts by computing some value closer to 0 that can be fed to sin or cos instead.

There's yet another branch to deal with x being a NaN or infinity.

This code uses some numerical hacks I've never seen before, though for all I know they might be well-known among floating-point experts. Sometimes a few lines of code would take several paragraphs to explain. For example, these two lines

are used (sometimes) in reducing x to a value close to 0 that differs from x by a multiple of &pi/2, specifically xn × &pi/2. The way this is done without division or branching is rather clever. But there's no comment at all!

Older 32-bit versions of GCC/glibc used the fsin instruction, which is surprisingly inaccurate for some inputs. There's a fascinating blog post illustrating this with just 2 lines of code.

fdlibm's implementation of sin in pure C is much simpler than glibc's and is nicely commented. Source code: fdlibm/s_sin.c and fdlibm/k_sin.c

Functions like sine and cosine are implemented in microcode inside microprocessors. Intel chips, for example, have assembly instructions for these. A C compiler will generate code that calls these assembly instructions. (By contrast, a Java compiler will not. Java evaluates trig functions in software rather than hardware, and so it runs much slower.)

Chips do not use Taylor series to compute trig functions, at least not entirely. First of all they use CORDIC, but they may also use a short Taylor series to polish up the result of CORDIC or for special cases such as computing sine with high relative accuracy for very small angles. For more explanation, see this StackOverflow answer.

OK kiddies, time for the pros. This is one of my biggest complaints with inexperienced software engineers. They come in calculating transcendental functions from scratch (using Taylor's series) as if nobody had ever done these calculations before in their lives. Not true. This is a well defined problem and has been approached thousands of times by very clever software and hardware engineers and has a well defined solution. Basically, most of the transcendental functions use Chebyshev Polynomials to calculate them. As to which polynomials are used depends on the circumstances. First, the bible on this matter is a book called "Computer Approximations" by Hart and Cheney. In that book, you can decide if you have a hardware adder, multiplier, divider, etc, and decide which operations are fastest. z.B. If you had a really fast divider, the fastest way to calculate sine might be P1(x)/P2(x) where P1, P2 are Chebyshev polynomials. Without the fast divider, it might be just P(x), where P has much more terms than P1 or P2. so it'd be slower. So, first step is to determine your hardware and what it can do. Then you choose the appropriate combination of Chebyshev polynomials (is usually of the form cos(ax) = aP(x) for cosine for example, again where P is a Chebyshev polynomial). Then you decide what decimal precision you want. z.B. if you want 7 digits precision, you look that up in the appropriate table in the book I mentioned, and it will give you (for precision = 7.33) a number N = 4 and a polynomial number 3502. N is the order of the polynomial (so it's p4.x^4 + p3.x^3 + p2.x^2 + p1.x + p0), because N=4. Then you look up the actual value of the p4,p3,p2,p1,p0 values in the back of the book under 3502 (they'll be in floating point). Then you implement your algorithm in software in the form: (((p4.x + p3).x + p2).x + p1).x + p0 . and this is how you'd calculate cosine to 7 decimal places on that hardware.

Note that most hardware implementations of transcendental operations in an FPU usually involve some microcode and operations like this (depends on the hardware). Chebyshev polynomials are used for most transcendentals but not all. z.B. Square root is faster to use a double iteration of Newton raphson method using a lookup table first. Again, that book "Computer Approximations" will tell you that.

If you plan on implmementing these functions, I'd recommend to anyone that they get a copy of that book. It really is the bible for these kinds of algorithms. Note that there are bunches of alternative means for calculating these values like cordics, etc, but these tend to be best for specific algorithms where you only need low precision. To guarantee the precision every time, the chebyshev polynomials are the way to go. Like I said, well defined problem. Has been solved for 50 years now. and thats how it's done.

Now, that being said, there are techniques whereby the Chebyshev polynomials can be used to get a single precision result with a low degree polynomial (like the example for cosine above). Then, there are other techniques to interpolate between values to increase the accuracy without having to go to a much larger polynomial, such as "Gal's Accurate Tables Method". This latter technique is what the post referring to the ACM literature is referring to. But ultimately, the Chebyshev Polynomials are what are used to get 90% of the way there.


Mathematical Functions

Inexact equality comparison: true if norm(x-y) <= max(atol, rtol*max(norm(x), norm(y))) . The default atol is zero and the default rtol depends on the types of x and y . The keyword argument nans determines whether or not NaN values are considered equal (defaults to false).

For real or complex floating-point values, if an atol > 0 is not specified, rtol defaults to the square root of eps of the type of x or y , whichever is bigger (least precise). This corresponds to requiring equality of about half of the significand digits. Otherwise, e.g. for integer arguments or if an atol > 0 is supplied, rtol defaults to zero.

x and y may also be arrays of numbers, in which case norm defaults to the usual norm function in LinearAlgebra, but may be changed by passing a norm::Function keyword argument. (For numbers, norm is the same thing as abs .) When x and y are arrays, if norm(x-y) is not finite (i.e. ±Inf or NaN ), the comparison falls back to checking whether all elements of x and y are approximately equal component-wise.

The binary operator ≈ is equivalent to isapprox with the default arguments, and x ≉ y is equivalent to !isapprox(x,y) .

Note that x ≈ 0 (i.e., comparing to zero with the default tolerances) is equivalent to x == 0 since the default atol is 0 . In such cases, you should either supply an appropriate atol (or use norm(x) ≤ atol ) or rearrange your code (e.g. use x ≈ y rather than x - y ≈ 0 ). It is not possible to pick a nonzero atol automatically because it depends on the overall scaling (the "units") of your problem: for example, in x - y ≈ 0 , atol=1e-9 is an absurdly small tolerance if x is the radius of the Earth in meters, but an absurdly large tolerance if x is the radius of a Hydrogen atom in meters.

Create a function that compares its argument to x using ≈ , i.e. a function equivalent to y -> y ≈ x .

The keyword arguments supported here are the same as those in the 2-argument isapprox .

Compute sine of x , where x is in radians.

Compute cosine of x , where x is in radians.

Simultaneously compute the sine and cosine of x , where the x is in radians.

Compute tangent of x , where x is in radians.

Compute sine of x , where x is in degrees.

Compute cosine of x , where x is in degrees.

Compute tangent of x , where x is in degrees.

Compute $sin(pi x)$ more accurately than sin(pi*x) , especially for large x .

Compute $cos(pi x)$ more accurately than cos(pi*x) , especially for large x .

Compute hyperbolic sine of x .

Compute hyperbolic cosine of x .

Compute hyperbolic tangent of x .

Compute the inverse sine of x , where the output is in radians.

Compute the inverse cosine of x , where the output is in radians

Compute the inverse tangent of y or y/x , respectively.

For one argument, this is the angle in radians between the positive x-axis and the point (1, ja), returning a value in the interval $[-pi/2, pi/2]$ .

For two arguments, this is the angle in radians between the positive x-axis and the point (x, ja), returning a value in the interval $[-pi, pi]$ . This corresponds to a standard atan2 function.

Compute the inverse sine of x , where the output is in degrees.

Compute the inverse cosine of x , where the output is in degrees.

Compute the inverse tangent of y or y/x , respectively, where the output is in degrees.

Compute the secant of x , where x is in radians.

Compute the cosecant of x , where x is in radians.

Compute the cotangent of x , where x is in radians.

Compute the secant of x , where x is in degrees.

Compute the cosecant of x , where x is in degrees.

Compute the cotangent of x , where x is in degrees.

Compute the inverse secant of x , where the output is in radians.

Compute the inverse cosecant of x , where the output is in radians.

Compute the inverse cotangent of x , where the output is in radians.

Compute the inverse secant of x , where the output is in degrees.

Compute the inverse cosecant of x , where the output is in degrees.

Compute the inverse cotangent of x , where the output is in degrees.

Compute the hyperbolic secant of x .

Compute the hyperbolic cosecant of x .

Compute the hyperbolic cotangent of x .

Compute the inverse hyperbolic sine of x .

Compute the inverse hyperbolic cosine of x .

Compute the inverse hyperbolic tangent of x .

Compute the inverse hyperbolic secant of x .

Compute the inverse hyperbolic cosecant of x .

Compute the inverse hyperbolic cotangent of x .

Compute $sin(pi x) / (pi x)$ if $x eq 0$ , and $1$ if $x = 0$ .

Compute $cos(pi x) / x - sin(pi x) / (pi x^2)$ if $x eq 0$ , and

Customizable binary operators

Some unicode characters can be used to define new binary operators that support infix notation. For example ⊗(x,y) = kron(x,y) defines the ⊗ (otimes) function to be the Kronecker product, and one can call it as binary operator using infix syntax: C = A ⊗ B as well as with the usual prefix syntax C = ⊗(A,B) .

Other characters that support such extensions include odot ⊙ and oplus ⊕

The complete list is in the parser code: https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm

Those that are parsed like * (in terms of precedence) include * / ÷ % & ⋅ ∘ × || ∩ ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛ ⊍ ▷ ⨝ ⟕ ⟖ ⟗ and those that are parsed like + include + - ||| ⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ ⩛ ⩝ ⩡ ⩢ ⩣ There are many others that are related to arrows, comparisons, and powers.

$ if $x = 0$ . This is the derivative of sinc(x) .

Convert x from degrees to radians.

Convert x from radians to degrees.

Compute the hypotenuse $sqrt<|x|^2+|y|^2>$ avoiding overflow and underflow.

This code is an implementation of the algorithm described in: An Improved Algorithm for hypot(a,b) by Carlos F. Borges The article is available online at ArXiv at the link https://arxiv.org/abs/1904.09481

Compute the hypotenuse $sqrt$ avoiding overflow and underflow.

Compute the natural logarithm of x . Throws DomainError for negative Real arguments. Use complex negative arguments to obtain complex results.

Compute the base b logarithm of x . Throws DomainError for negative Real arguments.

If b is a power of 2 or 10, log2 or log10 should be used, as these will typically be faster and more accurate. For example,

Compute the logarithm of x to base 2. Throws DomainError for negative Real arguments.

Compute the logarithm of x to base 10. Throws DomainError for negative Real arguments.

Accurate natural logarithm of 1+x . Throws DomainError for Real arguments less than -1.

Return (x,exp) such that x has a magnitude in the interval $[1/2, 1)$ or 0, and val is equal to $x imes 2^$ .

Compute the natural base exponential of x , in other words $e^x$ .

Compute the base 2 exponential of x , in other words $2^x$ .

Compute the base 10 exponential of x , in other words $10^x$ .

Return a tuple (fpart, ipart) of the fractional and integral parts of a number. Both parts have the same sign as the argument.

Without keyword arguments, x is rounded to an integer value, returning a value of type T , or of the same type of x if no T is provided. An InexactError will be thrown if the value is not representable by T , similar to convert .

If the digits keyword argument is provided, it rounds to the specified number of digits after the decimal place (or before if negative), in base base .

If the sigdigits keyword argument is provided, it rounds to the specified number of significant digits, in base base .

The RoundingMode r controls the direction of the rounding the default is RoundNearest , which rounds to the nearest integer, with ties (fractional values of 0.5) being rounded to the nearest even integer. Note that round may give incorrect results if the global rounding mode is changed (see rounding ).

Rounding to specified digits in bases other than 2 can be inexact when operating on binary floating point numbers. For example, the Float64 value represented by 1.15 is actually Weniger than 1.15, yet will be rounded to 1.2.


What's the Kan's theorem about geometric realization functor in algebraic geometry?

Denote $mathbf$ the category of locally ringed spaces (where the stalks of morphisms are local morphisms), and $mathbf$ a small category of (commutative unital) rings. For $Xinmathbf$, there is a functor $S_Xcolonmathbf omathbf$ specified by $S_X(A)=mathbf(operatornameA,X)$, which induces a functor $Scolonmathbf omathbf(mathbf,mathbf)$.

(Notation: Given a category $mathcal C$, $mathcal C(X,Y)$ is the set of morphisms between $X,Yinmathcal C$. We assume that all categories are locally small)

The theorem of existence of geometric realization, claims that $S$ has a left adjoint, called the geometric realization functor. In Demazure & Gabriel's Introduction to Algebraic Geometry and Algebraic Groups, they claim that it is a particular case of a well-known theorem of Kan. However, they sketch a proof for this special case.

I wonder what is the well-known Theorem von Kan in Frage? Der von ihnen skizzierte Beweis funktioniert in der Richtung, dass jeder Funktor $mathbf omathbf$ ist ein Colimit von darstellbaren Funktoren (über Kategorie von Elementen) und für darstellbare Funktoren $mathbf(A,-)$, sie definieren nur die geometrische Realisierung als $operatornameA$.


Schau das Video: Konvex, Konkav, Krümmung bei Funktionen, Übersicht und Berechnung. Mathe by Daniel Jung (Oktober 2021).