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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
|
////////////////////////////////////////////////////////////////////////////////
/*! @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 <map>
#include <list>
#include <cmath>
#include <valarray>
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/Oe<sup>2</sup>, и снятые ранее на разных установках спектры ЭПР
приобретут единый количественный смысл.
Будучи раз введённой тем или иным способом, единица сохраняется в
конфигурационной части базы знаний RA. Таким образом там формируется система
единиц. Пользователь может её просматривать и редактировать.
Единица имеет символьное представление (symbol), представляющее собой строку из
букв и других символов Юникода, но не содержащую пробелы, цифры и знаки
арифметических операций. Например, спектроскописты оценят возможность ввести
@b базовую единицу @a cm<sup>-1</sup> -- надо только изобразить степень
символами U+207B (Superscript Minus) и U+00B9 (Superscript One).
@note
Единица считается базовой, если для неё не задано определение (definition)
через какие-то другие единицы.
Единицу можно сделать производной, то есть снабдить определением через
физическую константу, выраженную в других единицах -- базовых, производных
(других, конечно) или @ref sUnits "составных". Например, определить "eV" как
8065.54468cm<sup>-1</sup> (здесь имеются в виду обратные сантиметры, набранные
специальными символами, как указано выше). Это сделает возможным перевод
физвеличин как из электрон-вольтов в обратные сантиметры, так @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<sString,psUnit> 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(...));
- а в будущем, возможно, даже подбирать приятный глазу вид представления,
пробуя разные обратные подстановки - из комбинаций базовых единиц
в производные.
В предметно-ориентированных частях программы нет нужды использовать
б<i>о</i>льшую часть этих свойств класса 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<sTerm> tProduct;
typedef std::valarray<int> 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
|