مقاله فنی

توابع مهندسی در Delphi: تبدیل مبنا و حساب مختلط

خانواده توابع مهندسی در Excel در نگاه اول ساده‌ترین گوشه دفترچه توابع به نظر می‌رسد. DEC2BIN یک عدد را به رشته دودویی تبدیل می‌کند. HEX2DEC آن را برمی‌گرداند. IMSUM دو عدد مختلط را جمع می‌کند. همه‌چیز شبیه یک تمرین قالب‌بندی به نظر می‌رسد، اما چنین نیست. پشت این نام‌ها یک کدگذاری مکمل دو ده‌بیتی پنهان است که بیشتر توسعه‌دهندگان از زمان کلاس معماری کامپیوتر سراغش نرفته‌اند، قالبی برای اعداد مختلط که به‌طور کامل درون رشته‌ها زندگی می‌کند، و عملگرهای بیتی‌ای که اگر پیش از بررسی کردن shift انجام شود می‌توانند یک عدد 64 بیتی را بی‌سروصدا overflow کنند. یک موتور صفحه‌گسترده که قرار است Excel را دقیق بازتولید کند، حق ندارد هیچ‌کدام از این‌ها را سرسری بگیرد

این توابع به سه گروه تقسیم می‌شوند و هر گروه دام متفاوتی دارد. تبدیل مبنا درباره اعداد منفی و آستانه‌های هر مبنا است. حساب مختلط درباره parse کردن و format کردن رشته‌ها است. عملگرهای بیتی درباره ماندن در مرزهای Int64 هستند. این مقاله هر سه گروه را همان‌طور که HotXLS پیاده‌سازی می‌کند قدم‌به‌قدم مرور می‌کند، همراه با همان فراخوانی‌های worksheet که واقعاً در عمل خواهید نوشت

تبدیل مبنا و مکمل دو ده‌بیتی

جهت رو به جلو همان بخشی است که همه انتظارش را دارند. DEC2BIN(9) مقدار "1001" را می‌دهد، و آرگومان اختیاری دوم خروجی را با صفر از سمت چپ تا یک عرض ثابت پر می‌کند. دام اصلی ورودی منفی است. Excel علامت منفی نمی‌نویسد. مقدار را به شکل یک رشته مکمل دو ده‌رقمی در مبنای مقصد کدگذاری می‌کند، به همین دلیل DEC2BIN(-5,10) به جای چیزی با علامت، مقدار "1111111011" را برمی‌گرداند. وقتی مقدار منفی شد، آرگومان places نادیده گرفته می‌شود، چون کدگذاری از قبل روی ده رقم قفل شده است

ده رقم یک بودجه ثابت است و همین بودجه بازه قابل نمایش را در هر مبنا تعیین می‌کند. در مبنای دودویی، بزرگی‌ای که به نیمه منفی flip می‌کند 512 است و modulus دورزدن 1024، بنابراین یک رشته دودویی فقط وقتی signed محسوب می‌شود که دقیقاً ده نویسه طول داشته باشد و مقدارش دست‌کم 512 باشد. همین ایده در مبناهای دیگر هم گسترش می‌یابد. در هشت‌هشتی، half threshold برابر 2^29 و full modulus برابر 2^30 است. در شانزده‌هشتی، این مقادیر 2^39 و 2^40 هستند. خواننده HotXLS دقیقاً همین قاعده را اعمال می‌کند: رقم‌ها را انباشته می‌کند و فقط وقتی رشته دقیقاً ده نویسه پهنا داشته باشد و مقدار انباشته به half threshold برسد یا از آن عبور کند، full modulus را کم می‌کند تا مقدار signed بازیابی شود. یک رشته نه‌نویسه‌ای، هرچقدر هم بزرگ باشد، همیشه غیرمنفی است

encoder تصویر آینه‌ای همین منطق است. مقدار غیرمنفی رقم‌به‌رقم تبدیل می‌شود و در صورت نیاز تا عرض خواسته‌شده با صفر پر می‌شود، و اگر از سقف مثبت آن مبنا عبور کند یا عرض درخواست‌شده برای جا دادن آن کم باشد، رد می‌شود. مقدار منفی ابتدا با افزودن full modulus به بازه مجاز آورده می‌شود؛ این کار آن را به مقداری تبدیل می‌کند که نمایش آن در آن مبنا همیشه ده رقم است، و بعد رقم‌ها با صفرهای پیشرو خروجی داده می‌شوند تا عرض کامل شود. همین یک بررسی بازه مشترک، یعنی حد پایین و بالای متقارن برای هر مبنا، همان چیزی است که DEC2BIN، DEC2OCT و DEC2HEX را در لبه‌هایشان با هم سازگار نگه می‌دارد

