Code Monkey home page Code Monkey logo

decimal_for_cpp's Introduction

About

Decimal data type support, for COBOL-like fixed-point operations on currency/money values.

decimal_for_cpp

Author: Piotr Likus

Created: 03/01/2011

Modified: 24/02/2024

Licence: BSD

Version: 1.20

This data type is designed to perform calculation with on-fly roundings & to support correct compare function (floating-point compare is unreliable).

Values are stored internally using 64-bit integer, so maximum number of digits is 18.

Precision is user-defined, so you can use this data type for currency rates.

To store decimal in file you can use "unbiased" functions or use stream i/o.

Examples

Example usage:

#include "decimal.h"

using namespace dec;
using namespace std;

// the following declares currency variable with 2 decimal points
// initialized with integer value (can be also floating-point)
decimal<2> value(143125);

// displays: Value #1 is: 143125.00
cout << "Value #1 is: " << value << endl;

// declare precise value with digits after decimal point
decimal<2> b("0.11");

// perform calculations as with any other numeric type
value += b;

// displays: Value #2 is: 143125.11
cout << "Value #2 is: " << value << endl;

// automatic rounding performed here
value /= 1000;

// displays: Value #3 is: 143.13
cout << "Value #3 is: " << value << endl;

// integer multiplication and division can be used directly in expression
// when integer is on right side
// displays: Value: 143.13 * 2 is: 286.26
cout << "Value: " << value << " * 2 is: " << (value * 2) << endl;

// to use integer on left side you need to cast it
// displays: Value: 2 * 143.13 is: 286.26
cout << "Value: 2 * " << value << " is: " << (decimal_cast<2>(2) * value) << endl;

// to use non-integer constants in expressions you need to use decimal_cast
value = value * decimal_cast<2>("3.33") / decimal_cast<2>(333.0);

// displays: Value #4 is: 1.43
cout << "Value #4 is: " << value << endl;

// to mix decimals with different precision use decimal_cast
// it will round result automatically
decimal<6> exchangeRate(12.1234);
value = decimal_cast<2>(decimal_cast<6>(value) * exchangeRate);

// displays: Value #5 is: 17.34
cout << "Value #5 is: " << value << endl;

// supports optional strong typing, e.g.
// depending on configuration mixing precision can be forbidden
// or handled automatically
decimal<2> d2("12.03");
decimal<4> d4("123.0103");

// compiles always
d2 += d2;
d2 += decimal_cast<2>(d4);
d4 += decimal_cast<4>(d2);

#if DEC_TYPE_LEVEL >= 2
// potential precision loss
// this will fail to compile if you define DEC_TYPE_LEVEL = 0 or 1
d2 += d4;
#endif

#if DEC_TYPE_LEVEL >= 1
// (possibly unintentional) mixed precision without type casting
// this will fail to compile if you define DEC_TYPE_LEVEL = 0
d4 += d2;
#endif

// for default setup displays: mixed d2 = 417.15
cout << "mixed d2 = " << d2 << endl;
// for default setup displays: mixed d4 = 687.2303
cout << "mixed d4 = " << d4 << endl;

// supports decimal and thousand separator localization
dec::decimal_format format(',', '.');

std::string srcText   = "315499999999999.98";
std::string formatted = "315.499.999.999.999,98";
dec::decimal<2> srcDecimal(srcText);

BOOST_CHECK_EQUAL(dec::toString(srcDecimal, format), formatted);

Supported rounding modes:

  • def_round_policy: default rounding (arithmetic)
  • null_round_policy: round towards zero = truncate
  • half_down_round_policy: round half towards negative infinity
  • half_up_round_policy: round half towards positive infinity
  • half_even_round_policy: bankers' rounding, convergent rounding, statistician's rounding, Dutch rounding, Gaussian rounding
  • ceiling_round_policy: round towards positive infinity
  • floor_round_policy: round towards negative infinity
  • round_down_round_policy: round towards zero = truncate
  • round_up_round_policy: round away from zero

In order to use one of these rounding modes you need to declare your decimal variable like this:

dec::decimal<2, half_even_round_policy> a;

and it will perform required rounding automatically - for example during assignment or arithmetic operations.

Testing

In order to test the library:

cd ~/tmp
git clone https://github.com/vpiotr/decimal_for_cpp.git
cd decimal_for_cpp
mkdir _build
cd _build

# to create makefile with test support
cmake ..    
# or
cmake -DBUILD_TESTING=ON ..

# to create makefile without test support (and to avoid Boost unit testing)
cmake -DBUILD_TESTING=OFF ..

# to build or install library (only required for testing)
make all

# to execute all test runners 
make test

