diff --git a/source/Library/Numeric.cpp b/source/Library/Numeric.cpp index ad28bfb8..caae7c99 100644 --- a/source/Library/Numeric.cpp +++ b/source/Library/Numeric.cpp @@ -6,6 +6,7 @@ namespace SqMod { // ------------------------------------------------------------------------------------------------ extern void Register_LongInt(HSQUIRRELVM vm); +extern void Register_Decimal(HSQUIRRELVM vm); extern void Register_Math(HSQUIRRELVM vm); extern void Register_Random(HSQUIRRELVM vm); @@ -13,6 +14,7 @@ extern void Register_Random(HSQUIRRELVM vm); void Register_Numeric(HSQUIRRELVM vm) { Register_LongInt(vm); + Register_Decimal(vm); Register_Math(vm); Register_Random(vm); } diff --git a/source/Library/Numeric/Decimal.cpp b/source/Library/Numeric/Decimal.cpp new file mode 100644 index 00000000..32abd8d2 --- /dev/null +++ b/source/Library/Numeric/Decimal.cpp @@ -0,0 +1,286 @@ +// ------------------------------------------------------------------------------------------------ +#include "Library/Numeric/Decimal.hpp" +#include "Library/Numeric/LongInt.hpp" + +// ------------------------------------------------------------------------------------------------ +#include +#include +#include +#include + +// ------------------------------------------------------------------------------------------------ +namespace SqMod { + +// ------------------------------------------------------------------------------------------------ +const Int64 Decimal::s_Factors[] = { + static_cast< Int64 >(std::pow(10, 0)), + static_cast< Int64 >(std::pow(10, 1)), + static_cast< Int64 >(std::pow(10, 2)), + static_cast< Int64 >(std::pow(10, 3)), + static_cast< Int64 >(std::pow(10, 4)), + static_cast< Int64 >(std::pow(10, 5)), + static_cast< Int64 >(std::pow(10, 6)), + static_cast< Int64 >(std::pow(10, 7)), + static_cast< Int64 >(std::pow(10, 8)), + static_cast< Int64 >(std::pow(10, 9)), + static_cast< Int64 >(std::pow(10, 10)), + static_cast< Int64 >(std::pow(10, 11)), + static_cast< Int64 >(std::pow(10, 12)), + static_cast< Int64 >(std::pow(10, 13)), + static_cast< Int64 >(std::pow(10, 14)), + static_cast< Int64 >(std::pow(10, 15)), + static_cast< Int64 >(std::pow(10, 16)), + static_cast< Int64 >(std::pow(10, 17)), + static_cast< Int64 >(std::pow(10, 18)), + static_cast< Int64 >(std::pow(10, 19)), + static_cast< Int64 >(std::pow(10, 20)), + static_cast< Int64 >(std::pow(10, 21)), + static_cast< Int64 >(std::pow(10, 22)), + static_cast< Int64 >(std::pow(10, 23)), + static_cast< Int64 >(std::pow(10, 24)), + static_cast< Int64 >(std::pow(10, 25)), + static_cast< Int64 >(std::pow(10, 26)), + static_cast< Int64 >(std::pow(10, 27)), + static_cast< Int64 >(std::pow(10, 28)), + static_cast< Int64 >(std::pow(10, 29)), + static_cast< Int64 >(std::pow(10, 30)), + static_cast< Int64 >(std::pow(10, 31)), + static_cast< Int64 >(std::pow(10, 32)), + static_cast< Int64 >(std::pow(10, 33)), + static_cast< Int64 >(std::pow(10, 34)), + static_cast< Int64 >(std::pow(10, 35)), + static_cast< Int64 >(std::pow(10, 36)), + static_cast< Int64 >(std::pow(10, 37)), + static_cast< Int64 >(std::pow(10, 38)), + static_cast< Int64 >(std::pow(10, 39)), + static_cast< Int64 >(std::pow(10, 40)), + static_cast< Int64 >(std::pow(10, 41)), + static_cast< Int64 >(std::pow(10, 42)), + static_cast< Int64 >(std::pow(10, 43)), + static_cast< Int64 >(std::pow(10, 44)), + static_cast< Int64 >(std::pow(10, 45)), + static_cast< Int64 >(std::pow(10, 46)), + static_cast< Int64 >(std::pow(10, 47)), + static_cast< Int64 >(std::pow(10, 48)), + static_cast< Int64 >(std::pow(10, 49)), + static_cast< Int64 >(std::pow(10, 40)), + static_cast< Int64 >(std::pow(10, 51)), + static_cast< Int64 >(std::pow(10, 52)), + static_cast< Int64 >(std::pow(10, 53)), + static_cast< Int64 >(std::pow(10, 54)), + static_cast< Int64 >(std::pow(10, 55)), + static_cast< Int64 >(std::pow(10, 56)), + static_cast< Int64 >(std::pow(10, 57)), + static_cast< Int64 >(std::pow(10, 58)), + static_cast< Int64 >(std::pow(10, 59)), + static_cast< Int64 >(std::pow(10, 60)), + static_cast< Int64 >(std::pow(10, 61)), + static_cast< Int64 >(std::pow(10, 62)), + static_cast< Int64 >(std::pow(10, 63)), +}; + +// ------------------------------------------------------------------------------------------------ +SQInteger Decimal::Typename(HSQUIRRELVM vm) +{ + static const SQChar name[] = _SC("SqDecimal"); + sq_pushstring(vm, name, sizeof(name)); + return 1; +} + +// ------------------------------------------------------------------------------------------------ +void Decimal::ValidatePrecision(Uint8 precision) +{ + if (precision >= (sizeof(s_Factors) / sizeof(Int64))) + { + STHROWF("Out of range precision: %u >= %u", precision, (sizeof(s_Factors) / sizeof(Int64))); + } +} + +// ------------------------------------------------------------------------------------------------ +Int64 Decimal::Convert(const Decimal & dec) const +{ + // Do they already have the same precision + if (m_Precision == dec.m_Precision) + { + return dec.m_Value; + } + // Return the value converted to the new precision + return (dec.m_Value / s_Factors[dec.m_Precision]) * s_Factors[m_Precision]; +} + +// ------------------------------------------------------------------------------------------------ +Int32 Decimal::Compare(const Decimal & o) const +{ + // Convert ti the same precision + const Int64 b = Convert(o); + // Perform the comparison + if (m_Value == b) + { + return 0; + } + else if (m_Value > b) + { + return 1; + } + else + { + return -1; + } +} + +// ------------------------------------------------------------------------------------------------ +Decimal::Decimal(CSStr value) + : m_Value(0), m_Precision(0) +{ + // Is there anything to parse at least? + if (!value) + { + STHROWF("Invalid decimal string: null"); + } + // Skip whitespace characters + while (std::isspace(*value)) + { + ++value; + } + // Do we still have anything left to parse? + if (*value == '\0') + { + return; // Leave default values + } + // Is there anything before the separator? + if (*value == '.') + { + // Extract the amount of precision required + m_Precision = std::strlen(++value); + // Extract the value as is + m_Value = std::strtoul(value, nullptr, 10); + // Leave the remaining value to default + return; + } + // Find the fraction separator + CSStr point = std::strchr(value, '.'); + // Was there a fraction separator found? + if (!point) + { + // Extract the value as is + m_Value = std::strtoul(value, nullptr, 10); + // Leave the remaining value to default + return; + } + // Extract the amount of precision required + m_Precision = ConvTo< Uint8 >::From(std::strlen(++point)); + // Validate the obtained precision + ValidatePrecision(m_Precision); + // Copy the value upto the the separator + std::strncpy(GetTempBuff(), value, point - value - 1)[point - value - 1] = '\0'; + // Extract the value itself + m_Value = (std::strtoul(GetTempBuff(), nullptr, 10) * s_Factors[m_Precision]); + m_Value += std::strtoul(point, nullptr, 10); +} + +// ------------------------------------------------------------------------------------------------ +Decimal::Decimal(SQInteger value, Uint8 precision) + : m_Value(0), m_Precision(precision) +{ + // Validate the specified precision + ValidatePrecision(m_Precision); + // Assign the specified value + m_Value = (s_Factors[m_Precision] * value); +} + +#ifndef _SQ64 + +// ------------------------------------------------------------------------------------------------ +Decimal::Decimal(Int64 value, Uint8 precision) + : m_Value(0), m_Precision(precision) +{ + // Validate the specified precision + ValidatePrecision(m_Precision); + // Assign the specified value + m_Value = (s_Factors[m_Precision] * value); +} + +#endif // _SQ64 + +// ------------------------------------------------------------------------------------------------ +CSStr Decimal::ToString() const +{ + // Generate the string into the temporary buffer + MakeString(GetTempBuff(), GetTempBuffSize()); + // Return the resulted string + return GetTempBuff(); +} + +// ------------------------------------------------------------------------------------------------ +SLongInt Decimal::GetConverted(const Decimal & dec) const +{ + return SLongInt(Convert(dec)); +} + +// ------------------------------------------------------------------------------------------------ +SLongInt Decimal::GetFactor() const +{ + return SLongInt(s_Factors[m_Precision]); +} + +// ------------------------------------------------------------------------------------------------ +SLongInt Decimal::GetUnbiased() const +{ + return SLongInt(m_Value); +} + +// ------------------------------------------------------------------------------------------------ +void Decimal::MakeString(CStr buffer, Uint32 size) const +{ + const Int64 after = m_Value % s_Factors[m_Precision]; + const Int64 before = (m_Value - after) / s_Factors[m_Precision]; + // Generate the format string + if (std::snprintf(buffer, size, "%lld.%0*lld", before, m_Precision, after) < 0) + { + buffer[0] = '\0'; // At least make sure the string is null terminated + } +} + +// ------------------------------------------------------------------------------------------------ +static Decimal Sq_DecimalFromLong(const SLongInt & value, Uint8 precision) +{ + return Decimal(value.GetNum(), precision); +} + +// ================================================================================================ +void Register_Decimal(HSQUIRRELVM vm) +{ + RootTable(vm).Bind(_SC("SqDecimal"), Class< Decimal >(vm, _SC("SqDecimal")) + // Constructors + .Ctor() + .Ctor< CSStr >() + .Ctor< SQInteger, Uint8 >() + // Core Meta-methods + .Func(_SC("_tostring"), &Decimal::ToString) + .SquirrelFunc(_SC("_typename"), &Decimal::Typename) + .Func(_SC("_cmp"), &Decimal::Cmp) + // Core Functions + .Func(_SC("tofloat"), &Decimal::GetSqFloat) + // Meta-methods + .Func< Decimal (Decimal::*)(const Decimal &) const >(_SC("_add"), &Decimal::operator +) + .Func< Decimal (Decimal::*)(const Decimal &) const >(_SC("_sub"), &Decimal::operator -) + .Func< Decimal (Decimal::*)(const Decimal &) const >(_SC("_mul"), &Decimal::operator *) + .Func< Decimal (Decimal::*)(const Decimal &) const >(_SC("_div"), &Decimal::operator /) + // Properties + .Prop(_SC("Factor"), &Decimal::GetFactor) + .Prop(_SC("Unbiased"), &Decimal::GetUnbiased) + .Prop(_SC("Precision"), &Decimal::GetPrecision) + .Prop(_SC("Abs"), &Decimal::GetAbs) + .Prop(_SC("Float"), &Decimal::GetSqFloat) + .Prop(_SC("Float32"), &Decimal::GetFloat32) + .Prop(_SC("Float64"), &Decimal::GetFloat64) + .Prop(_SC("Str"), &Decimal::ToString) + // Functions + .Func(_SC("Convert"), &Decimal::GetConverted) + // Static Functions + .StaticFunc(_SC("Long"), &Sq_DecimalFromLong) + .StaticFunc(_SC("FromLong"), &Sq_DecimalFromLong) + ); +} + +} // Namespace:: SqMod diff --git a/source/Library/Numeric/Decimal.hpp b/source/Library/Numeric/Decimal.hpp new file mode 100644 index 00000000..53128d7e --- /dev/null +++ b/source/Library/Numeric/Decimal.hpp @@ -0,0 +1,317 @@ +#ifndef _LIBRARY_NUMERIC_DECIMAL_HPP_ +#define _LIBRARY_NUMERIC_DECIMAL_HPP_ + +// ------------------------------------------------------------------------------------------------ +#include "Base/Utility.hpp" + +// ------------------------------------------------------------------------------------------------ +namespace SqMod { + +/* ------------------------------------------------------------------------------------------------ + * Simple decimal data type support. Mostly for working with databases that support this type. +*/ +class Decimal +{ +private: + + // -------------------------------------------------------------------------------------------- + Int64 m_Value; // Decimal value + Uint8 m_Precision; // Decimal precision + + // -------------------------------------------------------------------------------------------- + static const Int64 s_Factors[]; + +protected: + + /* -------------------------------------------------------------------------------------------- + * Validate the given precision. + */ + static void ValidatePrecision(Uint8 precision); + + /* -------------------------------------------------------------------------------------------- + * Convert a decimal to the same precision as this one. + */ + Int64 Convert(const Decimal & dec) const; + + /* -------------------------------------------------------------------------------------------- + * Validate against given decimal. + */ + Int32 Compare(const Decimal & o) const; + + /* -------------------------------------------------------------------------------------------- + * Result = (a * b) / d + */ + static Int64 MultiplyDivide(Int64 a, Int64 b, Int64 d) + { + if ((std::abs(a) <= std::numeric_limits< Int32 >::max()) || + (std::abs(b) <= std::numeric_limits< Int32 >::max())) + { + return std::llround(static_cast< Float64 >(a * b) / static_cast< Float64 >(d)); + } + + return std::llround(static_cast< Float64 >(a) * static_cast< Float64 >(b) / static_cast< Float64 >(d)); + } + +public: + + /* -------------------------------------------------------------------------------------------- + * Default constructor. + */ + Decimal() + : m_Value(0), m_Precision(4) + { + /* ... */ + } + + /* -------------------------------------------------------------------------------------------- + * String constructor. + */ + Decimal(CSStr value); + + /* -------------------------------------------------------------------------------------------- + * Numeric constructor. + */ + Decimal(SQInteger value, Uint8 precision); + +#ifndef _SQ64 + + /* -------------------------------------------------------------------------------------------- + * Numeric constructor. + */ + Decimal(Int64 value, Uint8 precision); + +#endif // _SQ64 + + /* -------------------------------------------------------------------------------------------- + * Copy constructor. + */ + Decimal(const Decimal & o) = default; + + /* -------------------------------------------------------------------------------------------- + * Move constructor. + */ + Decimal(Decimal && o) = default; + + /* -------------------------------------------------------------------------------------------- + * Destructor. + */ + ~Decimal() = default; + + /* -------------------------------------------------------------------------------------------- + * Copy assignment operator. + */ + Decimal & operator = (const Decimal & o) + { + m_Value = Convert(o); + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Move assignment operator. + */ + Decimal & operator = (Decimal && o) = default; + + /* -------------------------------------------------------------------------------------------- + * Used by the script engine to compare two instances of this type. + */ + Int32 Cmp(const Decimal & o) const + { + return Compare(o); + } + + /* -------------------------------------------------------------------------------------------- + * Used by the script engine to convert an instance of this type to a string. + */ + CSStr ToString() const; + + /* -------------------------------------------------------------------------------------------- + * Used by the script engine to retrieve the name from instances of this type. + */ + static SQInteger Typename(HSQUIRRELVM vm); + + /* -------------------------------------------------------------------------------------------- + * Perform an equality comparison between to instances of this type. + */ + bool operator == (const Decimal & o) const + { + return m_Value == Convert(o); + } + + /* -------------------------------------------------------------------------------------------- + * Perform an inequality comparison between to instances of this type. + */ + bool operator != (const Decimal & o) const + { + return m_Value != Convert(o); + } + + /* -------------------------------------------------------------------------------------------- + * Perform a less than comparison between to instances of this type. + */ + bool operator < (const Decimal & o) const + { + return m_Value < Convert(o); + } + + /* -------------------------------------------------------------------------------------------- + * Perform a greater than comparison between to instances of this type. + */ + bool operator > (const Decimal & o) const + { + return m_Value > Convert(o); + } + + /* -------------------------------------------------------------------------------------------- + * Perform a less than or equal comparison between to instances of this type. + */ + bool operator <= (const Decimal & o) const + { + return m_Value <= Convert(o); + } + + /* -------------------------------------------------------------------------------------------- + * Perform a greater than or equal comparison between to instances of this type. + */ + bool operator >= (const Decimal & o) const + { + return m_Value >= Convert(o); + } + + /* -------------------------------------------------------------------------------------------- + * Perform an addition between to instances of this type. + */ + Decimal operator + (const Decimal & o) const + { + return Decimal(m_Value + Convert(o), m_Precision); + } + + /* -------------------------------------------------------------------------------------------- + * Perform an subtraction between to instances of this type. + */ + Decimal operator - (const Decimal & o) const + { + return Decimal(m_Value - Convert(o), m_Precision); + } + + /* -------------------------------------------------------------------------------------------- + * Perform an multiplication between to instances of this type. + */ + Decimal operator * (const Decimal & o) const + { + return Decimal(MultiplyDivide(m_Value, Convert(o), s_Factors[m_Precision]), m_Precision); + } + + /* -------------------------------------------------------------------------------------------- + * Perform an division between to instances of this type. + */ + Decimal operator / (const Decimal & o) const + { + return Decimal(MultiplyDivide(m_Value, s_Factors[m_Precision], Convert(o)), m_Precision); + } + + /* -------------------------------------------------------------------------------------------- + * Perform an assignment addition between to instances of this type. + */ + Decimal & operator += (const Decimal & o) + { + m_Value += Convert(o); + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Perform an subtraction addition between to instances of this type. + */ + Decimal & operator -= (const Decimal & o) + { + m_Value -= Convert(o); + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Perform an multiplication addition between to instances of this type. + */ + Decimal & operator *= (const Decimal & o) + { + m_Value -= MultiplyDivide(m_Value, Convert(o), s_Factors[m_Precision]); + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Perform an division addition between to instances of this type. + */ + Decimal & operator /= (const Decimal & o) + { + m_Value -= MultiplyDivide(m_Value, s_Factors[m_Precision], Convert(o)); + return *this; + } + + /* -------------------------------------------------------------------------------------------- + * Convert a decimal to the same precision as this one. + */ + SLongInt GetConverted(const Decimal & dec) const; + + /* -------------------------------------------------------------------------------------------- + * Retrieve the factor. + */ + SLongInt GetFactor() const; + + /* -------------------------------------------------------------------------------------------- + * Returns integer value = real_value * (10 ^ precision) + */ + SLongInt GetUnbiased() const; + + /* -------------------------------------------------------------------------------------------- + * Retrieve the precision. + */ + Uint8 GetPrecision() const + { + return m_Precision; + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the absolute value. + */ + Decimal GetAbs() const + { + if (m_Value >= 0) + { + return *this; + } + // Calculate the absolute value + return Decimal(0, s_Factors[m_Precision]) - *this; + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the decimal value as a native script floating point value. + */ + SQFloat GetSqFloat() const + { + return static_cast< SQFloat >(m_Value) / static_cast< SQFloat >(m_Precision * s_Factors[m_Precision]); + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the decimal value as a single floating point value. + */ + Float32 GetFloat32() const + { + return static_cast< Float32 >(m_Value) / static_cast< Float32 >(m_Precision * s_Factors[m_Precision]); + } + + /* -------------------------------------------------------------------------------------------- + * Retrieve the decimal value as a double floating point value. + */ + Float64 GetFloat64() const + { + return static_cast< Float64 >(m_Value) / static_cast< Float64 >(m_Precision * s_Factors[m_Precision]); + } + + /* -------------------------------------------------------------------------------------------- + * Convert the decimal to a string and store it into the given buffer. + */ + void MakeString(CStr buffer, Uint32 size) const; + +}; + +} // Namespace:: SqMod + +#endif // _LIBRARY_NUMERIC_DECIMAL_HPP_