اختاپوس خسته

یادداشت‌هایی پیرامون کد، زندگی و دوستان

محاسبهٔ ثابت‌های ریاضی

هیچ‌کدام از استانداردهای سی‌پلاس‌پلاس و یا سی عدد π را تعریف نکرده‌اند. بنابراین هر برنامه‌نویسی که قصد داشته باشه از توابع مثلثاتی یا آماری استفاده کنه مجبوره خودش π رو تعریف کنه. خوشبختانه فایل‌های سرآیند استاندارد عدد π رو تعریف کرده‌اند، (ثابت M_PI در هدر math.h رو ببینید) با این وجود استفاده از این ثابت و ثابت‌های دیگه بسیار خطرناک هست و برای کاربردهای دقیق باعث بروز خطاهای عددی خواهد شد.

خوشبختانه میشه با صرف هزینهٔ صفر در زمان اجرا π رو به‌صورت استاندارد (با تعاریف مشخص عددی) و البته در زمان کامپال محاسبه کرد. برای این کار از meta-programming به وسیلهٔ template ها در ‪C++‬ استفاده می‌کنیم. از مزایای این روش میشه به قابل حمل بودن و پیروی کامل از استاندارد IEEE 754 اشاره کرد که از لحاظ دقت عددی و یکسان بودن نتایج روی ماشین‌های مختلف بسیار مهم هست.

برای محاسبهٔ عدد π روش‌های مختلفی وجود داره. اکثر این روش‌ها براساس محاسبهٔ یک سری یا دنباله هستند که در بینهایت به عدد پی همگرا میشه. یکی از بهترین روش‌ها فرمول Bailey–Borwein–Plouffe هست. اثبات میشه که: $$\pi=\sum_{n=0}^{\infty}{\left[\frac{1}{16^{n}}\left(\frac{4}{8n+1}–\frac{2}{8n +4}–\frac{1}{8n+5}–\frac{1}{8n+6}\right)\right]}$$ بنابراین با این کد توی ‪C++‬ می‌تونیم به‌سادگی عدد پی رو تا هر تعداد تکرار که میخواهیم حساب کنیم:

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
#include <cstdint>
#include <limits>
#include <iostream>

template <uint64_t b, uint64_t e>
struct pow {
    static const uint64_t result = b * pow < b, e - 1 >::result;
};

template <uint64_t b>
struct pow<b, 0> {
    static const uint64_t result = 1;
};

template <uint64_t n>
struct bbp {
    constexpr static double pi
            = (1.0 / pow<16, n>::result)
            * (   4.0 / (8 * n + 1.0) - 2.0 / (8 * n + 4.0)
                - 1.0 / (8 * n + 5.0) - 1.0 / (8 * n + 6.0))
            + bbp < n - 1 >::pi;
};

template <>
struct bbp < -1 > {
    constexpr static double pi = 0;
};

int main(int, char* []) {
    std::cout.precision(std::numeric_limits<double>::digits10);
    std::cout << "pi = " << bbp<1>::pi << std::endl;
    std::cout << "pi = " << bbp<5>::pi << std::endl;
    std::cout << "pi = " << bbp<10>::pi << std::endl;
    return 0;
}

نتایج:

1
2
3
4
pi: 3.14142246642247
pi: 3.14159265322809
pi: 3.14159265358979
pi: 3.14159265358979

چرا؟

روش‌های زیادی برای محاسبهٔ عدد پی وجود داره. چرا دقیقاً این روش بهتره؟ مثلاً روش نیوتون به‌صورت هندسی عدد پی رو به این صورت حساب می‌کنه:

$$\pi=\frac{3}{4}\sqrt{3}+24\int_{0}^{\frac{1}{4}}{\sqrt{x-x^{2}}}\,dx$$

که خوب از نظر عددی محاسبهٔ پیچیده‌تری داره و زمان کامپایل نمیشه درست حسابش کرد. روش‌های دیگه‌ای هم وجود دارن مثل روش تبدیل همگرایی افزایشی اویلر (OEIS A054387) که اثبات می‌کنه:

$$\pi=\sum_{n=0}^{\infty}{\frac{\left({n!}^{2}2^{n-1}\right)}{\left(2n+1\right)! }}$$

با بررسی تمام این سری‌ها متوجه میشیم که خطای عددی افزایشی در فرمول BBP از تمام فرمول‌های پیوسته و سری‌های گسستهٔ دیگه کمتره.

ثابت‌های دیگر