# to execute specific runner
./test_runner

# to list all test cases during runner execution
./test_runner --log_level=test_suite

# to execute tests via ctest
ctest -v

Other information

For more examples please see \test directory.

Directory structure:

\doc     - documentation (licence etc.)
\include - headers
\test    - unit tests, Boost-based

Code documentation can be generated using Doxygen: http://www.doxygen.org/

Tested compilers:

  • VS2019 Community (MSVC++ 14.2)
  • gcc 11.4.0

Uses C++11 by default, define DEC_NO_CPP11 symbol if your compiler does not support this standard. To use custom namespace, define DEC_NAMESPACE symbol which should contain your target namespace for decimal type. For full list of configuration options see "Config section" in decimal.h file.

For list of project contributors, currently open issues or latest version see project site: https://github.com/vpiotr/decimal_for_cpp

decimal_for_cpp's People

Contributors

crazyguitar avatar frafjord avatar gevorgvoskanyan avatar jensyt avatar kuriboshi avatar pretorean avatar reunanen avatar rpadrela avatar salvoilmiosi avatar vpiotr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

decimal_for_cpp's Issues

INT32_MAX alternative

Found another alternative to using the int32_max constant,

std::numeric_limits<int32_t>::max() from the limits.h file could also be used and ive found that it doesnt cause as much of a problem as trying to sort it through other means.

Not really a bug, just a feature suggestion lol

Request new feature (pow, sqrt)

It would be of great use if your decimal class supported overloading cmath functions std::pow() and std::sqrt().
I could not implement it myself, so don't have a pull request.

integral constant overflow

Hello. When i tried decimal_for_cpp with decimal10 i got this error:

decimal.h(72) :warning C4307: '*' : integral constant overflow
decimal.h(311) : see reference to class template instantiation 'decc::DecimalFactor' being compiled with [Prec=10]
decimal.h(308) : while compiling class template member function 'void decc::decimal::init(double)' with [Prec=10]

So i changed line 72 from:

static const int value = 10 * DecimalFactor<Prec - 1>::value;

to

static const int64 value = 10 * DecimalFactor<Prec - 1>::value;

And it worked: "57380.543000" 5738054300000 573805430000000
Before that, precision 10 was showing a very different number...

PS. I also had to change more lines because of truncation warnings:

