747 lines
23 KiB
Plaintext
747 lines
23 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3b9e97dc",
|
|
"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": null,
|
|
"id": "b00fa817",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"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": "00603f51",
|
|
"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": null,
|
|
"id": "d583f845",
|
|
"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": "e3d6ada4",
|
|
"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": null,
|
|
"id": "55f6b7dc",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"z1 = PComplex{Float64}(-3.3, 7π+1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "99f6d6e1",
|
|
"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": null,
|
|
"id": "e6c36f18",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"PComplex(r::T, ϕ::T) where {T<:AbstractFloat} = PComplex{T}(r,ϕ)\n",
|
|
"\n",
|
|
"z2 = PComplex(2.0, 0.3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5d77812c",
|
|
"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": null,
|
|
"id": "a4fc6d3c",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
" ⋖(r::Real, ϕ::Real) = PComplex(r, π*ϕ/180)\n",
|
|
"\n",
|
|
"z3 = 2. ⋖ 90."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c19f6e25",
|
|
"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": null,
|
|
"id": "13e750ef",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"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": "e1c7d6fa",
|
|
"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": null,
|
|
"id": "373829e0",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"f(x) = 3x^3\n",
|
|
"@which f"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "a4490292",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"wp = @which +\n",
|
|
"ws = @which(sqrt)\n",
|
|
"println(\"Modul für Addition: $wp, Modul für sqrt: $ws\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d49ec514",
|
|
"metadata": {},
|
|
"source": [
|
|
":::\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "d2186aeb",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"qwurzel(z::PComplex) = PComplex(sqrt(z.r), z.ϕ / 2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "5d93d3ca",
|
|
"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": "07ed8595",
|
|
"metadata": {},
|
|
"source": [
|
|
"Die Funktion `sqrt()` hat schon einige Methoden:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1ecfd1ac",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"length(methods(sqrt))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f619c771",
|
|
"metadata": {},
|
|
"source": [
|
|
"Jetzt wird es eine Methode mehr:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "56a4ce0f",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"Base.sqrt(z::PComplex) = qwurzel(z)\n",
|
|
"\n",
|
|
"length(methods(sqrt))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "195204e7",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"sqrt(z2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c912bcd2",
|
|
"metadata": {},
|
|
"source": [
|
|
"und nun zur Multiplikation:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "8c648a2d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"Base.:*(x::PComplex, y::PComplex) = PComplex(x.r * y.r, x.ϕ + y.ϕ)\n",
|
|
"\n",
|
|
"@show z1 * z2;"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "79074b01",
|
|
"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": null,
|
|
"id": "ff3ee027",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"1//3 + 5 + 5.2 + 0xff"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f43ef8c6",
|
|
"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": null,
|
|
"id": "8f1d4d57",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"promote(12, 34.555, 77/99, 0xff)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "27c2724e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"z = promote(BigInt(33), 27)\n",
|
|
"@show z typeof(z);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8347acad",
|
|
"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": null,
|
|
"id": "fe551c95",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"@show promote_type(Rational{Int64}, ComplexF64, Float32);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e4c08732",
|
|
"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": null,
|
|
"id": "5d6aabe4",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"z = convert(Float64, 3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "8b5205d2",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"z = convert(Int64, 23.00)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "0d4b80ce",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"z = convert(Int64, 2.3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3e3d953b",
|
|
"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": null,
|
|
"id": "2f9e1ebe",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"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": "c831e4c3",
|
|
"metadata": {},
|
|
"source": [
|
|
"Ein Test der neuen Konstruktoren:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "8103dc03",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"3//5 ⋖ 45, PComplex(Complex(1,1)), PComplex(-13) "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c85b8d7a",
|
|
"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": null,
|
|
"id": "8e6a1e94",
|
|
"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": "4ad30167",
|
|
"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": null,
|
|
"id": "9e72f683",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"z3, 3z3"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "f0aec53e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"(3.0+2im) * (12⋖30.3), 12sqrt(z2) "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0d881cc4",
|
|
"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.8.5",
|
|
"language": "julia",
|
|
"name": "julia-1.8"
|
|
},
|
|
"language_info": {
|
|
"file_extension": ".jl",
|
|
"mimetype": "application/julia",
|
|
"name": "julia",
|
|
"version": "1.8.5"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|