//////////////////////////////////////////////////////////////////////////////// /*! @file Units.h Единицы измерения физвеличин. - Part of RANet - Research Assistant Net Library (based on ANSI C++). - Copyright(C) 2007-2011, Viktor E. Bursian, St.Petersburg, Russia. Viktor.Bursian@mail.ioffe.ru */////////////////////////////////////////////////////////////////////////////// #ifndef Units_H #define Units_H #include "General.h" #include "Storable.h" #include #include #include #include namespace RA { //------------------------------------------------------------------------------ ANNOUNCE_CLASS(sUnit) ANNOUNCE_CLASS(sUnits) ANNOUNCE_CLASS(sPhysValue) //------------------------------------------------------------------------------ /*! Константа "пустые единицы". Используется в определениях переменных или членов, когда нужно создать заведомо безразмерную переменную или член типа sPhysValue или sPhysRange. Введена по двум причинам. Во-первых, C++ не даёт использовать в этих местах выражение @a sUnits() - думает, что это объявление функции. Во-вторых, просто улучшает читаемость. */ RANet_EXPORT extern csUnits _Unitsless_; //------------------------------------------------------------ sMetricPrefix --- /*! Provides transformations between metric prefixes and scale multipliers in accordance with the following table: @code Metric Prefixes USA old British 10^24 Y yotta йотта septillion 10^21 Z zetta зетта sextillion (trilliard) 10^18 E exa Э экса(экза) quintillion (trillion) 10^15 P peta П пета quadrillion (billiard) 10^12 T tera Т терра trillion (billion) 10^9 G giga Г гига billion (milliard) 10^6 M mega М мега million 10^3 k kilo к кило 10^2 h hecto(hekto) г гекто 10^1 da deca(deka) да дека 10^-1 d deci д деци 10^-2 c centi с санти 10^-3 m milli м милли 10^-6 µ micro мк микро 10^-9 n nano н нано 10^-12 p pico п пико 10^-15 f femto ф фемто 10^-18 a atto а атто one quintillionth 10^-21 z zepto зопто one sextillionth 10^-24 y yocto(yokto) йокто one septillionth @endcode */ class RANet_EXPORT sMetricPrefix { public://static static sString Symbol (int Log); static sString CombinedSymbol (int Log); static int Exp (sString Prefix); static real Multiplier (int Log); static real Multiplier (sString Prefix) { return Multiplier(Exp(Prefix)); } static sString Name (int Log); static csString StandardAllowance; static csString NoPrefixAllowed; }; //-------------------------------------------------------------------- sUnit --- /*! Единица измерения физвеличин (и система единиц). Для каждой единицы создаётся @b один объект класса sUnit. Он помещается в регистр (статический член sUnit::UnitList) и в дальнейшем используются только указатели на него. @note Поскольку все конструкторы класса приватны (кроме спрятанного в макро STORABLE конструктора ввода из потока), новые единицы появляются и одновременно регистрируются одним из следующих способов: - посредством вызова статической функции-члена sUnit::Register; - при разборе строки в конструкторе sUnits::sUnits(sString) класса составной единицы; - при вводе из потока объекта, ранее созданного и сохранённого. Ядро RA не навязывает никакой системы единиц. Пользователь сам вводит удобные лично ему единицы и их взаимосвязи. Однако некоторые плагин-пакеты инструментов могут либо мягко предлагать (в sInstrumentalPackage::InstallDefaults()), либо навязывать (лучше всего это делать в конструкторе производного от sInstrumentalPackage класса) единицы, необходимые для их работы. Например, драйвер вольтметра скорее всего будет навязывать "V" как базовую единицу. @note Драйверо-писателям настоятельно рекомендуется снабжать результаты измерений какими-то единицами, если только измеряемая величина не является принципиально безразмерной. Последняя оговорка применима крайне редко! Чаще бывает так, что некая вполне размерная физ.величина измеряется в неких условных единицах, например, в квантах АЦП, и не может быть пересчитана в разумные физические единицы, поскольку чувсвительность либо неизвестна, либо вообще меняется от раза к разу, т.е. зависит от условий эксперимента. Тем не менее, даже в этом случае, рекомендуется (@b настоятельно!) выдавать результат "в попугаях" ("ADC_quantum", "photon/s", ...). Можно не придумывать длинные имена, а использовать какой-нибудь экзотический символ Юникода (знак карточной масти, например). Важно, чтобы придуманная единица была раз и навсегда связана с конкретным измерительным трактом, измеряющим конкретное физическое свойство. @note Это положительно повлияет на последующее поведение: показ кривых на графиках, вычитание и деление спектров, и т.п. @note Кроме того, впоследствии, например, проградуировав прибор, можно будет объявить, что один попугай равен 0.033 удава, а один удав равен 5.67e12 Spin/Oe2, и снятые ранее на разных установках спектры ЭПР приобретут единый количественный смысл. Будучи раз введённой тем или иным способом, единица сохраняется в конфигурационной части базы знаний RA. Таким образом там формируется система единиц. Пользователь может её просматривать и редактировать. Единица имеет символьное представление (symbol), представляющее собой строку из букв и других символов Юникода, но не содержащую пробелы, цифры и знаки арифметических операций. Например, спектроскописты оценят возможность ввести @b базовую единицу @a cm-1 -- надо только изобразить степень символами U+207B (Superscript Minus) и U+00B9 (Superscript One). @note Единица считается базовой, если для неё не задано определение (definition) через какие-то другие единицы. Единицу можно сделать производной, то есть снабдить определением через физическую константу, выраженную в других единицах -- базовых, производных (других, конечно) или @ref sUnits "составных". Например, определить "eV" как 8065.54468cm-1 (здесь имеются в виду обратные сантиметры, набранные специальными символами, как указано выше). Это сделает возможным перевод физвеличин как из электрон-вольтов в обратные сантиметры, так @b и @b обратно. @note Не рекомендуется устраивать цикличные определения. Например, в дополнение к вышеуказанному, определить ещё и обратные сантиметры через электрон-вольты. Это ввергнет RA в состояние глубокой задумчивости! RA понимает и самостоятельно применяет метрические префиксы при единицах (см. sMetricPrefix) для более наглядного представления чисел. Это потрясающе удобно. Причём, как для юзера, так и для программера. Последнему не надо думать о количестве знаков, положении точки, домножать на десять-в-степени при выводе, делить при вводе,.. Но и тому, и другому надо учитывать ряд нюансов: -# Как упомянуто выше, новые единицы автоматически регистрируются в плагинах или в диалоге с пользователем. И, например, "ms" будет зарегистрирована как базовая, если до этого не регистрировалась "s". Если же "s" зарегистрирована до первого появления "ms", то "ms" распознаётся как milli-"s". @note Последнее, впрочем, не мешает всяким извращенцам, в дополнение к единице "s", явно зарегистрировать ещё и базовую единицу "ms" со смыслом, отличным от milli-"s". Тогда "ms" будет означать, например, единицу маразма, "Mms" - мегамаразм, но "ks" и "ns" останутся производными от "s". -# К некоторым единицам не принято применять некоторые (или никакие) метрические префиксы. Или у пользователя специфический вкус. Например, значение длины волны 1270nm программой RA будет представлено в виде 1.270 микрометров, а шаг сканирования 0.0033nm - в виде 3.3pm, что может быть неудобно. Проблема почти всегда решается указанием допустимых префиксов для таких единиц (см. prefix_allowance). В данном случае, правильным выходом будет задать "m", как базовую единицу, и запретить для неё префиксы micro и pico. Остальные лучше оставить разрешёнными - тогда, например, значение толщины образца тоже будет выглядеть красиво. -# Если стандартных префиксов не хватает RA может начать комбинировать префиксы, например, "kYm", что означает kilo-yotta-meter. (Интересно, что в не очень старых статьях можно найти частоту в kMHz, видимо, префикс giga- тогда ещё не был введён в обиход.) @sa sUnits, sPhysValue, sPhysRange. */ class RANet_EXPORT sUnit : public sStorable { STORABLE(sUnit) public://types typedef bool fRegisterDialog (rsString); typedef fRegisterDialog * pfRegisterDialog; ANNOUNCE_CLASS(sSystem) class sSystem { public: ~sSystem (); sSystem (); public://fields map UnitList; // friend class sUnit; }; public://static static psUnit Register (sString symbol ,sString prefix_allowance ,sPhysValue definition); static psUnit Register (sString symbol ,sString prefix_allowance); static psUnit Register (sString symbol ,sPhysValue definition); static psUnit Register (sString symbol); static psUnit Find (sString symbol); static bool ParseUnitWithPrefix (sString lexem ,psUnit & unit ,int & prefixlg); public://static fields static sSystem System; static psUnit _1_; static pfRegisterDialog RegisterDialog; public: sString Symbol () const { return TheSymbol; } bool PrefixAllowed (int prefix_index) const; sString PrefixAllowance () { return ThePrefixAllowance; } psPhysValue Definition () const { return TheDefinition; } bool IsBasic () const { return (TheDefinition != NULL); } private: sUnit (); //!< makes _1_ sUnit (sString symbol ,sString prefix_allowance ,psPhysValue definition); sUnit (rcsUnit); rsUnit operator = (rcsUnit); private://static void MakeVeryBasicDefinitions (); private://fields bool Is_1_Flag; bool IsObsoleteFlag; sString TheSymbol; sString ThePrefixAllowance; psPhysValue TheDefinition; // sString TheComment; friend class sSystem; }; //------------------------------------------------------------------- sUnits --- /*! Составные единицы измерения (то есть выражение над несколькими @ref sUnit "базовыми или производными единицами" с операциями умножения, деления и возведения в степень). Объекты класса содержат выражение в разобранном виде, но при написании предметно-ориентированных частей программы, а также при вводе пользователем они порождаются из строки, в которой показатели степеней пишутся "в строчку": @code "kV/cm" "kg*m2*ms-2" "kg*m2/ms2" // эквивалентно предыдущему "km2*kg/s2" // то же, но лишь в том случае, если "g", "m" и "s" объявлены // ранее как как единицы (базовые или производные). "Gg/s2*m2" // то же, с той же оговоркой // (N!B! порядок операций: "m2" - в числителе!) "MJ" // то же, если определена единица "J" = "kg*m2/s2" @endcode Класс довольно умный: умеет - разбирать строковые представления (см. sUnits::sUnits(sString)); - умножать и делить составные единицы друг на друга; - подставлять определения производных единиц, переводя всё в базовые (это делается не всегда, а при необходимости, см. ToBasic()); - упрощать выражение, сокращая сомножители (см. SimplifyProduct(int)); - сортировать сомножители, отправляя сомножители с отрицательными степенями в конец (см. SmartSort()); - "растворять" порядок числа в метрических префиксах (см. DissolveOrder(...)); - а в будущем, возможно, даже подбирать приятный глазу вид представления, пробуя разные обратные подстановки - из комбинаций базовых единиц в производные. В предметно-ориентированных частях программы нет нужды использовать большую часть этих свойств класса sUnits непосредственно. Обычно используется алгебра физвеличин, ради которой всё это и понаписано. Фактически вне ядра RA используются первые два свойства. Для определения безразмерных физвеличин в конструкторах sPhysValue в качестве единиц указывается константа RA::_Unitsless_. В конструкторах sPhysValue единицы можно задавать и непосредственно в строковом представлении. Примерчик: @code sUnits WeightUnit ("pound"); sUnits SizeUnit ("\""); //кавычки как символ дюйма sUnits AccelerationUnit ("m/s2"); //... sPhysValue Pressure (WeightUnit/(SizeUnit*SizeUnit)); sPhysValue ForseMoment1 (WeightUnit*SizeUnit); sPhysValue ForseMoment2 ("kg*m/s2*m"); sPhysValue Ratio (_Unitsless_); //... Ratio = ForseMoment1 / ForseMoment2; @endcode @sa sUnit, sPhysValue, sPhysRange. @bug Разбор строкового представления ломается, если строка содержит символы Юникода, в UTF-8 представлении которых содержатся байты '*', '/', '+', '-', '0', ... '9'. */ class RANet_EXPORT sUnits : public sStorable { STORABLE(sUnits) public://static static sUnits Parse (sString); public: ~sUnits (); sUnits (); sUnits (psUnit unit ,int power = 1 ,int prefix_lg = 0); sUnits (sString); sUnits (rcsUnits); bool Is_1_ () const; sString Text (eTextFormat F = Plain) const; //!< visual representation for user sPhysValue ToBasic () const; public://operators rsUnits operator = (rcsUnits); sPhysValue operator * (rcsUnits) const; sPhysValue operator / (rcsUnits) const; private://types ANNOUNCE_CLASS(sTerm) class sTerm { public: sTerm (psUnit unit ,int power ,int prefix_lg) :Unit(unit) ,Power(power) ,PrefixLg(prefix_lg) {} bool operator < (rcsTerm T) const { return Unit->Symbol() < T.Unit->Symbol(); } public://fields psUnit Unit; int Power; int PrefixLg; }; typedef std::list tProduct; typedef std::valarray tPrefixes; private://static static void SubstituteDefinitions (real & coeff ,rsUnits units); private: int MultBy (rcsUnits); int DivideBy (rcsUnits); // bool Comparable (rcsUnits units // ,real & ratio // ,bool & power_of_ten) const; bool Comparable (rcsUnits units ,real & ratio) const; int DissolveOrder (unsigned short int level ,int log_shift = 0 ,int min_rest_expected = 0 ,int max_rest_expected = -1); int SimplifyProduct (int log_shift = 0); // void OrderPushIn (int log_shift); void SmartSort (); void FindBestChoice (tPrefixes & best_choice ,int & best_weight ,int & best_rest_order ,int min_rest_expected ,int max_rest_expected ,tPrefixes considered_product ,int weight ,int rest_order ,size_t term_to_vary ,unsigned short int level); private://fields tProduct Product; bool InBasicTerms; bool Refined; friend class sPhysValue; //! @todo{PhysValues} friend class sPhysRange у sUnits - хорошо ли? friend class sPhysRange; }; //------------------------------------------------------------------------------ } //namespace RA #endif