عدد پی تنها عدد گنگ مورد استفاده در ریاضیات نیست. با این حال در زمینهٔ محاسبات علمی به اعداد دیگه مثل عدد e (پایهٔ لگاریتم طبیعی، عدد اویلر) خیلی کم نیاز پیدا می‌کنیم. (در این مورد خاص دو دلیل داره. یکی این که به‌راحتی قابل تبدیل به محاسبات مختلط هست و دوم این که تابع استاندارد exp در زبان برنامه‌نویسی سی وجود داره (:

مشکل نیم‌فاصله در KDE و روش حل آن

قبلاً در مورد باگ بسیار بدی که در کیوت به‌وجود آمده بود و روش حل آن نوشتم. این باگ باعث میشد امکان نوشتن نویسه‌های کنترلی مثل نیم‌فاصله و تغییر جهت به‌طور کامل از بین بره. (اگر از نیم‌فاصله استفاده نمی‌کنید و یا نمی‌دونید تغییر جهت متن چه اهمیتی داره حتماً نویسه‌های کنترلی و جهت‌دهی متون فارسی/انگلیسی رو بخونید)

الآن با گذشت چند ماه باگ مربوطه برطرف شده و با نسخهٔ 5.9.1 منتشر شده. (تغیرات گریت برای ماژول qtbase اینجا و برای ماژول qtdeclarative اینجا قابل مشاهده هستند) خوشبختانه برنامه‌های کیوت دیگه مشکل سابق رو ندارند و هم توی ماژول widgets و هم توی ماژول جدیدتر Qt Quick مشکل به‌طور کامل برطرف شده. خوب حداقل من این‌طور فکر می‌کردم!

برنامه‌نویسی شبکه در ویندوز/لینوکس: libcpnet

کارفرمای کاری که الان دارم انجام میدم اصرار داره که برنامه‌ش علاوه‌بر لینوکس روی ویندوز هم به‌خوبی اجرا بشه. مدیر من کاملاً این محدودیت رو پذیرفته و به کارفرما گفته که با سخت‌افزاری که مشخصاتش رو اعلام می‌کنیم و با مشخصات کارکردی که اعلام می‌کنیم، روی ویندوز هم می‌تونید برنامه رو اجرا کنید. خوب این تصمیم مشکلات بسیار بزرگی برای برنامه‌نویس به‌وجود میاره. ازجمله برنامه‌نویسی شبکه… این پست به بررسی این مشکلات و ارائهٔ یک راه حل خوب خواهد پرداخت (: اگر قصد دارید کدی بنویسید که هم روی ویندوز و هم روی سیستم‌عامل‌های واقعی بتونه از شبکه استفاده کنه حتماً ادامهٔ مطلب رو بخونید.

بدترین باگ کیوت!

کیوت به‌نظر من بهترین فریم‌ورک سی‌پلاس‌پلاس هست. یک کتابخانهٔ خیلی بزرگ با امکانات بسیار عالی و خوب که محیط کاری KDE به‌طور کامل برپایهٔ اون ساخته شده. اما از نسخهٔ 5.3 (ظاهراً) یک باگی به‌وجود آمده که زندگی رو برای ما خیلی سخت کرده: عدم امکان وارد کردن نویسه‌های کنترلی و نیم‌فاصله‌های چسبان و غیرچسبان! (قبلاً در مورد مورد نویسه‌های کنترلی و جهت‌دهی متون فارسی/انگلیسی نوشتم.) حوزهٔ تأثیر این باگ به قدری بزرگ و گسترده است که کار با محیط KDE رو برای ما غیرممکن کرده. تصور کنید که تقریباً هیچ جایی توی سیستم‌عامل و برنامه‌های کاربردی محیط دسکتاپ نتونید نویسه‌های کنترلی و فاصله‌ها رو تایپ کنید! توی این نوشته قصد دارم دلایل این باگ و روش برطرف کردنش و همچنین روش دور زدن اون رو توضیح بدم (: خوشبختانه روش فیکس خیلی ساده‌ست. و البته جای نگرانی نیست: نسخه‌های فیکس با کیوت 5.8.1 (اگر ریلیز بشه) و یا 5.9.0 (در هر صورت) منتشر میشن. منتها کسایی که نمی‌خوان تا اواسط 2018 صبر کنن که اون نسخه‌ها برای دبیان و اوبونتو بیاد، خودشون می‌تونن با این روشی که توضیح میدم پچ کنن (:

این بورد لعنتی

مدتی‌یه که برای انجام یه پروژهٔ صنعتی یه بورد Smart 210 به دستم رسیده. این بورد ساخت شرکت FriendlyARM هست که یه کمپانی چینی‌یه که سخت‌افزارهای ارزون‌قیمت صنعتی می‌سازه. مشخصات ظاهری‌ش خوب به نظر می‌رسه. با این وجود از لحاظ نرم‌افزاری یک فاجعه‌ست! این پست توضیحاتی در مورد طرز کار و بیشتر توضیح معایب این بورده. امیدوارم در آینده برای کسایی که می‌خوان باهاش کار کنن مفید باشه یا لااقل باعث باشه از خریدش منصرف بشن (:

برمی‌گردم

خوب بالأخره بعد از گذشت چند ماه مطلاطم شرایط زندگی‌م تقریباً به ثبات نسبی رسیده و با خیال راحت می‌تونم به کارهام برسم (:

چیزهایی که تو ذهنمه خیلی زیاده، چیزهای زیادی باید بنویسم، کارهای زیادی باید بکنم، پروژه‌های زیادی هستند که باید در موردشون بنویسم و هزار و یک تا کار دیگه…

تغییرات بزرگ

خیلی وقته که توی اینترنت فعالیت چندانی ندارم و اصولاً چند ماهی هست که از کل دنیا عقب افتادم!

توی این چند ماه اتفاقات خیلی زیادی افتاده و زندگی‌م تقریباً داره به‌طور کلی عوض میشه. اول این که متأسفانه به‌طور کامل تو تهران ساکن شدم. دیگه برنمی‌گردم مگر تعطیلات و غیره :( دوم این که یه کار خوب پیدا کردم توی یه شرکت خوب و باسابقه و با سیستم کاملاً منطقی و با حقوق نسبتاً بالا (برای یه لیسانس).

تا چه شود…

کتابخانهٔ Dynamixel

مدتی هست که هم توی شبکه‌های اجتماعی و هم این‌جا فعالیتم خیلی کم شده. دلیلش پروژه‌ای هست که در حال حاضر وقت خیلی زیادی ازم می‌گیره و مجبورم به‌خاطرش مسافرت‌های طولانی رو برم و برگردم.

اما ماحصل یکی از این سفرها یک کتابخانهٔ جدید و بسیار خوشگل و مرتب شده به اسم libdynamixel. این کتابخانه یک API سطح بالا برای کنترل و استفاده از سروو موتورهای هوشمند داینامیکسل طراحی شده. این پست در مورد ویژگی‌های این کتابخانه است.

ارتباط میان‌زبانی

زبان مورد استفاده برای پیاده‌سازی یک ابزار، یکی از ویژگی‌های آن نیست.

دنیا پر از ابزارها و کتابخانه‌هایی هست که به‌دست برنامه‌نویسان مختلفی به‌زبان‌های مختلف نوشته شدن. بدون وجود این کتابخانه‌ها و ابزارها زندگی برای ما برنامه‌نویس‌ها (به‌دلایلی واضح) خیلی سخت می‌شد.

به عقیدهٔ من همه‌چیز باید همه‌جا برای همه‌کس قابل استفاده باشه. یعنی این که مثلاً این که من فلان کتابخانهٔ کاربردی و باحال رو با زبان ‪C++‬ پیاده‌سازی کردم نباید باعث بشه که یک برنامه‌نویس پایتون یا جاوا نتونه ازش استفاده کنه. حتا زبان‌هایی که دامنه و کاربرد مختلفی دارن باید پشتیبانی بشن. مثلاً یکی از ابزارهایی که ساختم در اصل به‌عنوان یک «حل‌کنندهٔ مسأله» برای کاربردهای پیشرفتهٔ هوش مصنوعی طراحی شده، و این‌چنین موضوعاتی معمولاً برای کاربردهای سیستمی و خاص‌منظوره استفاده میشن. اما هیچ دلیلی وجود نداره که یک برنامه‌نویس وب برای یک اپلیکیشن آنلاین نخواد از مدل‌سازی ارضای محدودیت برای حل یک سری مسائل داخل برنامه‌ش استفاده کنه، یا یک برنامه‌نویس اندرویید نخواد از سیستم‌های استنتاج فازی برای برنامه‌ش استفاده کنه. بنابراین وظیفهٔ منِ برنامه‌نویس است، که برای تمام زبان‌هایی که می‌تونم، رابط (=>interface)های بومی (=>native) فراهم کنم تا همه بتونن از ابزارم استفاده کنن.

توی این پست نحوهٔ ایجاد رابط برای زبان‌های مختلف رو توضیح میدم. طبق این روش ساده، میشه به‌راحتی برای کتابخانه‌های ‪C++‬ رابط‌هایی برای تمام زبان‌های دیگه پیاده‌سازی کرد.

پادشاه و استادِ بزرگ

روزی پادشاهی استادِ بزرگ را نزد خویش فراخواند و به او گفت:

ای حکیم دانا!‌ به من چیزی بیاموز که در غمگینی مراخوشحال کرده و دوران خوشی مرا غمگین کند.

استادِ بزرگ به وی گفت:

ای پادشاه کبیر! هرگاه در چنگال غم و اندوه گرفتار شدی، به‌خاطر بیاور که غمِ دنیا می‌گذرد؛ آن‌گاه شاد خواهی شد. و هرگاه در روزگار بر وفق مراد بود به‌خاطر بیاور که این خوشی پایدار نیست و آنگاه اندوهگین خواهی شد.

پادشاه از وی تشکر کرد و استادِ بزرگ رفت. پادشاه از این که به جواب سؤال خود رسیده بود خوشحال شد. ناگهان حرف استاد را به‌خاطر آورد و ناراحت شد. سپس دوباره خوشحال شد، دوباره ناراحت شد، در حلقهٔ بینهایت افتاد، مغزش ترکید و مُرد. و بدین‌ترتیب مردم خوشحال شدند. سپس یاد حرف استاد افتادند و ناراحت شدند، سپس دوباره خوشحال شدند، بعد در حلقهٔ بینهایت افتادند، مغزشان ترکید و همگی مُردند.

در این میان استادِ بزرگ که از معبد خود در کوه‌های اطرف، شهر را زیر نظر داشت با خود گفت: «قاعدتاً‌ نباید این‌طوری می‌شد.»