بعد نوبت تبدیل‌های بین‌مبنایی می‌رسد، مثل HEX2BIN و OCT2HEX که در نامشان از decimal عبور نمی‌کنند. پیاده‌سازی برای هر جفت مرتب یک روال مستقل حمل نمی‌کند. رشته ورودی را با استفاده از مبنای مبدأ به یک مقدار decimal signed parse می‌کند، و بعد همان مقدار decimal را به مبنای مقصد format می‌کند. decimal نقش pivot را دارد. یک روال parse و یک روال format که با هم ترکیب شده‌اند، تمام ترکیب‌ها را پوشش می‌دهند، و چون هر دو نیمه از همان قرارداد signed ده‌رقمی استفاده می‌کنند، مقدار منفی در طول این سفر علامتش را از دست نمی‌دهد

اعداد مختلط رشته هستند، پس بخش اصلی کار parse کردن است

Excel هیچ نوع داده‌ای برای اعداد مختلط ندارد. مقدار مختلط رشته‌ای به شکل "a+bi" است، و هر تابعی در خانواده IM همین رشته‌ها را می‌گیرد و یک رشته برمی‌گرداند. COMPLEX رشته را از بخش حقیقی و موهومی می‌سازد. IMSUM، IMSUB، IMPRODUCT و IMDIV آرگومان‌ها را parse می‌کنند، حساب را روی بخش‌های عددی انجام می‌دهند، و نتیجه را دوباره به رشته format می‌کنند. بخش عددی همان جبر مقدماتی دانشگاهی است. سختی واقعی در تبدیل قابل‌اعتماد متن به دو عدد اعشاری شناور است، و دقیقاً همین‌جاست که parser داخلی ارزشش را نشان می‌دهد

دو جزئیات در این parser خیلی راحت اشتباه می‌شوند. اولی واحد موهومی برهنه است. رشته "i" یعنی یک‌بار i، نه صفر و نه error، بنابراین وقتی ضریب پیش از suffix خالی است یا فقط یک علامت مثبت تنها است، parser باید آن را مقدار 1 بخواند، و یک منفی تنها را -1. اگر این را جا بیندازید، IMSUM("i","i") دیگر 2i نخواهد بود. دومی برخورد notation علمی با علامتی است که بخش حقیقی و موهومی را جدا می‌کند. parser آن جداکننده را با جست‌وجوی یک مثبت یا منفی پیدا می‌کند، اما عددی مثل "1.5E-3" یک منفی دارد که متعلق به exponent است. بنابراین اسکنر وقتی نویسه بلافاصله قبل از آن e یا E باشد، آن مثبت یا منفی را جداکننده حساب نمی‌کند. بدون این guard، بخش حقیقی وسط علامت exponent نصف می‌شود و parse روی ورودی کاملاً معتبر شکست می‌خورد

خود suffix به جای نرمال‌سازی‌شدن حفظ می‌شود. Excel هر دو شکل i و j را می‌پذیرد، و HotXLS به خاطر می‌سپارد ورودی از کدام استفاده کرده تا خروجی format‌شده همان حرف را نگه دارد. بعد قواعد shorthand معمول اعمال می‌شوند: بخش موهومی با مقدار یک فقط به‌صورت خود suffix چاپ می‌شود، منفی یک به شکل -i، بخش موهومی صفر به یک عدد حقیقی ساده collapse می‌کند، و بخش حقیقی صفر پیشوند 0+ را حذف می‌کند

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Engineering');
    // Negative input: a ten-bit two's complement, places argument ignored.
    Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
    // Complex multiply on two "a+bi" strings.
    Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
  finally
    Book.Free;
  end;
end;

توابع مختلط transcendental مانند IMSQRT، IMEXP، IMLN و IMPOWER در مختصات دکارتی کار نمی‌کنند. آن‌ها مقدار parse‌شده را به فرم قطبی تبدیل می‌کنند، عمل را روی modulus و argument انجام می‌دهند، و بعد دوباره برمی‌گردانند. ریشه دوم argument را نصف می‌کند و ریشه modulus را می‌گیرد. توان، argument را ضرب می‌کند و modulus را به توان می‌رساند. انجام‌دادن این کار به هر روش دیگری یعنی بازاستخراج هر همانی در فرم دکارتی، که هم کد بیشتری می‌طلبد و هم نزدیک branch cutها از نظر عددی ناپایدارتر است

عملگرهای بیتی و overflowیی که باید پیش از هرچیز بررسی شود

Excel 2013 توابع BITAND، BITOR، BITXOR، BITLSHIFT و BITRSHIFT را اضافه کرد. آرگومان‌ها محدود هستند: هرکدام باید یک عدد صحیح نامنفی و حداکثر برابر 2^48 منهای 1 باشند، و هر آرگومان کسری یا منفی یک numeric error است. این سقف آن‌قدر بزرگ هست که هر مجموعه پرچم واقع‌بینانه‌ای را پوشش دهد و در عین حال داخل بازه‌ای بماند که یک double می‌تواند دقیق نمایش دهد؛ این مهم است چون Excel همه آرگومان‌های عددی را به شکل مقدار شناور عبور می‌دهد

