990 lines
27 KiB
Plaintext
990 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "69149570",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Ein Fallbeispiel: Der parametrisierte Datentyp PComplex\n",
|
|
"\n",
|
|
"Wir wollen als neuen numerischen Typen **komplexe Zahlen in Polardarstellung $z=r e^{i\\phi}=(r,ϕ)$** einführen. \n",
|
|
"\n",
|
|
"- Der Typ soll sich in die Typhierarchie einfügen als Subtyp von 'Number'.\n",
|
|
"- $r$ und $\\phi$ sollen Gleitkommazahlen sein. (Im Unterschied zu komplexen Zahlen in 'kartesischen' Koordinaten hat eine Einschränkung auf ganzzahlige Werte von r oder ϕ mathematisch wenig Sinn.)\n",
|
|
"\n",
|
|
"## Die Definition von `PComplex`\n",
|
|
"\n",
|
|
"Ein erster Versuch könnte so aussehen:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "3ee9c4ae",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"z1 = PComplex1{Float64}(-32.0, 33.0)\n",
|
|
"z2 = PComplex1{Float32}(12.0f0, 13.0f0)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"struct PComplex1{T <: AbstractFloat} <: Number\n",
|
|
" r :: T\n",
|
|
" ϕ :: T\n",
|
|
"end\n",
|
|
"\n",
|
|
"z1 = PComplex1(-32.0, 33.0)\n",
|
|
"z2 = PComplex1{Float32}(12, 13)\n",
|
|
"@show z1 z2;"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "729a1d8e",
|
|
"metadata": {},
|
|
"source": [
|
|
"Julia stellt automatisch *default constructors* zur Verfügung:\n",
|
|
"\n",
|
|
"- den Konstruktor `PComplex1`, bei dem der Typ `T` von den übergebenen Argumenten abgeleitet wird und\n",
|
|
"- Konstruktoren `PComplex{Float64},...` mit expliziter Typangabe. Hier wird versucht, die Argumente in den angeforderten Typ zu konvertieren. \n",
|
|
"\n",
|
|
"------\n",
|
|
"\n",
|
|
"Wir wollen nun, dass der Konstruktor noch mehr tut. \n",
|
|
"In der Polardarstellung soll $0\\le r$ und $0\\le \\phi<2\\pi$ gelten.\n",
|
|
"\n",
|
|
"Wenn die übergebenen Argumente das nicht erfüllen, sollten sie entsprechend umgerechnet werden.\n",
|
|
"\n",
|
|
"Dazu definieren wir einen _inner constructor_, der den _default constructor_ ersetzt.\n",
|
|
"\n",
|
|
"- Ein _inner constructor_ ist eine Funktion innerhalb der `struct`-Definition.\n",
|
|
"- In einem _inner constructor_ kann man die spezielle Funktion `new` verwenden, die wie der _default constructor_ wirkt.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "214480f6",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"struct PComplex{T <: AbstractFloat} <: Number\n",
|
|
" r :: T\n",
|
|
" ϕ :: T\n",
|
|
"\n",
|
|
" function PComplex{T}(r::T, ϕ::T) where T<:AbstractFloat\n",
|
|
" if r<0 # flip the sign of r and correct phi\n",
|
|
" r = -r\n",
|
|
" ϕ += π\n",
|
|
" end\n",
|
|
" if r==0 ϕ=0 end # normalize r=0 case to phi=0 \n",
|
|
" ϕ = mod(ϕ, 2π) # map phi into interval [0,2pi)\n",
|
|
" new(r, ϕ) # new() ist special function,\n",
|
|
" end # available only inside inner constructors\n",
|
|
"\n",
|
|
"end"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "873d5dc6",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#| echo: false\n",
|
|
"#| output: false\n",
|
|
"\n",
|
|
"#=\n",
|
|
"in den ganzen quarto-runs wollen bir hier noch das default-show benutzen\n",
|
|
"=#\n",
|
|
"zz = @which Base.show(stdout, PComplex{Float64}(2.,3.))\n",
|
|
"if zz.module != Base\n",
|
|
" Base.delete_method(zz)\n",
|
|
"end"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "95c7386c",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"PComplex{Float64}(3.3, 1.0)"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"z1 = PComplex{Float64}(-3.3, 7π+1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "eb213995",
|
|
"metadata": {},
|
|
"source": [
|
|
"Für die explizite Angabe eines *inner constructors* müssen wir allerdings einen Preis zahlen: Die sonst von Julia bereitgestellten *default constructors* fehlen. \n",
|
|
"\n",
|
|
"Den Konstruktor, der ohne explizite Typangabe in geschweiften Klammern auskommt und den Typ der Argumente übernimmt, wollen wir gerne auch haben:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "e87d3a42",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"PComplex{Float64}(2.0, 0.3)"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)\n",
|
|
"\n",
|
|
"z2 = PComplex(2.0, 0.3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0512b019",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Eine neue Schreibweise\n",
|
|
"\n",
|
|
"Julia verwendet `//` als Infix-Konstruktor für den Typ `Rational`. Sowas Schickes wollen wir auch. \n",
|
|
"\n",
|
|
"In der Elektronik/Elektrotechnik werden [Wechselstromgrößen durch komplexe Zahlen beschrieben.](https://de.wikipedia.org/wiki/Komplexe_Wechselstromrechnung). Dabei ist eine Darstellung komplexer Zahlen durch \"Betrag\" und \"Phase\" üblich und sie wird gerne in der sogenannten [Versor-Form](https://de.wikipedia.org/wiki/Versor) (engl. *phasor*) dargestellt:\n",
|
|
"$$\n",
|
|
" z= r\\enclose{phasorangle}{\\phi} = 3.4\\;\\enclose{phasorangle}{45^\\circ}\n",
|
|
"$$\n",
|
|
"wobei man in der Regel den Winkel in Grad notiert. \n",
|
|
"\n",
|
|
":::{.callout-note .titlenormal collapse=\"true\"}\n",
|
|
"\n",
|
|
"## Mögliche Infix-Operatoren in Julia\n",
|
|
"\n",
|
|
"In Julia ist eine große Anzahl von Unicode-Zeichen reserviert für die Verwendung als Operatoren. Die definitive Liste ist im [Quellcode des Parsers.](https://github.com/JuliaLang/julia/blob/eaa2c58aeb12f27c1d8c116ab111773a4fc4495f/src/julia-parser.scm#L13-L31)\n",
|
|
"\n",
|
|
"Auf Details werden wir in einem späteren Kapitel noch eingehen. \n",
|
|
"\n",
|
|
"Und ja, der Julia-Parser ist in einem Lisp(genauer: Scheme)-Dialekt geschrieben. In Julia ist ein kleiner Scheme-Interpreter namens [femtolisp](https://github.com/JeffBezanson/femtolisp) integriert. Geschrieben hat ihn einer der \"Väter\" von Julia bevor er mit der Arbeit an Julia begann. \n",
|
|
"\n",
|
|
":::\n",
|
|
"\n",
|
|
"Das Winkel-Zeichen `∠` steht leider nicht als Operatorsymbol zur Verfügung. Wir weichen aus auf `⋖`. Das kann in Julia als als `\\lessdot<tab>` eingegeben werden.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "81818d42",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"PComplex{Float64}(2.0, 1.5707963267948966)"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
" ⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)\n",
|
|
"\n",
|
|
"z3 = 2. ⋖ 90."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bf0863e9",
|
|
"metadata": {},
|
|
"source": [
|
|
"(Die Typ-Annotation -- `Real` statt `AbstractFloat` -- ist ein Vorgriff auf kommende weitere Konstruktoren. Im Moment funktioniert der Operator `⋖` erstmal nur mit `Float`s.)\n",
|
|
"\n",
|
|
"\n",
|
|
"Natürlich wollen wir auch die Ausgabe so schön haben. Details dazu findet man in der [Dokumentation](https://docs.julialang.org/en/v1/manual/types/#man-custom-pretty-printing).\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "4cf73e28",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"z3 = 2.0⋖90.0°\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"using Printf\n",
|
|
"\n",
|
|
"function Base.show(io::IO, z::PComplex)\n",
|
|
" # wir drucken die Phase in Grad, auf Zehntelgrad gerundet, \n",
|
|
" p = z.ϕ * 180/π\n",
|
|
" sp = @sprintf \"%.1f\" p\n",
|
|
" print(io, z.r, \"⋖\", sp, '°')\n",
|
|
"end\n",
|
|
"\n",
|
|
"@show z3;"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0dae2fef",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Methoden für `PComplex`\n",
|
|
"\n",
|
|
"Damit unser Typ ein anständiges Mitglied der von `Number` abstammenden Typfamilie wird, brauchen wir allerdings noch eine ganze Menge mehr. Es müssen Arithmetik, Vergleichsoperatoren, Konvertierungen usw. definiert werden. \n",
|
|
"\n",
|
|
"\n",
|
|
"Wir beschränken uns auf Multiplikation und Quadratwurzeln.\n",
|
|
"\n",
|
|
"\n",
|
|
":::{.callout-note collapse=\"true\"}\n",
|
|
"## Module \n",
|
|
"\n",
|
|
"- Um die `methods` der existierenden Funktionen und Operationen zu ergänzen, muss man diese mit ihrem 'vollen Namen' ansprechen.\n",
|
|
"- Alle Objekte gehören zu einem Namensraum oder `module`.\n",
|
|
"- Die meisten Basisfunktionen gehören zum Modul `Base`, welches standardmäßig immer ohne explizites `using ...` geladen wird. \n",
|
|
"- Solange man keine eigenen Module definiert, sind die eigenen Definitionen im Modul `Main`.\n",
|
|
"- Das Macro `@which`, angewendet auf einen Namen, zeigt an, in welchem Modul der Name definiert wurde. \n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "042418fd",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"Main"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"f(x) = 3x^3\n",
|
|
"@which f"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"id": "c20d10e5",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Modul für Addition: Base, Modul für sqrt: Base\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"wp = @which +\n",
|
|
"ws = @which(sqrt)\n",
|
|
"println(\"Modul für Addition: $wp, Modul für sqrt: $ws\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "82fe20b4",
|
|
"metadata": {},
|
|
"source": [
|
|
":::\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 28,
|
|
"id": "a53f0e79",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"qwurzel (generic function with 1 method)"
|
|
]
|
|
},
|
|
"execution_count": 28,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"qwurzel(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 29,
|
|
"id": "d6987594",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#| echo: false\n",
|
|
"#| output: false\n",
|
|
"\n",
|
|
"#=\n",
|
|
"damit das length(methods(sqrt)) klappt\n",
|
|
"=#\n",
|
|
"if hasmethod(sqrt, (PComplex,))\n",
|
|
" zz = @which Base.sqrt(PComplex{Float64}(1.,1.))\n",
|
|
" Base.delete_method(zz)\n",
|
|
"end"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "53f4bb77",
|
|
"metadata": {},
|
|
"source": [
|
|
"Die Funktion `sqrt()` hat schon einige Methoden:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 30,
|
|
"id": "f93f63c6",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"19"
|
|
]
|
|
},
|
|
"execution_count": 30,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"length(methods(sqrt))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7b4f323a",
|
|
"metadata": {},
|
|
"source": [
|
|
"Jetzt wird es eine Methode mehr:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 32,
|
|
"id": "b88cd74b",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"20"
|
|
]
|
|
},
|
|
"execution_count": 32,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"Base.sqrt(z::PComplex) = qwurzel(z)\n",
|
|
"\n",
|
|
"length(methods(sqrt))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 33,
|
|
"id": "e077b8bf",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"1.4142135623730951⋖8.6°"
|
|
]
|
|
},
|
|
"execution_count": 33,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"sqrt(z2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e223fdd1",
|
|
"metadata": {},
|
|
"source": [
|
|
"und nun zur Multiplikation:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "5537f7ec",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"z1 * z2 = 6.6⋖74.5°\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)\n",
|
|
"\n",
|
|
"@show z1 * z2;"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7189f0c6",
|
|
"metadata": {},
|
|
"source": [
|
|
"(Da das Operatorsymbol kein normaler Name ist, muss der Doppelpunkt bei der Zusammensetzung mit `Base.` sein.)\n",
|
|
"\n",
|
|
"Wir können allerdings noch nicht mit anderen numerischen Typen multiplizieren. Dazu könnte man nun eine Vielzahl von entsprechenden Methoden definieren. Julia stellt *für numerische Typen* noch einen weiteren Mechanismus zur Verfügung, der das etwas vereinfacht.\n",
|
|
"\n",
|
|
"\n",
|
|
"## Typ-Promotion und Konversion \n",
|
|
"\n",
|
|
"In Julia kann man bekanntlich die verschiedensten numerischen Typen nebeneinander verwenden.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"id": "610a38fe",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"265.53333333333336"
|
|
]
|
|
},
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"1//3 + 5 + 5.2 + 0xff"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bc39d089",
|
|
"metadata": {},
|
|
"source": [
|
|
"Wenn man in die zahlreichen Methoden schaut, die z.B. für `+` und `*` definiert sind, findet man u.a. eine Art 'catch-all-Definition'\n",
|
|
"\n",
|
|
"```julia\n",
|
|
"+(x::Number, y::Number) = +(promote(x,y)...)\n",
|
|
"*(x::Number, y::Number) = *(promote(x,y)...)\n",
|
|
"```\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"(Die 3 Punkte sind der splat-Operator, der das von promote() zurückgegebene Tupel wieder in seine Bestandteile zerlegt.)\n",
|
|
"\n",
|
|
"Da die Methode mit den Typen `(Number, Number)` sehr allgemein ist, wird sie erst verwendet, wenn spezifischere Methoden nicht greifen.\n",
|
|
"\n",
|
|
"Was passiert hier?\n",
|
|
"\n",
|
|
"### Die Funktion `promote(x,y,...)`\n",
|
|
"\n",
|
|
"Diese Funktion versucht, alle Argumente in einen gemeinsamen Typen umzuwandeln, der alle Werte (möglichst) exakt darstellen kann.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"id": "48cae0ae",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(12.0, 34.555, 0.7777777777777778, 255.0)"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"promote(12, 34.555, 77/99, 0xff)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"id": "768934ac",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"z = (33, 27)\n",
|
|
"typeof(z) = Tuple{BigInt, BigInt}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"z = promote(BigInt(33), 27)\n",
|
|
"@show z typeof(z);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "df2672c5",
|
|
"metadata": {},
|
|
"source": [
|
|
"Die Funktion `promote()` verwendet dazu zwei Helfer, die Funktionen\n",
|
|
" `promote_type(T1, T2)` und `convert(T, x)`\n",
|
|
"\n",
|
|
"Wie üblich in Julia, kann man diesen Mechanismus durch [eigene *promotion rules* und `convert(T,x)`-Methoden erweitern.](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/) \n",
|
|
"\n",
|
|
"\n",
|
|
"### Die Funktion `promote_type(T1, T2,...)` \n",
|
|
"\n",
|
|
"Sie ermittelt, zu welchem Typ umgewandelt werden soll. Argumente sind Typen, nicht Werte.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"id": "70306adc",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"promote_type(Rational{Int64}, ComplexF64, Float32) = ComplexF64\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"@show promote_type(Rational{Int64}, ComplexF64, Float32);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "ec999b3a",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Die Funktion `convert(T,x)`\n",
|
|
"\n",
|
|
"Die Methoden von \n",
|
|
"`convert(T, x)` wandeln `x` in ein Objekt vom Typ `T` um. Dabei sollte eine solche Umwandlung verlustfrei möglich sein.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"id": "b326a121",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"3.0"
|
|
]
|
|
},
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"z = convert(Float64, 3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 19,
|
|
"id": "57606643",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"23"
|
|
]
|
|
},
|
|
"execution_count": 19,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"z = convert(Int64, 23.00)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 20,
|
|
"id": "a3b39504",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"ename": "LoadError",
|
|
"evalue": "InexactError: Int64(2.3)",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"InexactError: Int64(2.3)",
|
|
"",
|
|
"Stacktrace:",
|
|
" [1] Int64",
|
|
" @ ./float.jl:912 [inlined]",
|
|
" [2] convert(::Type{Int64}, x::Float64)",
|
|
" @ Base ./number.jl:7",
|
|
" [3] top-level scope",
|
|
" @ In[20]:1"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"z = convert(Int64, 2.3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "06251c1d",
|
|
"metadata": {},
|
|
"source": [
|
|
"Die spezielle Rolle von `convert()` liegt darin, dass es an verschiedenen Stellen _implizit_ und automatisch eingesetzt wird: \n",
|
|
"\n",
|
|
"> [The following language constructs call convert](https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#When-is-convert-called?):\n",
|
|
"> \n",
|
|
" - Assigning to an array converts to the array's element type.\n",
|
|
" - Assigning to a field of an object converts to the declared type of the field.\n",
|
|
" - Constructing an object with new converts to the object's declared field types.\n",
|
|
" - Assigning to a variable with a declared type (e.g. local x::T) converts to that type.\n",
|
|
" - A function with a declared return type converts its return value to that type.\n",
|
|
"\n",
|
|
"-- und natürlich in `promote()`\n",
|
|
"\n",
|
|
"Für selbstdefinierte Datentypen kann man convert() um weitere Methoden ergänzen.\n",
|
|
"\n",
|
|
"Für Datentypen innerhalb der Number-Hierarchie gibt es wieder eine 'catch-all-Definition'\n",
|
|
"```julia\n",
|
|
"convert(::Type{T}, x::Number) where {T<:Number} = T(x)\n",
|
|
"```\n",
|
|
"\n",
|
|
"Also: Wenn für einen Typen `T` aus der Hierarchie `T<:Number` ein Konstruktor `T(x)` mit einem numerischen Argument `x` existiert, dann wird dieser Konstruktor `T(x)` automatisch für Konvertierungen benutzt. (Natürlich können auch speziellere Methoden für `convert()` definiert werden, die dann Vorrang haben.)\n",
|
|
"\n",
|
|
"\n",
|
|
"### Weitere Konstruktoren für `PComplex`\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 21,
|
|
"id": "e258330e",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"PComplex"
|
|
]
|
|
},
|
|
"execution_count": 21,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"## (a) r, ϕ beliebige Reals, z.B. Integers, Rationals\n",
|
|
"\n",
|
|
"PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} = \n",
|
|
" PComplex{T}(convert(T, r), convert(T, ϕ))\n",
|
|
"\n",
|
|
"PComplex(r::T1, ϕ::T2) where {T1<:Real, T2<: Real} = \n",
|
|
" PComplex{promote_type(Float64, T1, T2)}(r, ϕ) \n",
|
|
"\n",
|
|
"## (b) Zur Umwandlung von Reals: Konstruktor mit \n",
|
|
"## nur einem Argument r\n",
|
|
"\n",
|
|
"PComplex{T}(r::S) where {T<:AbstractFloat, S<:Real} = \n",
|
|
" PComplex{T}(convert(T, r), convert(T, 0)) \n",
|
|
"\n",
|
|
"PComplex(r::S) where {S<:Real} = \n",
|
|
" PComplex{promote_type(Float64, S)}(r, 0.0)\n",
|
|
"\n",
|
|
"## (c) Umwandlung Complex -> PComplex\n",
|
|
"\n",
|
|
"PComplex{T}(z::Complex{S}) where {T<:AbstractFloat, S<:Real} = \n",
|
|
" PComplex{T}(abs(z), angle(z))\n",
|
|
"\n",
|
|
"PComplex(z::Complex{S}) where {S<:Real} = \n",
|
|
" PComplex{promote_type(Float64, S)}(abs(z), angle(z))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "ccb96089",
|
|
"metadata": {},
|
|
"source": [
|
|
"Ein Test der neuen Konstruktoren:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 22,
|
|
"id": "ec747779",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.6⋖45.0°, 1.4142135623730951⋖45.0°, 13.0⋖180.0°)"
|
|
]
|
|
},
|
|
"execution_count": 22,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"3//5 ⋖ 45, PComplex(Complex(1,1)), PComplex(-13) "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "fd8541cf",
|
|
"metadata": {},
|
|
"source": [
|
|
"Wir brauchen nun noch *promotion rules*, die festlegen, welcher Typ bei `promote(x::T1, y::T2)` herauskommen soll. Damit wird `promote_type()` intern um die nötigen weiteren Methoden erweitert. \n",
|
|
"\n",
|
|
"### *Promotion rules* für `PComplex`\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 24,
|
|
"id": "806fff6a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"Base.promote_rule(::Type{PComplex{T}}, ::Type{S}) where {T<:AbstractFloat,S<:Real} = \n",
|
|
" PComplex{promote_type(T,S)}\n",
|
|
"\n",
|
|
"Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where \n",
|
|
" {T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "97c7de69",
|
|
"metadata": {},
|
|
"source": [
|
|
"1. Regel:\n",
|
|
": Wenn ein `PComplex{T}` und ein `S<:Real` zusammentreffen, dann sollen beide zu `PComplex{U}` umgewandelt werden, wobei `U` der Typ ist, zu dem `S` und `T` beide umgewandelt (_promoted_) werden können. \n",
|
|
"\n",
|
|
"2. Regel\n",
|
|
": Wenn ein `PComplex{T}` und ein `Complex{S}` zusammentreffen, dann sollen beide zu `PComplex{U}` umgewandelt werden, wobei `U` der Typ ist, zu dem `S` und `T` beide umgewandelt werden können. \n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Damit klappt nun die Multiplikation mit beliebigen numerischen Typen.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 25,
|
|
"id": "cc47939d",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(2.0⋖90.0°, 6.0⋖90.0°)"
|
|
]
|
|
},
|
|
"execution_count": 25,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"z3, 3z3"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"id": "63c20bc7",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(43.26661530556787⋖64.0°, 16.970562748477143⋖8.6°)"
|
|
]
|
|
},
|
|
"execution_count": 26,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"(3.0+2im) * (12⋖30.3), 12sqrt(z2) "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e94c6b15",
|
|
"metadata": {},
|
|
"source": [
|
|
":::{.callout-caution icon=\"false\" collapse=\"true\" .titlenormal}\n",
|
|
"\n",
|
|
"## Zusammenfassung: unser Typ `PComplex`\n",
|
|
"\n",
|
|
"```julia\n",
|
|
"struct PComplex{T <: AbstractFloat} <: Number\n",
|
|
" r :: T\n",
|
|
" ϕ :: T\n",
|
|
"\n",
|
|
" function PComplex{T}(r::T, ϕ::T) where T<:AbstractFloat\n",
|
|
" if r<0 # flip the sign of r and correct phi\n",
|
|
" r = -r\n",
|
|
" ϕ += π\n",
|
|
" end\n",
|
|
" if r==0 ϕ=0 end # normalize r=0 case to phi=0 \n",
|
|
" ϕ = mod(ϕ, 2π) # map phi into interval [0,2pi)\n",
|
|
" new(r, ϕ) # new() ist special function,\n",
|
|
" end # available only inside inner constructors\n",
|
|
"\n",
|
|
"end\n",
|
|
"\n",
|
|
"# additional constructors\n",
|
|
"PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)\n",
|
|
"\n",
|
|
"\n",
|
|
"PComplex{T}(r::T1, ϕ::T2) where {T<:AbstractFloat, T1<:Real, T2<: Real} = \n",
|
|
" PComplex{T}(convert(T, r), convert(T, ϕ))\n",
|
|
"\n",
|
|
"PComplex(r::T1, ϕ::T2) where {T1<:Real, T2<: Real} = \n",
|
|
" PComplex{promote_type(Float64, T1, T2)}(r, ϕ) \n",
|
|
"\n",
|
|
"\n",
|
|
"PComplex{T}(r::S) where {T<:AbstractFloat, S<:Real} = \n",
|
|
" PComplex{T}(convert(T, r), convert(T, 0)) \n",
|
|
"\n",
|
|
"PComplex(r::S) where {S<:Real} = \n",
|
|
" PComplex{promote_type(Float64, S)}(r, 0.0)\n",
|
|
"\n",
|
|
"\n",
|
|
"PComplex{T}(z::Complex{S}) where {T<:AbstractFloat, S<:Real} = \n",
|
|
" PComplex{T}(abs(z), angle(z))\n",
|
|
"\n",
|
|
"PComplex(z::Complex{S}) where {S<:Real} = \n",
|
|
" PComplex{promote_type(Float64, S)}(abs(z), angle(z))\n",
|
|
"\n",
|
|
"# nice input\n",
|
|
"⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)\n",
|
|
"\n",
|
|
"# nice output\n",
|
|
"using Printf\n",
|
|
"\n",
|
|
"function Base.show(io::IO, z::PComplex)\n",
|
|
" # wir drucken die Phase in Grad, auf Zehntelgrad gerundet, \n",
|
|
" p = z.ϕ * 180/π\n",
|
|
" sp = @sprintf \"%.1f\" p\n",
|
|
" print(io, z.r, \"⋖\", sp, '°')\n",
|
|
"end\n",
|
|
"\n",
|
|
"# arithmetic\n",
|
|
"Base.sqrt(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)\n",
|
|
"\n",
|
|
"Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)\n",
|
|
"\n",
|
|
"# promotion rules\n",
|
|
"Base.promote_rule(::Type{PComplex{T}}, ::Type{S}) where \n",
|
|
" {T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}\n",
|
|
"\n",
|
|
"Base.promote_rule(::Type{PComplex{T}}, ::Type{Complex{S}}) where \n",
|
|
" {T<:AbstractFloat,S<:Real} = PComplex{promote_type(T,S)}\n",
|
|
"```\n",
|
|
":::\n",
|
|
"\n",
|
|
":::{.content-hidden unless-format=\"xxx\"}\n",
|
|
"\n",
|
|
"Jetzt geht sowas wie `PComplex(1, 0)` noch nicht. Wir wollen auch andere reelle Typen für `r` und `ϕ` zulassen. Der Einfachheit halber wandeln wir hier alles nach `Float64` um. Analog verfahren wir auch, wenn nur ein reelles oder komplexes Argument verwendet wird.\n",
|
|
"\n",
|
|
"```julia\n",
|
|
"PComplex(r::Real, ϕ::Real) = PComplex(Float64(r), Float64(ϕ))\n",
|
|
"PComplex(r::Real) = PComplex(Float64(r), 0.0)\n",
|
|
"PComplex(z::Complex) = PComplex(abs(z), angle(z))\n",
|
|
"\n",
|
|
"z3 = PComplex(-2); z4 = PComplex(3im)\n",
|
|
"@show z3 z4;\n",
|
|
"```\n",
|
|
"\n",
|
|
":::"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Julia 1.10.2",
|
|
"language": "julia",
|
|
"name": "julia-1.10"
|
|
},
|
|
"language_info": {
|
|
"file_extension": ".jl",
|
|
"mimetype": "application/julia",
|
|
"name": "julia",
|
|
"version": "1.10.2"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|