נראה לי שאין בשלב זה עוד מה לפרט על שיטות ליצור Proxies. בהמשך כשנדבר על Interception נדבר על שיטות Interception שמרחיבות את העיסוק.
יש עוד שימוש נפלא בProxies שטרם ציינתי. השימוש הוא ביצירת Mockים.
למי שלא מכיר, Mockים הם אובייקטים שאנחנו בד”כ משתמשים בהם בUnit Testים על מנת לדמות התנהגות של תלות חיצונית.
למשל, נניח שיש לנו מחלקה שאמורה להתחבר לDatabase ולשלוף טבלאות. את החיבור לDatabase היא מבצעת באמצעות איזשהו ממשק, למשל
1
2
3
4
publicinterfaceIDataProvider
{
DataTable RetrieveBySql(string query);
}
כעת אנחנו מעוניינים לבדוק את המחלקה שמשתמשת בממשק הזה.
בUnit Test לא נרצה להתחבר לDatabase, מכמה סיבות:
זו פעולה שלוקחת זמן
זו פעולה שיכולה להכשל
לכן אם העיצוב של הקוד שלנו מספיק טוב, נוכל לבצע את הטריק הבא:
נדחוף לInstance של המחלקה שלנו איזשהו Instance של מחלקה שמממשת IDataProvider שנממש אנו, עם התנהגות משלנו – כלומר במחלקה זו לא יהיה חיבור לDatabase, אלא היא תמומש בצורה אחרת, למשל תחזיר תמיד אותו ערך, או מימוש אחר.
למחלקה שנממש קוראים בד"כ Mock. כאמור Mockים משמשים בעיקר בUnit Testים.
עד כה דיברנו על מה זה Mock באופן כללי.
עכשיו לבעיה: מה קורה אם יש לנו הרבה ממשקים שאנחנו צריכים לעשות להם Mock? מה אם יש להם הרבה פונקציות ואנחנו לא מעוניינים לממש את כולן, אלא רק את חלקן?
יוצא שאנחנו כותבים הרבה מאוד Mockים, ויכול להיות שMock מסוים שהשקענו בו הרבה עבודה משמש אותנו בUnit Test בודד.
כאן Proxyים נכנסים לתמונה:
מה שאנחנו יכולים לעשות זה ליצור Proxy שמה שהוא עושה זה קורא לפונקציה ידועה שמה שהיא עושה זה ניגשת לאיזשהו מבנה נתונים עם המידע על הפונקציה שנקראה והפרמטרים איתם נקראה, וממנו מקבלת איזשהו ערך – זה יהיה ערך ההחזר של הפונקציה.
הדבר הזה מאפשר יכולות נחמדות, כמו למשל ליצור Mock שמחזיר ערך ידוע עבור קריאה לפונקציה עם פרמטרים ידועים.
למעשה, אין צורך להמציא את הגלגל – יש Frameworkים מצוינים (שאפילו מתבססים על DynamicProxy) שמאפשרים את היכולת הזאת.
אחד הFrameworkים האלה הוא Moq המאפשר לנו לעשות את זה באמצעות Expression Trees של Framework 3.5 (ראו גם טיפים 173-181)
למשל דוגמה ליצירת Mock:
1
2
3
4
5
6
7
Mock<IFoo> mock = new Mock<IFoo>();
string outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString))
.Returns(true);
כעת נוכל לקבל אובייקט שמממש IFoo ע"י:
1
2
3
4
5
6
7
IFoo mockObject = mock.Object;
string outValue;
bool parsed =
mockObject.TryParse("ping", out outValue); // true
// outValue == "ack"
בקיצור, Moq מומלץ בחום לכל מי שנאלץ לכתוב Unit Testים 😃
אינו פעם שעברה איך ניתן לממש Proxy כלשהו בעזרת RealProxy של הFramework.
למרבה הצער, ראינו שזה מלווה בקריאות איטיות מאוד למתודות. (פי 500 יותר איטיות בערך)
קיימת עוד דרך לממש Proxy דינאמי והיא באמצעות Reflection.Emit.
Reflection.Emit היא בעצם ספריה המאפשרת לנו לקמפל קוד בזמן ריצה. הדבר הזה כולל בין השאר את הדברים הבאים:
יצירת מתודות בזמן ריצה - זה קשור לקימפול Expressionים בזמן ריצה. למעשה Expressionים משתמשים בReflection.Emit כדי להתקמפל. ראו גם טיפים על Expressionים: טיפים מספר 173-181.
יצירת טיפוסים בזמן ריצה.
די קשה להשתמש בReflection.Emit, כי שימוש בזה מצריך כתיבת IL שכנראה רובנו לא דוברים אותה באופן שוטף.
עם זאת, השימוש בReflection.Emit מאפשר לעשות דברים נפלאים.
למשל, נוכל ליצור טיפוס בזמן ריצה שמממש איזשהו ממשק ומפנה כל קריאה אליו לאיזשהו Delegate.
כך בעצם נוכל לממש Proxy בעצמנו בצורה דינאמית.
מאחר ואני לא מבין חובבי כתיבת IL, אני לא אראה כאן איך כותבים דבר כזה.
במקום זאת, אני אספר על איזשהו פרויקט Open source שנקרא DynamicProxy של Castle. זוהי ספריה שעושה את כל התהליך שדיברתי עליו למעלה:
היא יודעת ליצור טיפוס המממש ממשק בצורה של Proxy, כלומר לקרוא למתודה מתאימה בכל קריאה למתודה של הממשק.
איך משתמשים בזה? זה די פשוט: צריך לממש את הממשק ששמו IInterceptor, שם נכתוב מה אנחנו רוצים לעשות כשתקרא מתודה כלשהי (זו בעצם הפונקציה שעושים אליה Proxy, בדומה לInvoke שראינו פעם שעברה):
השני הוא להתחיל לפתוח ולחקור את העולם של Interception (זו הכללה לProxy)
אני אתחיל בסקירת שיטות למימוש Proxy.
כמו שראינו, הדרך הפשוטה ביותר לממש Proxy הוא לממש מחלקה המממשת ממשק ומנתבת את כלל המתודות למתודה מסוימת. זה קצת עבודה, אבל אפשרי לעשות זאת בלי יותר מדי רקע וידע.
הבעיה העיקרית בפתרון הזה, היא שצריך לממש עבור כל סוג ממשק מחלקה כזאת, כלומר לא נוכל לכתוב Proxy גנרי כלשהו. כמובן, זה בלתי מתקבל על הדעת ברוב המקרים, שהרי אם WCF למשל היה דורש מאיתנו לכתוב איזשהו מימוש, דיפולטי ככל שיהיה, לשליחת הודעות לשרת והחזרת תשובה, לא היינו משתמשים בו.
אז מה אפשר לעשות?
הדרך הראשונה לפתור את הבעיה שנסקור היא ע”י ירושה מRealProxy. זוהי מחלקה מהFramework, שכפי שרומז שמה, מאפשרת לנו ליצור Proxyים בצורה פשוטה. כל מה שעלינו לעשות זה לרשת מRealProxy ולדרוס את המתודה ששמה Invoke:
// No return value yet (doesn't deal with functions)
returnnew ReturnMessage(null, methodCall);
}
}
כעת כדי להשתמש בזה פשוט ניצור Instance ונקרא לפונקציה GetTransparentProxy:
1
2
3
4
5
6
7
8
9
10
MyProxy proxyProvider = new MyProxy(typeof(IBank));
IBank bank = (IBank)proxyProvider.GetTransparentProxy();
bank.Deposit("stam", 100);
//id := stam
//amount := 100
bank.Deposit("yossi", 1024);
//id := yossi
//amount := 1024
מגניב ביותר!
אפשר גם לשנות את ערכי ההחזר (כולל הפרמטרים שהם out), ולקבוע Exception וכו’ בעזרת הממשק IMethodReturnMessage (יש טיפוס שנקרא ReturnMessage שמממש אותו שהשתמשנו בו)
איך הדבר הזה עובד?
למעשה יש איזשהו טיפול בCLR שבודק שאם אובייקט מסוים הוא RealProxy, אז מתבצעת הקריאה למתודה לא בצורה הקלאסית שאנחנו מכירים: כל המידע שהועבר לפונקציה בקריאה מועבר לאיזשהו IMessage המכיל את התוכן המתאים, ונקראת הפונקציה Invoke עם הIMessage הנ"ל.
הבעיה העיקרית בשיטה הזו היא שהיא הרבה יותר איטית מקריאה רגילה לפונקציה. אם נערוך איזשהי השוואה נראה כי ההבדלים בין קריאה למתודה ריקה (ללא תוכן) ממחלקה שמממשת ממשק לעומת ממחלקה שיוצרת את הממשק דרך RealProxy הם:
Number of calls
Dummy (in milliseconds)
RealProxy (in milliseconds)
10
0
0
100
0
0
1000
0
4
10000
0
66
100000
0
549
1000000
9
4927
10000000
99
50100
יוצא שהקריאות של דרך RealProxy הן יותר מפי 500 יותר איטיות מקריאות למתודה.
מבחינת ביצועים זה לא להיט, אבל בהחלט יש שימוש בזה בFramework. עד כדי כך, שאפילו הProxyים שאנחנו מקבלים מChannelFactory של WCF ממומשים באמצעות RealProxy.
כעת יצרנו Proxy לממשק, כלומר יש לנו מחלקה שמנתבת את כל הפונקציות של הממשק לפונקציה בודדת.
1
2
3
privatevoidHandle(MethodCallInfo methodCallInfo)
{
}
מה שאנחנו מסוגלים לעשות עם זה, זה להעביר את הקריאות האלה למתודה חיצונית – מתודה שתקרא על מחשב אחר למשל.
איך נעשה את זה? נצטרך להמיר את הMethodCallInfo לאיזשהו אובייקט שאפשר להעביר ברשת.
במחשב השני תהיה אפליקציה שתאזין להודעות כאלה ותקרא לפונקציה מתאימה.
בנוסף, אפליקציה זו תחזיר הודעה שמתארת את התוצאה של הפונקציה, למשל מה ערך ההחזר, או האם קפץ Exception.
בפונקציה שאליה נכנס (Handle) נצטרך לבצע את ההמרה של הMethodCallInfo להודעה מתאימה, לשלוח את ההודעה, לקבל את התשובה, ולהכניס לMethodCallInfo את התוצאה שחזרה/Exception שחזר.
קיימים מספר מימושים לרעיון זה, למשל WCF הוא Framework המאפשר ליצור Proxy שיודע לשלוח הודעות SOAP לService מרוחק ולהחזיר תשובה.
[מבוסס על הפוסט הזה] למי שיצא לעבוד עם WPF, יצא בוודאי להתקל בXAML (Extensible Application Markup Language) – זהו תקן שמשחק תפקיד חשוב בWPF ומאפשר לנו לבנות יישומים בצורה שניתנת להרחבה בצורה פשוטה.
רובנו לא מכירים, אבל החל מFramework 4.0, נוספה תמיכה בתקן דומה שנקרא CSAML (C# Application Markup Language, נהגה לעתים כScammel) – זהו תקן המהווה אלטרנטיבה לSyntax של C# המבוסס על Xmlו-XAML. למעשה, CSAML משתמש בSyntax של הProperty Element של XAML בשביל לייצג הצהרות מסובכות.
שפה זו בעצם יוצרת מיזוג בין C# וXml ומייצגת קפיצה משמעותית בהתפתחות של שני התקנים, ואף מייצג פיתוח שטרם נראה כמוהו ב”Xmlיזציה” של מידע טקסטואלי.
כעת נראה מספר דוגמאות לשימוש בCSAML:
הסתכלו על התוכנית Hello World עם הSyntax המיושן של C# שכולנו מכירים:
1
2
3
4
5
6
7
8
9
10
namespaceMyNamespace
{
publicclassMyClass
{
publicstaticvoidMain()
{
Console.WriteLine("Hello, C#");
}
}
}
שימו לב לפורמט חסר המבנה החופשי הזה – לא ניתן לסבול אותו בסביבות מחשוב מודרניות: תסתכלו על רצף המילים "public static void Main". מהן המילים האלו? איך הן קשורות אחת לשנייה? אין שום דרך לדעת. מהבעיות הללו התחמקו בימי ALGOL, אבל כיום כבר לא ניתן להתעלם מהן.
מצד שני, הסתכלו על הבהירות של התוצאות כאשר הקוד השקול מבוטא בSyntax שמייצג דיוק והתחשבות. זהו קובץ CSAML עם ההצהרות של הnamespace הנוכחי:
כפי שבוודאי תשימו לב, רוב שמות הElementים כן מבוססים על הספציפיקציה של שפת C#, כך שכל מתכנת C# שמכיר את השמות מהספציפיקציה, יכול להתחיל לתכנת בCSAML באופן מיידי.
ע"י כך שנשענת על המבנה ההיררכי של Xml, שפת CSAML מסוגלת לוותר על על הסוגריים המסולסלים, הסוגריים המרובעים וסוגריים שמאפיינים את הSyntax המוכר של C#. כפי ששמתם לב בקובץ לעיל, סוגריים מסולסלים שמורים לשימוש שלהם בהרחבה של XAML, כמו x:Type.
למעשה, CSAML מסוגלת להעיף כל Symbol שקיים בSyntax המוכר של C#. למשל, שימו לב להשמה הבאה בSyntax הישן של C#:
1
A = 5 * (B + 27 * C);
בלי יותר מדי בעיות, הSyntax הזה מתרגם לCSAML הזה:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<ExpressionStatement>
<AssignmentLValue="A">
<Assignment.Expression>
<MultiplicationExpression>
<Multiplication.Multiplier>
<LiteralType="{x:Type Int32}"
Value="5" />
</Multiplication.Multiplier>
<Multiplication.Multiplicand>
<AdditionExpressionAugend="B">
<AdditionExpression.Addend>
<Multiplication.Multiplier>
<LiteralType="{x:Type Int32}"
Value="27" />
</Multiplication.Multiplier>
<MultiplicationExpressionMultiplicand="C"/>
</AdditionExpression.Addend>
</AdditionExpression>
</Multiplication.Multiplicand>
</MultiplicationExpression>
</Assignment.Expression>
</Assignment>
</ExpressionStatement>
היתרון של ייצוג כזה ברור:
בגלל שאין צורך (ואפילו אסור) שימוש בסוגריים, המתכנת מרכיב את הCSAML תוך התחשבות זהירה בבעיה.
יש סיכוי גדול יותר ששגיאות קימפול לא יופיעו.
הקומפיילר הופך להיות הרבה יותר יעיל, בגלל שכל הצהרה, "פורסרה" ע"י המתכנת.
הנה עוד דוגמה לבעיה האבסורדית האולי הכי ידועה לשמצה בכל תכנות C# מסורתי: לולאת הfor:
1
2
3
4
5
6
7
for (i = 0, j = 0; i < 1000; i++)
{
if (IsPrime(i))
{
j++;
}
}
בCSAML האוסף של הSymbolים והנקודות-פסיק הנ"ל ננטש לטובת מבנה יפהפה שיכול לגרום למתכנת המודרני להתמוגג מאושר:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<ForLoop>
<ForLoop.Initializer>
<StatementExpressionList>
<AssignmentLValue="i">
<Assignment.Expression>
<LiteralType="{x:Type Int32}"
Value="0" />
</Assignment.Expression>
</Assignment>
</StatementExpressionList>
</ForLoop.Initializer>
<ForLoop.Condition>
<BooleanExpression>
<LessThanExpressionLeftSide ="i">
<LessThanExpression.RightSide>
<LiteralType="{x:Type Int32}"
Value="1000" />
</LessThanExpression.RightSide>
</LessThanExpression>
</BooleanExpression>
</ForLoop.Condition>
<ForLoop.Iterator>
<StatementExpressionList>
<PreIncrementExpressionIdentifier="i" />
</StatementExpressionList>
</ForLoop.Iterator>
<ForLoop.EmbeddedStatement>
<IfStatement>
<IfStatement.Condition>
<BooleanExpression>
<InvocationExpressionMemberAccess="IsPrime">
<InvocationExpression.ArgumentList>
<VariableIdentifier="i" />
</InvocationExpression.ArgumentList>
</InvocationExpression>
</BooleanExpression>
</IfStatement.Condition>
<IfStatement.EmbeddedStatement>
<StatementList>
<PreIncrementExpressionIdentifier="j" />
</StatementList>
</IfStatement.EmbeddedStatement>
</IfStatement>
</ForLoop.EmbeddedStatement>
</ForLoop>
בהמשך נראה עוד שימושים מדהימים לCSAML.
בסופו של הדבר הכוח האדיר של שפה זו היא שהיא מאפשרת לנו להרחיב ולתקן את האפליקציה שלנו ללא שינוי קוד!
הפעם נדבר טיפה יותר על מה היינו רוצים מProxy שאנחנו מממשים:
כאמור – כאשר אני אומר Proxy, אני מתכוון למימוש של ממשק שמקיים שכל המתודות פונות בסופו של דבר למתודה ספציפית.
היינו מצפים שלמתודה הזאת נקבל את המידע הבא:
המתודה שהופעלה – אבל אנחנו רוצים יותר מאת השם, אלא את הOverload המתאים שנקרא. כלומר, כנראה את הMethodInfo של המתודה המתאימה שהופעלה.
הפרמטרים שאיתם נקראה המתודה – מדובר במבנה המכיל את הפרמטרים שנשלחו למתודה. ניתן לשלוח את זה בתור מיפוי כלשהו, או בתור מערך שאליו ניגש לפי הסדר של הפרמטרים בMethodInfo כדי למצוא פרמטר ספציפי.
בנוסף, היינו מצפים לקבל את הפרמטרים הגנריים של המתודה, אם יש כאלה. אותם אנחנו יכולים לקבל גם בתוך הMethodInfo בהנחה שקראו לMakeGenericType.
בנוסף, היינו מצפים ליכולות הבאות:
לקבוע את ערך ההחזר של המתודה שהופעלה
לשנות את הפרמטרים שהם ref או out
לזרוק Exception ביציאה מהפונקציה
בכל מקרה, היינו מצפים שהפונקציה שאליה שאר הפונקציות עושות Proxy תקבל איזה ממשק שמספק פונקציונאליות כזאת.
יש כמה מימושים כאלה, ביניהם:
MethodCall שנמצא בRemoting
IInvocation שנמצא בOpen Source בשם DynamicProxy של Castle
בפעמים הבאות נמשיך לדבר על Proxyים ובהמשך נראה את השימושים שהבטחתי
אני מתחיל היום סדרה חדשה שמתעסקת בProxies ומה שמסביב.
למה אני מתכוון כשאני אומר Proxies?
נניח שיש לנו איזשהו ממשק. עכשיו, אנחנו מעוניינים ליצור טיפוס המממש את הממשק, אבל לספק איזושהי התנהגות כללית שתקרה כאשר יקראו לפונקציות של הממשק. לדוגמה, נניח שיש לנו את הממשק הזה:
1
2
3
4
5
6
publicinterfaceIBank
{
voidDeposit(string id, int amount);
voidWithdraw(string id, int amount);
intGetAvailableBudget(string id);
}
אז נוכל לממש את הממשק ע"י התנהגות אחידה: כתיבת הפרמטרים למסך:
טיפ זה יהיה האחרון בתקופה הקרובה שעוסק בקירובים נומריים. (עם זאת, ייתכנו עוד טיפים שמדברים על נושאים מתמטיים כלשהם. סביר להניח בנושאים של קומבינטוריקה או תורת הגרפים)
הפעם אני רוצה להסביר על אלגוריתם CORDIC בו משתמשים מחשבונים בד”כ על מנת לממש חישובים של הפונקציות הטריגונומטריות.
נתאר את הבעיה: יש לנו מספר ממשי כלשהו המתאר זווית (לצורך העניין נגביל אותה בגודל מסוים, נגיד לבין 0 ל$ \frac{\pi}{4} $ ($ {45}^{\circ} $)) ואנחנו מעוניינים לחשב את ה$ \sin $/$ \cos $/$ \tan $ שלה. לחלופין יכול להיות שנרצה לחשב את אחת הפונקציות ההפוכות הנ”ל.
ראשית אנחנו מחשבים את $ \arctan x $ עבור x שהוא הופכי של חזקה של 2, כלומר $ \displaystyle{\arctan \frac{1}{2^k}} $ לכל הkים בתחום מסוים.
את זה אנחנו עושים ע”י שימוש בטור טיילור של $ \arctan x $: $ \displaystyle{\arctan{x} = \sum_{n = 0}^{\infty}{\frac{\left( -1 \right)^n}{2n + 1} }x^{2n+1}} $ כאשר $ |x| \le 1 $.
את החישובים האלה אנחנו שומרים בטבלה סטטית בצד (איזשהו HashTable של $ k $ ל$ \displaystyle{\arctan \frac{1}{2^k}} $)
כעת אלגוריתם CORDIC עובד כך:
אנחנו מקבלים זווית $x$ שאנחנו מעוניינים לחשב אותה.
אנחנו עובדים עם שלושה משתנים:
$ \theta$– מייצג את הזווית שחישבנו את ה $ \cos $וה$ \sin $ שלה עד כה.
$ \mathrm{vector} $ – מספר מרוכב בכיוון של $ \cos\theta + i \sin\theta $
$ k $ – מספר האיטרציות שעשינו עד כה
המטרה שלנו היא לחשב את $ \mathrm{vector} $ עבור $\theta$ שקרובה מספיק ל$x$ שלנו.
את זה אנחנו עושים באופן הבא: אנחנו מתחילים עם $ \mathrm{vector} = 1 $, $ \theta = 0 $ ו$ k = 0 $. כעת אנחנו מבצעים את הפעולות הבאות עד שהקירוב מספיק טוב לנו:
אם $ i $ היחידה המדומה, כלומר המספר המרוכב המקיים $ i^2 = -1 $, אזי מתקיים כי $ (\cos \alpha + i \sin \alpha) \cdot (\cos\beta + i \sin\beta) = \cos(\alpha + \beta) + i \sin(\alpha + \beta) $
כלומר – כפל של שני וקטורים במישור המרוכב הוא וקטור בכיוון סכום הכיוונים שלהם.
נשים לב של $ 1 + \frac{1}{2^k} \cdot i $ יש את אותו כיוון כמו ל$ \cos\arctan\frac{1}{2^k} + i \cdot \sin\arctan\frac{1}{2^k} $. (כי $ \frac{\frac{1}{2^k}}{1} = \frac{1}{2^k} $ ולכן יש להם אותו $ \arctan $)
מה שאנחנו בעצם עושים זה מנסים להתקרב לזווית x כמה שיותר ע”י הוספה או הסרה של$ \displaystyle{ \arctan\frac{1}{2^k} } $ לפי הצורך בכל שלב. בסוף אנחנו מקבלים וקטור עם זווית מאוד קרובה לx. כעת אנחנו יכולים לחשב את $ \sin $ ואת ה$ \cos $ שלו ע”י נירמולו – כלומר ע”י חלוקה בערך המוחלט שלו.
הבעיה בפתרון זה – היא שחילוק יכול להרוס לנו את הדיוק של הפעולות שביצענו עד כה. במקום זאת ניתן להשתמש בטריק הבא: נניח וביצענו אינסוף פעולות, אז קיבלנו ערך מוחלט של: $ \left( 1 + \frac{1}{2^0} \cdot i \right) \cdot \left( 1 + \frac{1}{2^1} \cdot i \right) \cdot \left( 1 + \frac{1}{2^2} \cdot i \right) \cdot \left( 1 + \frac{1}{2^3} \cdot i \right) \cdot \dots $
מסתבר שזה מתכנס לקבוע $ K \approx 0.6072529350088812561694 $, כך שאנחנו יכולים לשמור אותו בצד (עם שאר הקבועים שחישבנו מראש) ובכל פעם להכפיל בו בסוף התהליך.
כאן יש איזשהו מימוש פשוט של CORDIC עם Complex (ראו גם טיפ מספר 128).
שימו לב שלא חישבתי את הקבועים מראש, אבל זה די אפשרי לעשות זאת:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
publicstaticdoubleSin(double x)
{
Complex vector = 1;
double theta = 0;
Complex oldVector = 0;
for (int k = 0; oldVector != vector; k++)
{
oldVector = vector;
double power = 1/Math.Pow(2, k); // Get it faster..
double powerAngle = Math.Atan(power); // Get it from a hash..
if (x > theta)
{
theta += powerAngle;
vector *= (1 + power * Complex.ImaginaryOne);
}
elseif (x < theta)
{
theta -= powerAngle;
vector *= (1 - power * Complex.ImaginaryOne);
}
}
vector /= Complex.Abs(vector); // Multiply by a constant instead.
return vector.Imaginary;
}
היתרון של CORDIC על אלגוריתמים אחרים הוא שהוא מאוד מהיר בעולם בו אין כפל - אם תסתכלו על האלגוריתם, הוא למעשה משתמש רק בחיבור/חיסור, כפל בחזקה של 2 (שהוא מאוד זול – הוא Shift) וכפל חד פעמי בתוצאה. (אמנם נראה כאן שיש כפל, אבל אפשר לשים לב ש$ \left(1+ai\right)\left(1+bi\right)=1-ab+i\left(a+b\right) $ ואם a או b חזקות של 2 אז המכפלה היחידה שיש פה היא Shift)
לכבוד יום הפאי – נדבר היום על נוסחת BBP לחישוב פאי.
קיימות לא מעט נוסחאות המחשבות את פאי המבוססות על אנליזה מתמטית.
נוסחאות אלה מאפשרות לקרב את פאי כרצוננו. הבעיה עם רוב הנוסחאות האלה הן שהן לא מאפשרות לנו לחשב את פאי החל ממקום מסוים – אם אנחנו רוצים לחשב למשל רק את הספרה המיליון של פאי, נצטרך לחשב את כל הספרות הקודמות לה, ולשמור אותן בזכרון, כך שהאלגוריתמים האלה לא חוסכים בזכרון.
ב1995 גילה בחור בשם Simon Plouffe באמצעות תוכנת מחשב שכתב, נוסחה מעט מסובכת לחישוב פאי:
privatestaticdoublePiFiniteSeries(int remainder, int digitIndex)
{
double sum = 0;
for (int i = 0; i <= digitIndex; i++)
{
long currentNum = 8 * i + remainder;
sum += PowerModulo(16, digitIndex - i, currentNum) / (double)currentNum;
sum = sum % 1.0;
}
return sum;
}
privatestaticdoublePiInfiniteSeries(int remainder, int digitIndex)
{
double sum = 0;
double previousSum = 1;
for (long i = digitIndex + 1; sum != previousSum; i++)
{
long currentNum = 8 * i + remainder;
previousSum = sum;
sum += Math.Pow(16, digitIndex - i) / currentNum;
}
return sum;
}
publicstaticlongPowerModulo(long a, long n, long m)
{
long result = 1;
long currentPower = a;
while (n > 0)
{
if (n % 2 == 1)
{
result *= currentPower;
result = result % m;
}
currentPower *= currentPower;
currentPower = currentPower % m;
n /= 2;
}
return result;
}
בגדול: מנסים לחשב את הספרה הראשונה אחרי הנקודה של $ \pi \cdot 16^n $. את זה עושים ע"י חישוב שני טורים: אחד סופי ואחד אינסופי. בדרך מתעלמים מהחלק השלם של כל המספרים (שהרי מעניין אותנו רק הספרה הראשונה אחרי הנקודה של המספר)
שימו לב שגם יש פה אלגוריתם חישוב חזקה מודולו מספר שדומה לאלגוריתם שראינו פעם (ע"י פירוק המעריך לבינארית)
היתרון העיקרי של הנוסחה היא שאפשר לחשב ספרה בלי להיסחב עם כל הספרות האחרות.
קרי: הגדר את $x_{n + 1}$ בתור הממוצע של $ x_n $ ו$ \displaystyle{\frac{a}{x_n}} $.
השיטה הזאת מתכנסת לשורש של a בצורה מאוד מהירה (למי שלמד אנליזה נומרית – ההתכנסות היא ריבועית, כלומר באיטרציה ה$ n $ יש פוטנציאלית $ 2^n$ ספרות מדויקות אחרי הנקודה)
השיטה היא מקרה פרטי של שיטת ניוטון רפסון המאפשרת לחשב שורש של פונקציה. שיטת ניוטון רפסון מוגדרת כך:
אנחנו רוצים לחשב שורש של פונקציה $ f(x) $, כלומר למצוא $ x_0$ כך ש $ f(x_0) = 0 $.
הגדר את $x_0$ להיות ניחוש לשורש של הפונקציה. בכל שלב הגדר את $x_{n + 1}$ כך:
השיטה בתנאי התחלה מסוימים מתכנסת לשורש של המספר, ובמקרים מסוימים (ש($ f’(x_0) $ הוא לא 0 למשל), מתקיים שההתכנסות ריבועית.
אם נגדיר $ f(x) = x^2 - a $ נקבל בדיוק את שיטת הרון.
בד”כ בפועל מה שעושים זה משלבים את שיטת ניוטון רפסון עם שיטת חיפוש כלשהו (כמו חיפוש בינארי) על מנת למצוא ניחוש ראשוני מספיק טוב, ואז מתחילים לבצע את תהליך ניוטון רפסון כדי להתקרב לשורש של הפונקציה.