#ifndef TypeHelpersH
#define TypeHelpersH
#include <cstddef>
#include <cstdint>
#include <type_traits>
// std::is_fundamental [http://w...content-available-to-author-only...s.com/reference/type_traits/is_fundamental/]
enum class ECFundamentalTypeTags {
UNIDENTIFIED,
BOOL,
SIGNED_CHAR,
UNSIGNED_CHAR,
// Signedness of wchar_t is unspecified
// [http://stackoverflow.com/questions/11953363/wchar-t-is-unsigned-or-signed]
WCHAR,
//// 'char16_t' AND 'char32_t' SHOULD be a keywords since the C++11,
//// BUT MS VS Community 2013 Upd 5 does NOT supports that
//// AND specifys 'char16_t' AND 'char32_t' as a typdef aliases instead
//// (so they are NOT presented here)
SIGNED_SHORT_INT,
UNSIGNED_SHORT_INT,
SIGNED_INT,
UNSIGNED_INT,
SIGNED_LONG_INT,
UNSIGNED_LONG_INT,
SIGNED_LONG_LONG_INT, // C++11
UNSIGNED_LONG_LONG_INT, // C++11
FLOAT,
DOUBLE,
LONG_DOUBLE,
VOID_,
NULLPTR // C++11 std::nullptr_t
};
template <typename T, class TypeTags = ECFundamentalTypeTags>
struct TypeTag {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::UNIDENTIFIED;
};
template <class TypeTags>
struct TypeTag<bool, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::BOOL;
};
template <class TypeTags>
struct TypeTag<signed char, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::SIGNED_CHAR;
};
template <class TypeTags>
struct TypeTag<unsigned char, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::UNSIGNED_CHAR;
};
template <class TypeTags>
struct TypeTag<wchar_t, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::WCHAR;
};
template <class TypeTags>
struct TypeTag<signed short int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::SIGNED_SHORT_INT;
};
template <class TypeTags>
struct TypeTag<unsigned short int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::UNSIGNED_SHORT_INT;
};
template <class TypeTags>
struct TypeTag<signed int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::SIGNED_INT;
};
template <class TypeTags>
struct TypeTag<unsigned int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::UNSIGNED_INT;
};
template <class TypeTags>
struct TypeTag<signed long int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::SIGNED_LONG_INT;
};
template <class TypeTags>
struct TypeTag<unsigned long int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::UNSIGNED_LONG_INT;
};
template <class TypeTags>
struct TypeTag<signed long long int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::SIGNED_LONG_LONG_INT;
};
template <class TypeTags>
struct TypeTag<unsigned long long int, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::UNSIGNED_LONG_LONG_INT;
};
template <class TypeTags>
struct TypeTag<float, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::FLOAT;
};
template <class TypeTags>
struct TypeTag<double, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::DOUBLE;
};
template <class TypeTags>
struct TypeTag<long double, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::LONG_DOUBLE;
};
template <class TypeTags>
struct TypeTag<void, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::VOID_;
};
template <class TypeTags>
struct TypeTag<std::nullptr_t, TypeTags> {
typedef TypeTags TypeTags_;
static const auto TAG = TypeTags::NULLPTR;
};
#define TYPE_TAG(Object) TypeTag<typename std::decay<decltype(Object)>::type>::TAG
#endif // TypeHelpersH
#ifndef MacroUtilsH
#define MacroUtilsH
namespace MacroUtils {
#define M_S_(arg) #arg
#define MAKE_STR_(arg) M_S_(arg)
}
#endif // MacroUtilsH
#ifndef FuncUtilsH
#define FuncUtilsH
//// [!] Version 1.012 [!]
#include <utility> // for 'std::move', 'std::forward'
#include <type_traits> // for 'std::is_same'
/* Use following define guard to avoid multiple definitions:
#ifndef <func. / proc. name>_EXEC_MEMBER_<FUNC / PROC>_IF_PRESENT_
#define <func. / proc. name>_EXEC_MEMBER_<FUNC / PROC>_IF_PRESENT_
EXEC_MEMBER_<FUNC / PROC>_IF_PRESENT(<func. / proc. name>, <default val.>)
#endif
Example:
#ifndef getHashIfKnown_EXEC_MEMBER_FUNC_IF_PRESENT_
#define getHashIfKnown_EXEC_MEMBER_FUNC_IF_PRESENT_
EXEC_MEMBER_FUNC_IF_PRESENT(getHashIfKnown, size_t())
#endif
*/
/* [!] Does NOT works correctly with the 'std::reference_wrapper':
do NOT use 'std::ref' NOR 'std::cref' here [!]
*/
// If NOT exists - returns the 'DefaultValue',
// which SHOULD be the same type as a decltype(*.FuncName())
// Works with static/const/virtual funcs
#define EXEC_MEMBER_FUNC_IF_PRESENT(FuncName, DefaultValue) namespace FuncName {\
template <typename TReturnType = decltype(DefaultValue), typename... TArgs>\
auto ExecIfPresent(TArgs&&... args) -> TReturnType {\
return std::move(DefaultValue);\
}\
\
template <class C, typename... TArgs>\
auto ExecIfPresent(C& obj, TArgs&&... args)\
/*Remove prototype from the overload resolution if such a callable (func./functor) is NOT exist*/\
-> decltype(obj.FuncName(std::forward<TArgs>(args)...)) /*NOT 'const C& obj' NOR 'C::FuncName()'*/\
{\
return std::move(obj.FuncName(std::forward<TArgs>(args)...));\
}\
};
#endif // FuncUtilsH
#ifndef MemUtilsH
#define MemUtilsH
//// [!] Version 1.003 [!]
namespace MemUtils {
#ifndef AUTO_ADJUST_MEM
#define AUTO_ADJUST_MEM(MemSize, Alignment) (MemSize) + (((MemSize) % (Alignment)) ?\
((Alignment) - ((MemSize) % (Alignment)))\
: 0U)
#endif
};
#include <cstring>
#include <cassert>
#endif // MemUtilsH
#ifndef MathUtilsH
#define MathUtilsH
//// [!] Version 1.023 [!]
#include <cstring>
#include <cassert>
#include <cfloat> // for 'LDBL_DIG'
#include <cstdio>
#include <math.h> // for 'modfl'
#include <mutex>
#include <atomic>
#include <limits>
#include <algorithm>
// Abstract
// [!] In C++14 many of this funcs can be 'constexpr' [!]
class MathUtils {
public:
// Up to 10^18; returns zero if degree > 18
// Complexity: constant - O(1)
static long long int getTenPositiveDegree(const size_t degree = 0U) throw() {
static const long long int TABLE[] =
// 32 bit
{1LL, 10LL, 100LL, 1000LL, 10000LL, 100000LL, 1000000LL, 10000000LL, 100000000LL, 1000000000LL,
// 64 bit
10000000000LL, 100000000000LL, 1000000000000LL, 10000000000000LL, 100000000000000LL,
1000000000000000LL, 10000000000000000LL, 100000000000000000LL, 1000000000000000000LL};
return degree < (sizeof(TABLE) / sizeof(*TABLE)) ? TABLE[degree] : 0LL; // 0 if too high
}
// Returns 1 for (0, 21), 6 for (1, 360), 2 for (1, 120), 7 for (0, 7), 4 for (1, 40);
// 0 for (0, 920) OR (3, 10345) OR (0, 0) OR (4, 10) etc
// 'allBelowOrderDigitsIsZero' will be true for
// (0, 7) OR (0, 20) OR (1, 130) OR (2, 12300) OR (0, 0)
// Negative numbers are allowed; order starts from zero
// (for number 3219 zero order digit is 9)
// Complexity: constant - O(1)
static size_t getDigitOfOrder(const size_t order, const long long int num,
bool& allBelowOrderDigitsIsZero) throw()
{
const auto degK = getTenPositiveDegree(order);
const auto shiftedVal = num / degK; // shifting to the speicified order, ignoring remnant
allBelowOrderDigitsIsZero = num == (shiftedVal * degK); // shift back without adding remnant
return static_cast<size_t>(std::abs(shiftedVal % 10LL)); // getting the exact digit
}
};
#endif // MathUtilsH
#ifndef ConvertionUtilsH
#define ConvertionUtilsH
//// [!] Version 1.114 [!]
#include <cfloat> // for 'LDBL_DIG'
#include <cerrno> // for 'ERANGE'
#include <climits> // for 'LONG_MIN'
EXEC_MEMBER_FUNC_IF_PRESENT(truncated, false) // special for limited size storage
namespace ConvertionUtils {
// A wrapper to the standart 'strtol' with the extended error recognition
// In case of error sets 'errMsg' to the static non-empty str.
// ('strerror_s' can be used then to get a system specific error message (if exists))
static bool strToL(long int& num, const char* const str,
const char*& errMsg, const int base = 10) throw()
{
if (!str) {
num = std::decay<decltype(num)>::type(); // reset
errMsg = "invalid pointer";
return false;
}
if (!*str) {
num = std::decay<decltype(num)>::type(); // reset
errMsg = "empty string";
return false;
}
char* endPtr = nullptr;
/* If this variable is intended to be used for error checking after a std. C library function call,
it SHOULD be reset by the program to zero before the call
(since ANY previous call to a library function may have altered its value)
*/
errno = 0; // clear error (NO library function sets its value back to zero once changed)
num = strtol(str, &endPtr, base);
// 'errno' CAN be set on error ('ERANGE'); 'endptr' SHOULD point to the str. terminator
if (errno || *endPtr) { // C++11 requires 'errno' to be implemented in a per-thread basis
if (ERANGE == errno) { // range error
switch (num) {
case LONG_MIN: errMsg = "too low value"; break;
case LONG_MAX: errMsg = "too big value"; break;
default: errMsg = "out of range";
} // functions of the standard library may set 'errno' to ANY value
} else errMsg = "invalid string"; // NOT a range error; some incorrect symb. is met probably
num = std::decay<decltype(num)>::type();
return false;
}
errMsg = "";
return true;
};
// In AMERICAN English, many students are taught NOT to use the word "and"
// anywhere in the whole part of a number
// "and" delimiter is used ALSO in Irish, Australian AND New Zealand English
static const char* const ENG_GB_VERBAL_DELIMITER = "and";
static const char DEFAULT_DELIMITER = ' ';
// Do NOT add other dialects of english e.g. Irish, Australian, New Zealand, Indian etc
enum ELocale {
L_RU_RU, // Russian Federation Russian
L_EN_US, // United States English
L_EN_GB, // United Kingdom English
COUNT // USED ONLY to get the enum elems count
};
struct LocaleSettings {
LocaleSettings() = default;
~LocaleSettings() = default;
LocaleSettings(const LocaleSettings&) = default;
LocaleSettings& operator=(const LocaleSettings&) = default;
static const LocaleSettings DEFAULT_LOCALE_SETTINGS;
// Enables some language very specific rules for numbers spelling
// (like pronouncing four-digit numbers in US & UK Eng.)
bool verySpecific = false;
bool positiveSign = false; // add positive sign [for positive nums]
// Если целая часть равна нулю, то она может не читаться: 0.75 (.75) – point seventy five
bool shortFormat = false; // skip mention zero int. / fract. part
bool foldFraction = false; // try find repeated pattern & treat it
ELocale locale = ELocale::L_EN_GB;
size_t precison = size_t(LDBL_DIG); // max. digits count (<= 'LDBL_DIG')
};
// TO DO:
// - add ordinal numbers support
// http://e...content-available-to-author-only...a.org/wiki/English_numerals#Ordinal_numbers
// - add multiplicative adverbs support
// http://e...content-available-to-author-only...a.org/wiki/English_numerals#Multiplicative_adverbs
// - add long / short scale support
// https://e...content-available-to-author-only...a.org/wiki/Long_and_short_scales
// - add money support (writing a cheque (or check),
// the number 100 is always written "one hundred", it is never "a hundred")
// [enum: money, count, index, dates, temperatures -
// http://e...content-available-to-author-only...a.org/wiki/English_numerals#Negative_numbers]
// - add specialized numbers support
// http://e...content-available-to-author-only...a.org/wiki/English_numerals#Specialized_numbers
// - add special fractions support: 1/2 — a half [«one half»]; 1/3 — a / one third; 1/4: «one quarter»
// ['HandleSpecificValues' - if 'verySpecific' is true]
// - add 'double o' 'triple o' etc folding support [0.001 — 'nought point double о one']
/* NOTES on known, BUT does NOT supported features:
1) Ten thousand is RARELY called a myriad [do NOT used, as processing by triades]
2) Americans may pronounce four-digit numbers with non-zero tens AND/OR ones
as pairs of two-digit numbers without saying "hundred"
AND inserting "oh" for zero tens: "twenty-six fifty-nine" or "forty-one oh five"
(currently uses ANOTHER format: 9522 = 'ninety-five hundred twenty-two')
3) American English: 1,200,000 = 1.2 million = one point two million;
23,380,000,000 = 23.38 billion = twenty-three point three eight billion
[such folding currently does NOT supported]
4) English fractions can ALSO use russian-style representation:
'one ten-thousandth' (одна десятитысячная) [0.005 five thousandths] [0.54 fifty four hundredths]
*/
// 'TStrType' SHOULD support operator '+=', 'empty' AND 'size' methods
// 'ReserveBeforeAdding' can be used to DISABLE possible 'trade-space-for-time' optimization
template<class TStrType, const bool ReserveBeforeAdding = true>
// "Number to the numeric format string" (321 -> "three hundred twenty-one")
// Accpets negative numbers AND fractions
// Complexity: linear in the number's digit count
static bool numToNumFormatStr(long double num, TStrType& str,
LocaleSettings& localeSettings =
LocaleSettings::DEFAULT_LOCALE_SETTINGS,
const char** const errMsg = nullptr) {
auto negativeNum = false;
if (num < 0.0L) {
negativeNum = true;
num = -num; // revert
}
//// Check borders
static const auto VAL_UP_LIMIT_ = 1e100L; // see 'getOrderStr'
if (num >= VAL_UP_LIMIT_) {
if (errMsg) *errMsg = "too big value";
return false;
}
if (ELocale::L_RU_RU == localeSettings.locale) { // for rus. lang. ONLY
static const auto VAL_LOW_LIMIT_RU_ = 10.0L / VAL_UP_LIMIT_;
if (num && num < VAL_LOW_LIMIT_RU_) {
if (errMsg) *errMsg = "too low value";
return false;
}
}
//// Treat sign
const auto delimiter = DEFAULT_DELIMITER;
auto getSignStr = [](const ELocale locale, const bool positive) throw() -> const char* {
switch (locale) {
case ELocale::L_EN_US: return positive ? "positive" : "negative";
case ELocale::L_EN_GB: return positive ? "plus" : "minus";
case ELocale::L_RU_RU: return positive ? "плюс" : "минус";
}
assert(false); // locale error
// Design / implementation error, NOT runtime error!
return "<locale error [" MAKE_STR_(__LINE__) "]>"; // works OK in GCC
};
if (negativeNum || (localeSettings.positiveSign && num)) { // add sign
if (!str.empty()) str += delimiter; // if needed
str += getSignStr(localeSettings.locale, !negativeNum);
}
if (truncated::ExecIfPresent(str)) { // check if truncated
if (errMsg) *errMsg = "too short buffer"; return false;
}
//// Get str. representation in the scientific notation
// +2(+3) for rounding without loosing the precison: http://stackoverflow.com/questions/16839658
// (see also https://e...content-available-to-author-only...a.org/wiki/Extended_precision#Need_for_the_80-bit_format)
/* SOME compilers support a 'long double' format that has more precision than 'double'
(Microsoft MSVC is NOT, BUT Borland C++ BuilderX has 'LDBL_DIG' = 18)
'LDBL_DIG' is the number of decimal digits that can be represented without losing precision
*/
// 'printf("%.*Lf", 999, 1.1L)': '1.100000000000000000021684043449710088680149056017398834228515625'
/* 'std::numeric_limits<T>::digits10': number of base-10 digits that can be represented by the type 'T'
without change - ANY number with this many decimal digits can be converted to a value of type 'T'
AND back to decimal form, without change due to rounding OR overflow
*/
static const size_t MAX_DIGIT_COUNT_ = size_t(LDBL_DIG);
// Normalized form (mantissa is a 1 digit ONLY):
// first digit (one of 'MAX_DIGIT_COUNT_') + '.' + [max. digits AFTER '.' - 1] + 'e+000'
// [https://e...content-available-to-author-only...a.org/wiki/Scientific_notation#Normalized_notation]
static const size_t MAX_STR_LEN_ = 6U + MAX_DIGIT_COUNT_;
// +24 to be on a safe side in case if NOT normalized form (unlikely happen) + for str. terminator
static const size_t BUF_SIZE_ = AUTO_ADJUST_MEM(MAX_STR_LEN_ + 24U, 8U);
char strBuf[BUF_SIZE_];
// 21 digits is max. for 'long double' [https://msdn.microsoft.com/ru-ru/library/4hwaceh6.aspx]
// (20 of them can be AFTER decimal point in the normalized scientific notation)
if (localeSettings.precison > MAX_DIGIT_COUNT_) localeSettings.precison = MAX_DIGIT_COUNT_;
const ptrdiff_t len = sprintf(strBuf, "%.*Le", localeSettings.precison, num); // scientific format
// On failure, a negative number is returned
if (len < static_cast<decltype(len)>(localeSettings.precison)) {
if (errMsg) *errMsg = "number to string convertion failed";
return false;
}
//// Fraction repeated pattern recognition [123 from 1.123123, 7 from 123.777 etc]
//// [!] Warning: does NOT work with the zero ending patterns (like '0.15015') [!]
// (add zeroes to the end of the str. to match correctly??)
// Return nullptr if a pattern of such a len. is EXISTS (returns last NOT matched occurrence else)
auto testPattern = [](const char* const str, const char* const strEnd,
const size_t patternSize) throw() {
assert(str); // SHOULD NOT be nullptr
auto equal = true;
auto nextOccurance = str + patternSize;
while (true) {
if (memcmp(str, nextOccurance, patternSize)) return nextOccurance; // NOT macthed
nextOccurance += patternSize;
if (nextOccurance >= strEnd) return decltype(nextOccurance)(); // ALL matched, return nullptr
}
};
// Retruns pattern size if pattern exist, 0 otherwise
// TO DO: add support for advanced folding: 1.25871871 [find repeated pattern NOT ONLY from start]
// [in cycle: str+1, str+2, ...; get pattern start, pattern len. etc in 'tryFindPatternEx']
// ['сто двадцать целых двадцать пять до периода и шестьдесят семь в периоде']
// [controled by 'enableAdvancedFolding' new option]]
auto tryFindPattern = [&](const char* const str, const size_t totalLen) throw() {
const size_t maxPatternLen = totalLen / size_t(2U);
auto const strEnd = str + totalLen; // past the end
for (auto patternSize = size_t(1U); patternSize <= maxPatternLen; ++patternSize) {
if (totalLen % patternSize) continue; // skip invalid dividers [OPTIMIZATION]
if (!testPattern(str, strEnd, patternSize)) return patternSize;
}
return size_t();
};
//// Analyze str. representation in the scientific notation
char* currSymbPtr; // ptr. used to iterate over the numeric str.
char* fractPartStart; // in the original scientific representation
char* fractPartEnd; // past the end [will point to the str. terminator, replacing the exp. sign]
long int expVal; // 3 for '1.0e3'
auto fractPartLen = ptrdiff_t();
size_t intPartLen; // real len.
size_t intPartBonusOrder; // of the current digit
size_t fractPartLeadingZeroesCount; // extra zeroes count BEFORE first meaning digit
static const auto DECIMAL_DELIM_ = '.'; // [decimal separator / decimal mark] to use
auto analyzeScientificNotationRepresentation = [&]() throw() {
currSymbPtr = strBuf + len - size_t(1U); // from the end to start (<-)
//// Get exp.
static const auto EXP_SYMB_ = 'e';
while (EXP_SYMB_ != *currSymbPtr) {
--currSymbPtr; // rewind to the exp. start
assert(currSymbPtr > strBuf);
}
fractPartEnd = currSymbPtr;
*currSymbPtr = '\0'; // break str.: 2.22044604925031310000e+016 -> 2.22044604925031310000 +016
const char* errMsg;
const auto result = strToL(expVal, currSymbPtr + size_t(1U), errMsg);
assert(result);
//// Get int. part len.
fractPartStart = currSymbPtr - localeSettings.precison;
intPartLen = fractPartStart - strBuf;
assert(intPartLen);
if (localeSettings.precison) --intPartLen; // treat zero fract. precison ('1e0')
assert((currSymbPtr - strBuf - int(localeSettings.precison) - 1) >= 0);
assert(localeSettings.precison ? DECIMAL_DELIM_ == *(strBuf + intPartLen) : true);
//// Finishing analyse (partition the number): get int. part real len.
if (expVal < 0L) { // negative exp.
if (static_cast<size_t>(-expVal) >= intPartLen) { // NO int. part
fractPartLeadingZeroesCount = -(expVal + static_cast<long int>(intPartLen));
intPartLen = size_t(); // skip processing int. part
} else { // reduce int. part
intPartLen += expVal; // decr. len.
fractPartLeadingZeroesCount = size_t();
}
intPartBonusOrder = size_t();
if (localeSettings.precison) // if fract. part exists [in the scientific represent.]
--fractPartLen; // move delim. into the fract part., so reduce it length
} else { // non-negative exp.: incr. len.
const auto additive =
std::min<decltype(localeSettings.precison)>(expVal, localeSettings.precison);
intPartLen += additive;
fractPartLeadingZeroesCount = size_t();
intPartBonusOrder = expVal - additive;
}
};
analyzeScientificNotationRepresentation();
// Rewind to the fract. start [BEFORE getting fract. part real len.]
currSymbPtr = strBuf + intPartLen +
(expVal > decltype(expVal)() ? size_t(1U) : size_t()); // 1.23e1 = 12.3e0 [move right +1]
auto fractPartTrailingZeroesCount = size_t(), fractPartAddedCount = size_t();
char* fractPartRealStart;
auto folded = false; // true if repeated pattern founded
auto calcFractPartRealLen = [&]() throw() {
if (DECIMAL_DELIM_ == *currSymbPtr) ++currSymbPtr; // skip delimiter when it separtes ('1.1e0')
assert(fractPartEnd >= currSymbPtr); // 'currSymbPtr' SHOULD now be a real fract. part start
fractPartRealStart = currSymbPtr;
fractPartLen += fractPartEnd - currSymbPtr; // 'fractPartLen' CAN be negative BEFORE addition
assert(fractPartLen >= ptrdiff_t()); // SHOULD NOT be negative now
if (!fractPartLen) return; // NO fract. part
//// Skip trailing zeroes
auto fractPartCurrEnd = fractPartEnd - size_t(1U); // will point to the last non-zero digit symb.
while ('0' == *fractPartCurrEnd && fractPartCurrEnd >= currSymbPtr) --fractPartCurrEnd;
assert(fractPartCurrEnd >= strBuf); // SHOULD NOT go out of the buf.
fractPartTrailingZeroesCount = fractPartEnd - fractPartCurrEnd - size_t(1U);
assert(fractPartLeadingZeroesCount >= size_t() &&
fractPartLen >= static_cast<ptrdiff_t>(fractPartTrailingZeroesCount));
fractPartLen -= fractPartTrailingZeroesCount;
//// Fraction folding (if needed)
if (fractPartLen > size_t(1U) && localeSettings.foldFraction) {
//// Remove delim. (if needed)
assert(fractPartStart && fractPartStart > strBuf); // SHOULD be setted (delim. founded)
if (fractPartRealStart < fractPartStart) { // move: "12.1e-1" -> "1 21e-1"
currSymbPtr = fractPartStart - size_t(1U);
assert(*currSymbPtr == DECIMAL_DELIM_);
while (currSymbPtr > fractPartRealStart)
*currSymbPtr-- = *(currSymbPtr - size_t(1U)); // reversed move
*currSymbPtr = '\0';
fractPartRealStart = currSymbPtr + size_t(1U); // update, now SHOULD point to the new real start
assert(fractPartLen);
}
//// Actual folding (if needed)
if (fractPartLen > size_t(1U)) {
const auto patternLen = tryFindPattern(fractPartRealStart, fractPartLen);
if (patternLen) {
fractPartLen = patternLen; // actual folding (reduce fract. part len. to the pattern. len)
folded = true;
}
}
}
};
// We are NOT using 'modfl' to get part values trying to optimize by skipping zero parts
calcFractPartRealLen(); // update len.
assert(fractPartLen ? localeSettings.precison : true);
const auto fractPartWillBeMentioned = fractPartLen || !localeSettings.shortFormat;
currSymbPtr = strBuf; // start from the beginning, left-to-right (->)
//// Language-specific morphological lambdas (a result of the morphological analysis)
//// [by default they returns numerals in the nominative case]
/* Word can have up to a 3 morphems (affixes) in addition to the root:
1) prefix: placed BEFORE the stem of a word
2) infix: inserted INSIDE a word stem
OR
interfix: [linkage] placed in BETWEEN two morphemes AND does NOT have a semantic meaning
3) postfix: (suffix OR ending) placed AFTER the stem of a word
Word = [prefix]<root>[infix / interfix][postfix (suffix, ending)]
Online tools:
склонение:
http://m...content-available-to-author-only...r.ru/Demo.aspx
http://w...content-available-to-author-only...a.ru
http://n...content-available-to-author-only...e.ru
spelling:
http://w...content-available-to-author-only...h.com/saynum.html
http://e...content-available-to-author-only...5.ru/en/numbers_translation
http://p...content-available-to-author-only...w.com/numbers/index_en.htm
http://w...content-available-to-author-only...s.com/explore/reallybignumbers.html
*/
/// RU: returns root, which can be used to add ending of the appropriate case
/// (by default, returns ending for the nominative case) [нолЬ / нолЯ / нолЮ | нулём! нолём?]
// 0 - 9 [1]
auto getZeroOrderNumberStr = [&](const size_t currDigit, const size_t order, const char*& postfix,
const LocaleSettings& localeSettings) throw() -> const char* {
static const char* const EN_TABLE[] = // roots
{"", "one", "tw", "th", "fo", "fi", "six", "seven", "eigh", "nine"};
static const char* const EN_POSTFIXES[] = // endings
{"", "", "o", "ree", "ur", "ve", "", "", "t", ""};
static const char* const RU_TABLE[] =
{"нол", "од", "дв", "тр", "четыр", "пят", "шест", "сем", "вос", "девят"};
static const char* const RU_POSTFIXES[] = // восЕМЬ восЬМИ восЕМЬЮ
// одИН одНОГО одНОМУ одНИМ; двА двУХ двУМ двУМЯ; трИ трЕМЯ; четырЕ четырЬМЯ четырЁХ
{"ь", "ин", "а", "и", "е", "ь", "ь", "ь", "емь", "ь"};
// НолЬ нолЯ нолЮ; пятЬ пятЬЮ пятЕРЫХ; шестЬ шестЬЮ шестИ; семЬ семИ семЬЮ; девятЬ девятЬЮ девятИ
static_assert(sizeof(EN_TABLE) == sizeof(RU_TABLE) && sizeof(EN_TABLE) == sizeof(EN_POSTFIXES) &&
sizeof(RU_TABLE) == sizeof(RU_POSTFIXES) &&
size_t(10U) == std::extent<decltype(EN_TABLE)>::value,
"Tables SHOULD have the same size (10)");
assert(currDigit < std::extent<decltype(EN_TABLE)>::value); // is valid digit?
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB:
postfix = EN_POSTFIXES[currDigit];
if (!currDigit) { // en.wikipedia.org/wiki/Names_for_the_number_0_in_English
// American English:
// zero: number by itself, decimals, percentages, phone numbers, some fixed expressions
// o (letter): years, addresses, times and temperatures
// nil: sports scores
if (localeSettings.verySpecific) return "o"; // 'oh'
return localeSettings.locale == ELocale::L_EN_US ? "zero" : "nought";
}
return EN_TABLE[currDigit];
case ELocale::L_RU_RU:
postfix = "";
switch (order) {
case size_t(0U): // last digit ['двадцать две целых ноль десятых']
// Один | одНА целая ноль десятых | одна целая одНА десятая
if (!fractPartWillBeMentioned) break;
case size_t(3U): // тысяч[?]
switch (currDigit) {
case size_t(1U): postfix = "на"; break; // 'ста двадцать одНА тысяча'
case size_t(2U): postfix = "е"; break; // 'ста двадцать двЕ тысячи' []
}
break;
}
if (!*postfix) postfix = RU_POSTFIXES[currDigit]; // if NOT setted yet
return RU_TABLE[currDigit];
}
assert(false); // locale error
return "<locale error [" MAKE_STR_(__LINE__) "]>";
};
// 10 - 19 [1]; 20 - 90 [10]
auto getFirstOrderNumberStr = [&](const size_t currDigit, const size_t prevDigit,
const char*& infix, const char*& postfix,
const LocaleSettings& localeSettings) throw() -> const char* {
//// Sub. tables: 10 - 19 [1]; Main tables: 20 - 90 [10]
static const char* const EN_SUB_TABLE[] = {"ten", "eleven"}; // exceptions [NO infixes / postfixes]
static const char* const EN_SUB_INFIXES[] = // th+ir+teen; fo+ur+teen; fi+f+teen
{"", "", "", "ir", "ur", "f", "", "", "", ""};
#define ESP_ "teen" // EN_SUB_POSTFIX
static const char* const EN_SUB_POSTFIXES[] = // tw+elve ["a dozen"]; +teen ALL others
{"", "", "elve", ESP_, ESP_, ESP_, ESP_, ESP_, ESP_, ESP_}; // +teen of ALL above 2U (twelve)
static const char* const EN_MAIN_INFIXES[] = // tw+en+ty ["a score"]; th+ir+ty; fo+r+ty; fi+f+ty
{"", "", "en", "ir", "r", "f", "", "", "", ""}; // +ty ALL
#define R23I_ "дцат" // RU_20_30_INFIX [+ь]
#define RT1I_ "на" R23I_ // RU_TO_19_INFIX [на+дцат+ь]
static const char* const RU_SUB_INFIXES[] = // +ь; одиннадцатЬ одиннадцатИ одиннадцатЬЮ
// ДесятЬ десятИ десятЬЮ; од и надцат ь / тр и надцат ь; дв е надцат ь; вос ем надцат ь
{"", "ин" RT1I_, "е" RT1I_, "и" RT1I_, RT1I_, RT1I_, RT1I_, RT1I_, "ем" RT1I_, RT1I_};
// ДвадцатЬ двадцатЬЮ двадцатЫЙ двадцатОМУ двадцатИ; семьдесят BUT семидесяти!
#define R5T8I_ "ьдесят" // RU_50_TO_80_INFIX [NO postfix]
static const char* const RU_MAIN_INFIXES[] = // дв а дцат ь; тр и дцат ь; пят шест сем +ьдесят
{"", "", "а" R23I_, "и" R23I_, "", R5T8I_, R5T8I_, R5T8I_, "ем" R5T8I_, ""}; // вос ем +ьдесят
static const char* const RU_MAIN_POSTFIXES[] = // дв а дцат ь; тр и дцат ь; пят шест сем +ьдесят
{"", "", "ь", "ь", "", "", "", "", "", "о"}; // сорок; вос ем +ьдесят; девяност о девяност а
static_assert(sizeof(EN_SUB_INFIXES) == sizeof(EN_MAIN_INFIXES) &&
sizeof(EN_SUB_POSTFIXES) == sizeof(RU_MAIN_POSTFIXES) &&
sizeof(RU_SUB_INFIXES) == sizeof(RU_MAIN_INFIXES), "Tables SHOULD have the same size");
assert(prevDigit < std::extent<decltype(EN_SUB_POSTFIXES)>::value); // is valid digits?
assert(currDigit < std::extent<decltype(EN_SUB_POSTFIXES)>::value);
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB:
switch (prevDigit) {
case size_t(1U): // ten - nineteen
infix = EN_SUB_INFIXES[currDigit], postfix = EN_SUB_POSTFIXES[currDigit];
if (currDigit < size_t(2U)) return EN_SUB_TABLE[currDigit]; // exceptions
break;
default: // twenty - ninety
assert(!prevDigit && currDigit > size_t(1U));
infix = EN_MAIN_INFIXES[currDigit], postfix = "ty"; // +ty for ALL
break;
}
break;
case ELocale::L_RU_RU:
switch (prevDigit) {
case size_t(1U): // десять - девятнадцать
infix = RU_SUB_INFIXES[currDigit], postfix = "ь"; // +ь for ALL
if (!currDigit) return "десят";
break;
default: // двадцать - девяносто
assert(currDigit > size_t(1U));
infix = RU_MAIN_INFIXES[currDigit], postfix = RU_MAIN_POSTFIXES[currDigit];
switch (currDigit) {
case size_t(4U): return "сорок"; // сорокА
case size_t(9U): return "девяност"; // девяностО девяностЫХ девяностЫМ
}
break;
}
break;
default: assert(false); // locale error
return "<locale error [" MAKE_STR_(__LINE__) "]>";
} // END switch (locale)
const char* tempPtr;
return getZeroOrderNumberStr(currDigit, size_t(), tempPtr, localeSettings);
};
// 100 - 900 [100]
auto getSecondOrderNumberStr = [&](const size_t currDigit, const char*& infix, const char*& postfix,
const LocaleSettings& localeSettings) throw() -> const char* {
static const char* const RU_POSTFIXES[] =
{"", "", "сти", "ста", "ста", "сот", "сот", "сот", "сот", "сот"};
static_assert(size_t(10U) == std::extent<decltype(RU_POSTFIXES)>::value,
"Table SHOULD have the size of 10");
assert(currDigit && currDigit < std::extent<decltype(RU_POSTFIXES)>::value);
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB:
postfix = " hundred";
return getZeroOrderNumberStr(currDigit, size_t(), infix, localeSettings);
case ELocale::L_RU_RU:
postfix = RU_POSTFIXES[currDigit];
switch (currDigit) {
case size_t(1U): infix = ""; return "сто"; break;
case size_t(2U): {
const char* temp;
infix = "е"; //ALWAYS 'е'
return getZeroOrderNumberStr(currDigit, size_t(), temp, localeSettings); // дв е сти
}
}
return getZeroOrderNumberStr(currDigit, size_t(), infix, localeSettings);
} // END switch (locale)
assert(false); // locale error
return "<locale error [" MAKE_STR_(__LINE__) "]>";
};
// Up to 10^99 [duotrigintillions]
auto getOrderStr = [](size_t order, const size_t preLastDigit, const size_t lastDigit,
const char*& postfix, const LocaleSettings& localeSettings)
throw() -> const char* {
// https://e...content-available-to-author-only...a.org/wiki/Names_of_large_numbers
static const char* const EN_TABLE[] = // uses short scale (U.S., part of Canada, modern British)
{"", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion",
"septillion", "octillion", "nonillion", "decillion", "undecillion", "duodecillion" /*10^39*/,
"tredecillion", "quattuordecillion", "quindecillion", "sedecillion", "septendecillion",
"octodecillion", "novemdecillion ", "vigintillion", "unvigintillion", "duovigintillion",
"tresvigintillion", "quattuorvigintillion", "quinquavigintillion", "sesvigintillion",
"septemvigintillion", "octovigintillion", "novemvigintillion", "trigintillion" /*10^93*/,
"untrigintillion", "duotrigintillion"};
// https://r...content-available-to-author-only...a.org/wiki/Именные_названия_степеней_тысячи
static const char* const RU_TABLE[] = // SS: short scale, LS: long scale
{"", "тысяч", "миллион", "миллиард" /*SS: биллион*/, "триллион" /*LS: биллион*/,
"квадриллион" /*LS: биллиард*/, "квинтиллион" /*LS: триллион*/,
"секстиллион" /*LS: триллиард*/, "септиллион" /*LS: квадриллион*/, "октиллион", "нониллион",
"дециллион", "ундециллион", "додециллион", "тредециллион", "кваттуордециллион" /*10^45*/,
"квиндециллион", "седециллион", "септдециллион", "октодециллион", "новемдециллион",
"вигинтиллион", "анвигинтиллион", "дуовигинтиллион", "тревигинтиллион", "кватторвигинтиллион",
"квинвигинтиллион", "сексвигинтиллион", "септемвигинтиллион", "октовигинтиллион" /*10^87*/,
"новемвигинтиллион", "тригинтиллион", "антригинтиллион", "дуотригинтиллион"}; // 10^99
static_assert(sizeof(EN_TABLE) == sizeof(RU_TABLE), "Tables SHOULD have the same size");
static const size_t MAX_ORDER_ =
(std::extent<decltype(EN_TABLE)>::value - size_t(1U)) * size_t(3U); // first empty
static const char* const RU_THOUSAND_POSTFIXES[] = // десять двадцать сто двести тысяч
// Одна тысячА | две три четыре тысячИ | пять шесть семь восемь девять тысяч
{"", "а", "и", "и", "и", "", "", "", "", ""};
static const char* const RU_MILLIONS_AND_BIGGER_POSTFIXES[] = // один миллион; два - четыре миллионА
// Пять шесть семь восемь девять миллионОВ [миллиардОВ триллионОВ etc]
// Десять двадцать сто двести миллионОВ миллиардОВ etc
{"ов", "", "а", "а", "а", "ов", "ов", "ов", "ов", "ов"};
static_assert(size_t(10U) == std::extent<decltype(RU_THOUSAND_POSTFIXES)>::value &&
size_t(10U) == std::extent<decltype(RU_MILLIONS_AND_BIGGER_POSTFIXES)>::value,
"Tables SHOULD have the size of 10");
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB:
postfix = "";
if (size_t(2U) == order) return "hundred"; // 0U: ones, 1U: tens
order /= 3U; // 0 - 1: empty, 3 - 5: thousands, 6 - 8: millions, 9 - 11: billions etc
assert(order < std::extent<decltype(EN_TABLE)>::value);
return EN_TABLE[order]; // [0, 33]
case ELocale::L_RU_RU:
assert(preLastDigit < size_t(10U) && lastDigit < size_t(10U));
if (size_t(3U) == order) { // determine actual postfix first
if (size_t(1U) != preLastDigit) {
postfix = RU_THOUSAND_POSTFIXES[lastDigit];
} else postfix = ""; // 'тринадцать тысяч'
} else if (order > size_t(3U)) { // != 3U
if (size_t(1U) == preLastDigit) { // десять одиннадцать+ миллионОВ миллиардОВ etc
postfix = "ов";
} else postfix = RU_MILLIONS_AND_BIGGER_POSTFIXES[lastDigit];
}
order /= 3U; // 6 - 8: миллионы, 9 - 11: миллиарды etc
assert(order < std::extent<decltype(RU_TABLE)>::value);
return RU_TABLE[order]; // [0, 33]
}
assert(false); // locale error
return "<locale error [" MAKE_STR_(__LINE__) "]>";
};
// 'intPartPreLastDigit' AND 'intPartLastDigit' CAN be negative (in case of NO int. part)
auto getFractionDelimiter = [](const ptrdiff_t intPartPreLastDigit, const ptrdiff_t intPartLastDigit,
const char*& postfix, const bool folded,
const LocaleSettings& localeSettings) throw() -> const char* {
assert(intPartPreLastDigit < ptrdiff_t(10) && intPartLastDigit < ptrdiff_t(10));
postfix = "";
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB: return "point"; // also 'decimal'
case ELocale::L_RU_RU: // "целые" НЕ употребляются в учебниках!
if (intPartLastDigit < ptrdiff_t() && localeSettings.shortFormat) return ""; // NO int. part
if (folded) postfix = "и";
return ptrdiff_t(1) == intPartLastDigit ?
(ptrdiff_t(1) == intPartPreLastDigit ? "целых" : "целая") : // одинадцать целЫХ | одна целАЯ
"целых"; // ноль, пять - девять целЫХ; две - четыре целЫХ; десять цел ых
}
assert(false); // locale error
return "<locale error [" MAKE_STR_(__LINE__) "]>";
};
auto getFoldedFractionEnding = [](const LocaleSettings& localeSettings) throw() {
// Also possibly 'continuous', 'recurring'; 'reoccurring' (Australian)
switch (localeSettings.locale) {
case ELocale::L_EN_US: return "to infinity"; // also 'into infinity', 'to the infinitive'
case ELocale::L_EN_GB: return "repeating"; // also 'repeated'
case ELocale::L_RU_RU: return "в периоде";
}
assert(false); // locale error
return "<locale error [" MAKE_STR_(__LINE__) "]>";
};
//// Language-specific processing lambdas
auto getMinDigitsSubPartSizeToAddOrder = [](const LocaleSettings& localeSettings) throw() {
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB: return size_t(2U); // hundreds
case ELocale::L_RU_RU: return size_t(3U); // тысячи
}
assert(false); // locale error
return size_t();
};
// Returns zero (NOT set, undefined) if NOT spec. case
auto getSpecificCaseSubPartSize = [](const long double& num,
const LocaleSettings& localeSettings) throw() {
switch (localeSettings.locale) {
/*
In American usage, four-digit numbers with non-zero hundreds
are often named using multiples of "hundred"
AND combined with tens AND/OR ones:
"One thousand one", "Eleven hundred three", "Twelve hundred twenty-five",
"Four thousand forty-two", or "Ninety-nine hundred ninety-nine"
*/
case ELocale::L_EN_US:
if (num < 10000.0L) {
bool zeroTensAndOnes;
const auto hundreds =
MathUtils::getDigitOfOrder(size_t(2U), static_cast<long long int>(num), zeroTensAndOnes);
if (hundreds && !zeroTensAndOnes) return size_t(2U); // if none-zero hundreds
}
break;
// In British usage, this style is common for multiples of 100 between 1,000 and 2,000
// (e.g. 1,500 as "fifteen hundred") BUT NOT for higher numbers
case ELocale::L_EN_GB:
if (num >= 1000.0L && num < 2001.0L) {
// If ALL digits of order below 2U [0, 1] is zero
if (!(static_cast<size_t>(num) % size_t(100U))) return size_t(2U); // if is multiples of 100
}
break;
}
return size_t();
};
auto getIntSubPartSize = [&]() throw() {
auto subPartSize = size_t();
if (localeSettings.verySpecific)
subPartSize = getSpecificCaseSubPartSize(num, localeSettings); // CAN alter digits subpart size
if (!subPartSize) { // NOT set previously
switch (localeSettings.locale) { // triads by default
// For eng. numbers step = 1 can be ALSO used: 64.705 — 'six four point seven nought five'
case ELocale::L_EN_US: case ELocale::L_EN_GB: case ELocale::L_RU_RU: subPartSize = size_t(3U);
}
}
return subPartSize;
};
auto getFractSubPartSize = [](const LocaleSettings& localeSettings) throw() {
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB:
// Step = 2 OR 3 can be ALSO used: 14.65 - 'one four point sixty-five'
return size_t(1U); // point one two seven
case ELocale::L_RU_RU: return size_t(3U); // сто двадцать семь сотых
}
assert(false); // locale error
return size_t();
};
// Currently there is NO specific handling for 'short format' AND 'very specific' options
auto estimatePossibleLength = [](const size_t digitsPartSize, const bool fractPart,
const LocaleSettings& localeSettings) throw() {
// If processing by the one digit per time; EN GB uses 'nought' instead of 'zero'
static const auto EN_US_AVG_CHAR_PER_DIGIT_NAME_ = size_t(4U); // 40 / 10 ['zero' - 'nine']
static size_t AVG_SYMB_PER_DIGIT_[ELocale::COUNT]; // for ALL langs; if processing by triads
struct ArrayIniter { // 'AVG_SYMB_PER_DIGIT_' initer
ArrayIniter() throw() {
//// All this value is a result of the statistical analysis
AVG_SYMB_PER_DIGIT_[ELocale::L_EN_GB] = size_t(10U); // 'one hundred and twenty two thousand'
AVG_SYMB_PER_DIGIT_[ELocale::L_EN_US] = size_t(9U); // 'one hundred twenty two thousand'
AVG_SYMB_PER_DIGIT_[ELocale::L_RU_RU] = size_t(8U); // 'сто двадцать две тысячи'
}
}; static const ArrayIniter INITER_; // static init. is a thread safe in C++11
static const auto RU_DELIM_LEN_ = size_t(5U); // "целых" / "целая"
// Frequent postfixes (up to trillions: 'десятитриллионных')
static const auto RU_MAX_FREQ_FRACT_POSTFIX_LEN_ = size_t(17U);
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB:
if (!fractPart) return AVG_SYMB_PER_DIGIT_[localeSettings.locale] * digitsPartSize;
// For the fract part [+1 for the spacer]
return (EN_US_AVG_CHAR_PER_DIGIT_NAME_ + size_t(1U)) * digitsPartSize;
case ELocale::L_RU_RU: // RU RU processes fract. part by the triads (like an int. part)
{
size_t len_ = AVG_SYMB_PER_DIGIT_[ELocale::L_RU_RU] * digitsPartSize;
if (fractPart && digitsPartSize) len_ += RU_DELIM_LEN_ + RU_MAX_FREQ_FRACT_POSTFIX_LEN_;
return len_;
}
}
assert(false); // locale error
return size_t();
};
auto addFractionPrefix = [&]() {
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB: // 'nought nought nought' for 1.0003
{
const char* postfix;
for (auto leadingZeroIdx = size_t(); leadingZeroIdx < fractPartLeadingZeroesCount;) {
assert(str.size()); // NOT empty
str += delimiter;
str += getZeroOrderNumberStr(size_t(), leadingZeroIdx, postfix, localeSettings);
str += postfix;
++leadingZeroIdx;
}
return;
}
case ELocale::L_RU_RU: return; // NO specific prefix
}
assert(false); // locale error
};
size_t currDigit, prevDigit;
// 'order' is an order of the last digit of a fractional part + 1 (1 based idx.)
// [1 for the first, 2 for the second etc]
auto addFractionEnding = [&](const size_t orderExt) {
if (folded) { // add postifx for the folded fraction
auto const ending = getFoldedFractionEnding(localeSettings);
if (*ending) { // if NOT empty
str += delimiter;
str += ending;
}
return;
}
//// Add 'normal' postifx
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB: break; // NO specific ending currently
case ELocale::L_RU_RU: {
auto toAdd = "";
//// Add prefix / root
assert(orderExt); // SHOULD NOT be zero
const size_t subOrder = orderExt % size_t(3U);
switch (subOrder) { // zero suborder - empty prefix
case size_t(1U): // ДЕСЯТ ая(ых) | ДЕСЯТ И тысячная(ых) ДЕСЯТ И миллиардная(ых)
toAdd = orderExt < size_t(3U) ? "десят" : "десяти"; break;
case size_t(2U): // СОТ ая(ых) | СТО тысячная(ых) СТО миллиардная(ых)
toAdd = orderExt < size_t(3U) ? "сот" : "сто"; break;
}
if (*toAdd) {
str += delimiter;
str += toAdd;
}
//// Add root (if NOT yet) + part of the postfix (if needed)
if (orderExt > size_t(2U)) { // from 'тысяч н ая ых'
if (!*toAdd) str += delimiter; // deim. is NOT added yet
const char* temp;
str += getOrderStr(orderExt, size_t(), size_t(), temp, localeSettings);
str += "н"; // 'десят И тысяч Н ая ых', 'сто тысяч Н ая ых'
}
//// Add postfix
assert(prevDigit < size_t(10U) && currDigit < size_t(10U));
if (size_t(1U) == prevDigit) { // одинадцать двенадцать девятнадцать сотЫХ десятитысячнЫХ
toAdd = "ых";
} else { // NOT 1U prev. digit
if (size_t(1U) == currDigit) {
toAdd = "ая"; // одна двадцать одна десятАЯ, тридцать одна стотысячнАЯ
} else toAdd = "ых"; // ноль десятых; двадцать две тридцать пять девяносто девять тясячнЫХ
}
str += toAdd;
}
break;
default: // locale NOT present
assert(false); // locale error
str += "<locale error [" MAKE_STR_(__LINE__) "]>";
}
};
// Also for 'and' in EN GB
const auto minDigitsSubPartSizeToAddOrder = getMinDigitsSubPartSizeToAddOrder(localeSettings);
auto totalAddedCount = size_t();
// ONLY up to 3 digits
auto processDigitOfATriad = [&](const size_t subOrder, const size_t order, size_t& currAddedCount,
const size_t normalDigitsSubPartSize, const bool fractPart) {
auto addFirstToZeroOrderDelim = [&]() {
char delim_;
switch (localeSettings.locale) { // choose delim.
case ELocale::L_EN_US: case ELocale::L_EN_GB: delim_ = '-'; break; // 'thirty-four'
case ELocale::L_RU_RU: default : delim_ = delimiter; break; // 'тридцать четыре'
}
str += delim_;
};
auto addDelim = [&](const char delim) {
if (ELocale::L_EN_GB == localeSettings.locale) {
// In AMERICAN English, many students are taught NOT to use the word "and"
// anywhere in the whole part of a number
if (totalAddedCount && normalDigitsSubPartSize >= minDigitsSubPartSizeToAddOrder) {
str += delim;
str += ENG_GB_VERBAL_DELIMITER;
}
}
str += delim;
};
assert(subOrder < size_t(3U) && prevDigit < size_t(10U) && currDigit < size_t(10U));
const char* infix, *postfix;
switch (subOrder) {
case size_t(): // ones ('three' / 'три') AND numbers like 'ten' / 'twelve'
if (size_t(1U) == prevDigit) { // 'ten', 'twelve' etc
if (!str.empty()) addDelim(delimiter); // if needed
str += getFirstOrderNumberStr(currDigit, prevDigit, infix, postfix, localeSettings);
str += infix, str += postfix;
++currAddedCount, ++totalAddedCount;
} else if (currDigit || size_t(1U) == normalDigitsSubPartSize) { // prev. digit is NOT 1
//// Simple digits like 'one'
if (prevDigit) { // NOT zero
assert(prevDigit > size_t(1U));
addFirstToZeroOrderDelim();
} else if (!str.empty()) addDelim(delimiter); // prev. digit IS zero
str += getZeroOrderNumberStr(currDigit, order, postfix, localeSettings);
str += postfix;
++currAddedCount, ++totalAddedCount;
}
break;
case size_t(1U): // tens ['twenty' / 'двадцать']
if (currDigit > size_t(1U)) { // numbers like ten / twelve would be proceeded later
if (!str.empty()) addDelim(delimiter); // if needed
str += getFirstOrderNumberStr(currDigit, size_t(), infix, postfix, localeSettings);
str += infix, str += postfix;
++currAddedCount, ++totalAddedCount;
} // if 'currDigit' is '1U' - skip (would be proceeded later)
break;
case size_t(2U): // hundred(s?)
if (!currDigit) break; // zero = empty
if (!str.empty()) str += delimiter; // if needed
switch (localeSettings.locale) {
case ELocale::L_EN_US: case ELocale::L_EN_GB: // 'three hundred'
str += getZeroOrderNumberStr(currDigit, order, postfix, localeSettings);
str += postfix;
str += delimiter;
{
const char* postfix_; // NO postfix expected, just a placeholder var.
str += getOrderStr(size_t(2U), size_t(0U), currDigit, postfix_, localeSettings);
assert(postfix_ && !*postfix_);
}
break;
case ELocale::L_RU_RU: // 'триста'
str += getSecondOrderNumberStr(currDigit, infix, postfix, localeSettings);
str += infix, str += postfix;
break;
}
++currAddedCount, ++totalAddedCount;
break;
} // 'switch (subOrder)' END
};
//// Generic processing lambdas
auto intPartPreLastDigit = ptrdiff_t(-1), intPartLastDigit = ptrdiff_t(-1); // NO part by default
auto addFractionDelimiter = [&]() {
const char* postfix;
auto const fractionDelim =
getFractionDelimiter(intPartPreLastDigit, intPartLastDigit, postfix, folded, localeSettings);
if (*fractionDelim) { // if NOT empty
if (!str.empty()) str += delimiter;
str += fractionDelim;
}
if (*postfix) {
if (*fractionDelim) str += delimiter;
str += postfix;
}
};
auto addedCount = size_t(); // during processing curr. part
auto emptySubPartsCount = size_t();
// Part order is an order of the last digit of the part (zero for 654, 3 for 456 of the 456654 etc)
// Part (integral OR fractional) of the number is consists of the subparts of specified size
// (usually 3 OR 1; for ENG.: 3 for int. part., 1 for fract. part)
// 'subPartOrderExt' SHOULD exists ONLY for a LAST subpart
auto processDigitsSubPart = [&](const size_t currDigitsSubPartSize,
const size_t normalDigitsSubPartSize,
const size_t order, size_t subPartOrderExt, const bool fractPart) {
assert(currDigitsSubPartSize && currDigitsSubPartSize <= size_t(3U));
auto currAddedCount = size_t(); // reset
auto emptySubPart = true; // true if ALL prev. digits of the subpart is zero
prevDigit = std::decay<decltype(prevDigit)>::type(); // reset
for (size_t subOrder = currDigitsSubPartSize - size_t(1U);;) {
if (DECIMAL_DELIM_ != *currSymbPtr) { // skip decimal delim.
currDigit = *currSymbPtr - '0'; // assuming ANSI ASCII
PPOCESS_DIGIT_:
assert(*currSymbPtr >= '0' && currDigit < size_t(10U));
emptySubPart &= !currDigit;
processDigitOfATriad(subOrder + subPartOrderExt, order, currAddedCount,
normalDigitsSubPartSize, fractPart);
if (subPartOrderExt) { // treat unpresented digits [special service]
--subPartOrderExt;
prevDigit = currDigit;
currDigit = std::decay<decltype(currDigit)>::type(); // remove ref. from type
goto PPOCESS_DIGIT_; // don't like 'goto'? take a nyan cat here: =^^=
}
if (!subOrder) { // zero order digit
++currSymbPtr; // shift to the symb. after the last in an int. part
break;
}
--subOrder, prevDigit = currDigit;
}
++currSymbPtr;
}
if (emptySubPart) ++emptySubPartsCount; // update stats
// Add order str. AFTER part (if exist)
if (currAddedCount && normalDigitsSubPartSize >= minDigitsSubPartSizeToAddOrder) {
const char* postfix;
auto const orderStr = getOrderStr(order, prevDigit, currDigit, postfix, localeSettings);
assert(orderStr && postfix);
if (*orderStr) { // if NOT empty (CAN be empty for zero order [EN, RU])
assert(str.size()); // NOT zero
str += delimiter, str += orderStr, str += postfix;
++currAddedCount;
}
}
addedCount += currAddedCount;
};
size_t intPartAddedCount, strLenWithoutFractPart;
// Strategy used to process both integral AND fractional parts of the number
// 'digitsPartSize' is a total part. len. in digits (i. e. 1 for 4, 3 for 123, 6 for 984532 etc)
// [CAN be zero in some cases]
// 'partBonusOrder' will be 3 for 124e3, 9 for 1.2e10, 0 for 87654e0 etc
// 'fractPart' flag SHOULD be true if processing fraction part
auto processDigitsPart = [&](size_t digitsPartSize, const size_t digitsSubPartSize,
size_t partBonusOrder, const bool fractPart) {
currDigit = size_t(), prevDigit = size_t(); // reset
if (digitsPartSize) {
assert(digitsSubPartSize); // SHOULD be NOT zero
size_t currDigitsSubPartSize =
(digitsPartSize + partBonusOrder) % digitsSubPartSize; // 2 for 12561, 1 for 9 etc
if (!currDigitsSubPartSize) currDigitsSubPartSize = digitsSubPartSize; // if zero remanider
// Will be 2 for '12.34e4' ('1234e2' = '123 400' - two last unpresented zeroes); 1 for 1e1
auto subPartOrderExt = size_t(); // used ONLY for a last subpart
// OPTIMIZATION HINT: redesign to preallocate for the whole str., NOT for a diffirent parts?
if (ReserveBeforeAdding) // optimization [CAN acquire more / less space then really required]
str.reserve(str.length() + estimatePossibleLength(digitsPartSize, fractPart, localeSettings));
do {
if (currDigitsSubPartSize > digitsPartSize) { // if last AND unnormal [due to the '%']
subPartOrderExt = currDigitsSubPartSize - digitsPartSize;
partBonusOrder -= subPartOrderExt;
currDigitsSubPartSize = digitsPartSize; // correct
}
digitsPartSize -= currDigitsSubPartSize;
processDigitsSubPart(currDigitsSubPartSize, digitsSubPartSize,
digitsPartSize + partBonusOrder, subPartOrderExt, fractPart);
currDigitsSubPartSize = digitsSubPartSize; // set default [restore]
} while (digitsPartSize);
}
auto mentionZeroPart = [&]() {
if (!str.empty()) str += delimiter;
const char* postfix;
str += getZeroOrderNumberStr(size_t(), size_t(), postfix, localeSettings);
str += postfix;
++totalAddedCount;
};
if (!addedCount) { // NO part
if (!localeSettings.shortFormat || folded) { // NOT skip mention zero parts
if (fractPart) {
addFractionDelimiter(); // 'ноль целых'
} else intPartLastDigit = ptrdiff_t(); // now. IS int. part
mentionZeroPart();
++addedCount;
} else if (fractPart) { // short format AND now processing fraction part
assert(!folded); // NO fract. part - SHOULD NOT be folded
assert(strLenWithoutFractPart <= str.size()); // SHOULD NOT incr. len.
if (!intPartAddedCount) { // NO int. part [zero point zero -> zero] <EXCEPTION>
mentionZeroPart(); // do NOT incr. 'addedCount'!!
}
}
}
};
//// Process int. part
processDigitsPart(intPartLen, getIntSubPartSize(), intPartBonusOrder, false);
if (truncated::ExecIfPresent(str)) { // check if truncated
if (errMsg) *errMsg = "too short buffer"; return false;
}
if (intPartLen) { // if int. part exist
assert(currSymbPtr > strBuf);
intPartLastDigit = *(currSymbPtr - ptrdiff_t(1)) - '0';
assert(intPartLastDigit > ptrdiff_t(-1) && intPartLastDigit < ptrdiff_t(10));
if (intPartLen > size_t(1U)) { // there is also prelast digit
auto intPartPreLastDigitPtr = currSymbPtr - ptrdiff_t(2);
if (DECIMAL_DELIM_ == *intPartPreLastDigitPtr) --intPartPreLastDigitPtr; // skip delim.: 2.3e1
assert(intPartPreLastDigitPtr >= strBuf); // check borders
intPartPreLastDigit = *intPartPreLastDigitPtr - '0';
assert(intPartPreLastDigit > ptrdiff_t(-1) && intPartPreLastDigit < ptrdiff_t(10));
}
}
strLenWithoutFractPart = str.size(); // remember (for future use)
intPartAddedCount = addedCount;
addedCount = decltype(addedCount)(); // reset
//// Process fract. part
if (fractPartLen) {
addFractionDelimiter();
addFractionPrefix(); // if needed
currSymbPtr = fractPartRealStart; // might be required if folded [in SOME cases]
}
processDigitsPart(fractPartLen, getFractSubPartSize(localeSettings), size_t(), true);
if (addedCount) { // smth. added (even if zero part)
fractPartAddedCount = addedCount;
//// Add specific ending (if needed, like 'десятимиллионная')
assert(fractPartLen >= decltype(fractPartLen)());
size_t fractPartLastDigitOrderExt = fractPartLeadingZeroesCount + fractPartLen;
if (!fractPartLastDigitOrderExt) fractPartLastDigitOrderExt = size_t(1U); // at least one
addFractionEnding(fractPartLastDigitOrderExt);
}
assert(totalAddedCount); // SHOULD NOT be zero
if (truncated::ExecIfPresent(str)) { // check if truncated
if (errMsg) *errMsg = "too short buffer"; return false;
} return true;
}
};
#endif // ConvertionUtilsH
const ConvertionUtils::LocaleSettings ConvertionUtils::LocaleSettings::DEFAULT_LOCALE_SETTINGS;
#include <iostream>
#include <string>
int main() {
std::string str;
ConvertionUtils::LocaleSettings localeSettings;
auto errMsg = "";
std::cout.precision(LDBL_DIG);
auto num = 6437268689.4272L;
localeSettings.locale = ConvertionUtils::ELocale::L_EN_US;
ConvertionUtils::numToNumFormatStr(num, str, localeSettings, &errMsg);
std::cout << num << " =>\n " << str << std::endl << std::endl;
num = 1200.25672567L;
str.clear();
localeSettings.locale = ConvertionUtils::ELocale::L_EN_GB;
localeSettings.foldFraction = true;
localeSettings.verySpecific = true;
ConvertionUtils::numToNumFormatStr(num, str, localeSettings, &errMsg);
std::cout << num << " =>\n " << str << std::endl << std::endl;
num = 1.0000300501L;
str.clear();
localeSettings.locale = ConvertionUtils::ELocale::L_RU_RU;
ConvertionUtils::numToNumFormatStr(num, str, localeSettings, &errMsg);
std::cout << num << " =>\n " << str << std::endl << std::endl;
num = 9432654671318.0e45L;
str.clear();
localeSettings.shortFormat = true;
localeSettings.locale = ConvertionUtils::ELocale::L_RU_RU;
ConvertionUtils::numToNumFormatStr(num, str, localeSettings, &errMsg);
std::cout << num << " =>\n " << str;
return 0;
}