توابع shift تنها یک قاعده ترتیبی واقعاً دردسرساز دارند. یک left shift می‌تواند مقداری بسیار بزرگ‌تر از ورودی تولید کند، و اگر اول shl را اجرا کنید و بعد نتیجه را بررسی کنید، Int64 از قبل overflow شده و آن بررسی بی‌معناست. guard باید پیش از shift انجام شود. HotXLS عملوند را با سقفی مقایسه می‌کند که به اندازه مقدار shift به راست جابه‌جا شده است، و فقط اگر عملوند جا شود خود left shift را انجام می‌دهد. بزرگی shift بالاتر از 53 بیت مستقیماً رد می‌شود، و shift منفی فقط جهت را برعکس می‌کند، بنابراین BITLSHIFT با شمارش منفی مانند right shift رفتار می‌کند. این اصل خیلی فراتر از این تابع خاص می‌رود: هرجا guardی برای جلوگیری از overflow دارید، باید روی ورودی‌ها اجرا شود، نه روی نتیجه‌ای که قرار بوده از آن محافظت کند

// Bitwise calls evaluate the same way through Calculate.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)');    // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)');   // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)');  // 5

توابع آینده و پیشوند نام _xlfn

عملگرهای بیتی و فهرست بلندی از افزوده‌های پس از 2007 با یک طرح نام‌گذاری درگیر می‌شوند که هیچ ربطی به محاسبه آن‌ها ندارد و کاملاً به نحوه ذخیره‌سازی‌شان در Excel مربوط است. قالب دودویی قدیمی worksheet برای هر تابع built-in یک جایگاه عددی در یک جدول ثابت داشت. توابعی که بعد از freeze شدن آن جدول ابداع شدند، هیچ slotی ندارند. برای ذخیره چنین تابعی در فایل به شکلی که Excel جدید آن را بشناسد، نام با پیشوند _xlfn. نوشته می‌شود؛ بنابراین BITAND روی دیسک به صورت _xlfn.BITAND ذخیره می‌شود، با اینکه کاربر فقط BITAND را تایپ می‌کند

پیچیدگی اینجاست که این قاعده یکنواخت نیست. بعضی توابع جدیدتر slot گرفته‌اند و بدون پیشوند نوشته می‌شوند، و چند تابع پنهان قدیمی هم با وجود سنشان باز بدون پیشوند می‌آیند. HotXLS یک whitelist صریح از نام‌هایی که به پیشوند نیاز دارند نگه می‌دارد، آن را هنگام نوشتن اضافه می‌کند و هنگام خواندن برمی‌دارد، تا متنی که شما تنظیم می‌کنید و دوباره می‌خوانید همیشه همان نام تمیزِ رو‌به‌کاربرِ Excel باشد. شما =BITLSHIFT(5,2) را می‌گذارید، فایل _xlfn.BITLSHIFT را نگه می‌دارد، و مقدار در هر حال 20 برمی‌گردد. این پیشوند یک جزئیات ذخیره‌سازی است و نباید هرگز به فرمول‌هایی که در کد با آن‌ها کار می‌کنید نشت کند

سرهم‌بندی همه این‌ها در یک worksheet

سطح عمومی این قابلیت‌ها کوچک است. یک TXLSXWorkbook بسازید، یک worksheet اضافه کنید، و یا یک formula را از راه Cells[Row, Col].Formula در سلول بنویسید و workbook را recalculate کنید، یا مستقیم با متد Calculate خود worksheet یک expression را ارزیابی کنید که formula را در همان sheet compile می‌کند و یک Variant برمی‌گرداند. مثال‌های بالا از Calculate استفاده می‌کنند چون نتیجه یک فراخوانی منفرد مهندسی را بدون state پیرامونی worksheet نشان می‌دهد، اما همین توابع داخل formulaهای واقعی سلولی هم دقیقاً به همان شکل ارزیابی می‌شوند وقتی workbook بازمحاسبه می‌شود

چیزی که باید به خاطر بسپارید encodingها هستند، نه محل‌های فراخوانی. یک رشته دودویی فقط در طول ده رقم و فقط آن‌طرف half threshold مبنایش signed است. عدد مختلط متن است، ضریب موهومی خالی معادل یک است، و parser از روی e در exponent می‌پرد. left shift پیش از انجام‌شدن بررسی می‌شود. اگر این چهار واقعیت را درست بگیرید، خانواده مهندسی دیگر منبع غافلگیری‌های علامت‌اشتباه نخواهد بود

اگر در حال وصل‌کردن ریاضیات دامنه خودتان به همین موتور هستید، سازوکار ثبت handler و بازگرداندن مقدارها در مقاله ما درباره گسترش موتور فرمول با custom functionها توضیح داده شده است، و وقتی آن formulaها باید به‌جای آدرس سلول، با نام از sheetهای دیگر ارجاع بگیرند، راهنمای defined nameها و formulaهای بین‌برگه‌ای نشان می‌دهد این ارجاع‌ها چگونه resolve می‌شوند. توابع مهندسی شرح‌داده‌شده در اینجا بخشی از HotXLS spreadsheet component برای Delphi و C++Builder هستند، در کنار APIهای خواندن، نوشتن و محاسبه که در بخش‌های دیگر این وبلاگ پوشش داده شده‌اند