131: explicit decimal(int64 value, int64 precFactor) { initWithPrec(value, precFactor); }
135: inline int64 getPrecFactor() const { return DecimalFactor::value; }
323: void initWithPrec(int64 value, int64 precFactor) {
324: int64 ownFactor = DecimalFactor::value;

Do you think thats ok, to put all those int64 there?

is there a way to perform assigment from std::string?

Hello,

[
these are my first steps with this lib ,
btw, its awesome , thanks for sharing it.
]

is there a way to perform assignment from std::string, something like
DEC_NAMESPACE::decimal<8> TempDec8Value
for (...)
{
TempDec8Value = std::string
}

thanks,
Omer.

namespace

Hello
Thx very mutch for your decimal_for_cpp. Its working great.
But there is one littel issue.
If you use decimal_for_cpp with Qt you have to rename the namespace, because the namespace "dec"
is allready taken by Qt.
You maybe consider to change that namespace name.

Regards Sascha

templated operators do not work

for example template
decimal & operator+=(const decimal &rhs) :

       dec::decimal<8> p8(1);
       dec::decimal<4> p4(0);

        p8 += p4;
        p4 += p8;

both operations fail on either one of the static asserts. They cannot removed, because else one branch creates an infinite template recursion. This is on linux with gcc 7.3.

Suggest using constexpr

I would suggest making constructors and init methods constexpr.
Also it would be convenient to have user defined literal (for example 0.0_dec)

Error on == operator

  decimal<2> x;

  x = 5.6;

  if (x == 5.6) {
      puts("ok");
  }

I get with vs2012, this error :

error C2678: binary '==' : no operator found which takes a left-hand operand of type 'dec::decimal' (or there is no acceptable conversion)

Still C++11 stuff when DEC_NO_CPP11 is defined

Example in README gives me those warnings when DEC_NO_CPP11is defined before inclusion of decimal.h (commit of version 1.18), with AppleClang 12 on Macos without forcing C++11 standard or higher:

/Users/spaceim/.conan/data/decimal_for_cpp/1.18/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/decimal.h:129:9: warning: 'static_assert' macro redefined [-Wmacro-redefined]
#define static_assert(a,b)
        ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__config:880:10: note: previous definition is here
# define static_assert(...) _Static_assert(__VA_ARGS__)
         ^
In file included from ../../test_package.cpp:2:
/Users/spaceim/.conan/data/decimal_for_cpp/1.18/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/decimal.h:1602:36: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
        char decimal_point() const override {
                                   ^
/Users/spaceim/.conan/data/decimal_for_cpp/1.18/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/decimal.h:1606:36: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
        char thousands_sep() const override {
                                   ^
/Users/spaceim/.conan/data/decimal_for_cpp/1.18/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/decimal.h:1610:41: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
        bool thousands_grouping() const override {
                                        ^
/Users/spaceim/.conan/data/decimal_for_cpp/1.18/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/decimal.h:1614:38: warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
        std::string grouping() const override {
                                     ^

how to initialize in struct declaration

Hello Piotr

First I must thank you for creation, its really a must in the cpp space.

I'm using 1.18,
and am unable to compile the following
struct MyStruct
{
decimal<2> myVariable(0);
}

also can't
struct MyStruct
{
decimal<2> myVariable = 0;
}
error : could not convert ‘0’ from ‘int’ to ‘dec::decimal<2>’
^

I can set a value in the constructor mil. this is a decent workaround , but less convenient for me at this time.

please advise,
Omer.

Bug in toString

The following code snippet returns an invalid string.

cout << "Test with <4> is: " << decimal<4>(-0.5) << endl;

no serialization/deserialization feature

Nice lib. Tried it, it works OK.

It just miss a serialization/deserialization (to string) feature so I can save the values in my database without any loss.

Existing "to string" convert to double first, with potential precision loss. And there is no "from string", need to import from double, too.

Thanks.

How does it stand against Muller's recursion ?

To test the numerical precision of your implementation, there is a simple test to perform: compute Muller's recursion.
This simple formula shows that usual floating point diverges rapidly from expected result.
Muller's formula is:

   f(y, z) = 108 - (815 - 1500/z) / y
   x_0 = 4
   x_1 = 4.25
   x_i = f(x_[i-1], x_[i-2])

This formula has 2 poles: 5 and 100. Typically, if x happens to be 5, it'll stay 5. The closer it is to 5, the more likely it'll reach 5. Similarly, if x happens to be 100, it'll stay 100.

The expected result with the starting values above should be 5, reached after 30 iterations.
Used with standard floating point, it's deviating and converging towards 100 rapidly.

See my godbolt for example code

Problem with overflow detection with CLANG compiler..

I had a problem with CLANG compiler for ARM. In release build the code below was optimized in a way that the multiplication and division did not occur as separate operations and no overflow was detected (there should have been an overflow). When I turned off optimization it worked as intended. In the VS 2017 compiler this worked as intended in both release and debug.

Original code that was optimized to "true" as in no overflow detected although there was an overflow:

        int64 resDecPart = value1dec * value2dec;
	if (resDecPart / value1dec == value2dec) { // no overflow

I changed the code to this and that works in CLANG and VS compiler, both release and debug

if (value1dec != 0 && (std::numeric_limits<int64>::max() / value1dec) >= value2dec) { // no overflow

incorrect constructing from fp

incorrect constructing from fp

dec::decimal2(9999999999999998.0); // 9999999999999997.44
dec::decimal4(9999999999999998.0); // -92233720368547758.08

incorrect round
dec::decimal2(3.1549999999999998); // must be 3.15, but 3.16

test condition and test condition is incorrect

unable to change precision of existing variable

hi.

I've been using this lib (1.18) for a few months now. ( thanks for building it btw).

now I've encountered a situation were I've realized I need to change many existing decimal2 variables to decimal4 .
when I change one of them, i get the complication error
..../decimal.h:793: error: no matching function for call to ‘dec::decimal<4>::decimal(const dec::decimal<2>&)’
793 | return (*this == static_cast(rhs));
| ^~~~~~~~~~~~~~~~~~~~~~~~~

using gcc/g++ 9.4.0.

I was not able to find in my code cases of of the changed variable construction using another dec2 variable, that would match the error msg [i.e my_dec4_variable = decimal4(old_dec2_variable)].
the compiler is unable to pinpoint the location of the error ( I guess its because this is a template class).
but there are many assignments [ i.e. changed_dec4_variable = .... unchanged_dec2_variable ... ; ]

I was wondering how I can proceed.
i'd rather avoid manually changing a lot of code to add explicit decimal_cast<4>.
id rather have some sort of built in support for the required assignment. ( an overload of the constructor or assignment operator ???)

I noticed config parameter DEC_TYPE_LEVEL, but did not understand if/how it can be applied here.

can anyone suggest a simple way to proceed?
:-)

Make it fully trivial

I would like the option of making the class fully trivial. This would be a breaking change, as an uninitialized object would have a random value (just like int).

The advantage of this is that it can easily be used by templates that require trivial objects.

I suggest using a new #define, but this will add to the complexity of the class.

#ifdef DEC_TRIVIAL
    decimal() = default;
#else
    decimal() noexcept : m_value(0) {}
#endif

Edge cases failing

Hey congrats for this work! I am using decimal_for_cpp for checking the results of some other algorithm (fpga) when I came across to a few test cases that are failing when apparently they should not. I want to check if this is expected behavior. For example:

BOOST_CHECK_EQUAL( dec::decimal<0>::buildWithExponent(1,-23), dec::decimal<0>("0"));

This test should not fail but it is right now. This can be seen as an underflow, right?

pack

    decimal &pack(int64 beforeValue, int64 afterValue) {
      if (Prec > 0) {
        m_value = beforeValue * DecimalFactor<Prec>::value;
        m_value += (afterValue % DecimalFactor<Prec>::value);
       } else
        m_value = beforeValue * DecimalFactor<Prec>::value;
      return *this;
    }

Sorry, but are you sure that this works? I mean taking a modulo for afterValue?

Suppose you have pack(123, 456) with precision of 2. You will got 123.04 as a result.

And second question is about negative numbers, what if beforeValue < 0? Then when calling pack(-12, 34) with precision of 2, you will got -1200 + 34 == 1166.

specify decimal separator

in fromString and toString it would be nice to have the option of specifying decimal separator and thousand grouping directly as parameters.

Sometimes I want to parse with different settings than what my computer has.

I don't like to change my locale settings just to parse a few numbers with a different separator.

support for power and root ?

hello,

I need to extract the root of a DEC_NAMESPACE,
the standard sqrt operates on double.
I was able to result = std::sqrt(Input.getAsDouble());
but i guess i'm losing precision here somewhere...

please advise,
thanks,
Omer.

min-max macro compatibility

please try to evaluate the following small change

define DEC_MAX_INT32 ((std::numeric_limits::max)())

to make the header file compatible with includes which defines min and max macros

how to get an int/double from dec type ?

Hi

Can you please share the method to extract/convert to basic types?

for example :
int myInt;
dec<2> myDec("1");
myInt = myDec ;
currently this returns a "cannot convert error".
i've also tried dec<2,null_round_policy>... to no avail.

furthermore,
what I actually need right now is to perform a conversion on a "temporary"(implicitly defined) variable , for example :
int result = (decA - DecB) / DecC;
(in this case I precheck that the result of this calculation is in fact an integer)

Please advise,
Omer.

Overflow in multDiv code

There can be an overflow condition that isn't caught right now.
Currently line 709 has:
if ((abs(value1) <= DEC_MAX_INT32) || (abs(value2) <= DEC_MAX_INT32))

I encountered an overflow when using dec::decimal6.
I changed my downloaded version and changed the line to:

if ((llabs(value1) <= DEC_MAX_INT32) && (llabs(value2) <= DEC_MAX_INT32))

The overflow occurred on the abs() call to one of the values.
I thought it safer to make it an AND condition, not and OR.

this.m_value should be this->m_value for decimal::operator/=

Hello,

Firstly, thank you very much for creating and publishing this wonderful library!

When adding to my project, i got a bunch of compiler errors similar to the one below.

../MehProject/./LIBS/decimal_for_cpp-master/include/decimal.h:403:29: error: member reference base type 'decimal<Prec, RoundPolicy> *' is not a structure or union
                multDiv(this.m_value, 1, rhs);
                        ~~~~^~~~~~~~

The method is defined using this.m_value to access the classes m_value dec_storage_t value when it should be a ->. Changing this allows the project to compile and now the arguments to the functions being called make sense.

INT32_MAX on linux

Linux seems to not use INT32_MAX, I have added

#ifdef __linux__
    #define INT32_MAX INT_MAX
#endif

To the top of the header and this lets it compile.

Enhancement: More friendly constructor

Compile failure with that code:

int main() {
    dec::decimal<2> vol = 1.002;
    vol = 100.015;
    std::cout<<vol<<std::endl;
    return 0;
}
./try_decimal.cpp:5:21: error: no viable conversion from 'double' to 'dec::decimal<2>'
    dec::decimal<2> vol = 1.002;
./decimal_for_cpp/include/decimal.h:667:5: note: candidate constructor not viable: no known conversion from 'double' to 'const dec::decimal<2, dec::def_round_policy> &' for 1st argument
    decimal(const decimal &src) = default;

The stackoverflow said that: https://stackoverflow.com/questions/21822438/in-copy-initialization-is-the-call-to-the-copy-constructor-explicit-or-implicit

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.