2

I have a very large Decimal number as a string, it cannot be accurately represented by a float or double, so I cannot use any function that works with numbers, I have to keep my number as string, and do all functions with strings.

For example: PI:

string pI = round("3.141592653589793239", 6);

will return "3.141593".

Precision normally refers to total number of digits, as such my answer has a Precision of 7, so I will call this Decimal Places as to not confuse this issue.

I realize I could just write a function to do this, and I am working on it, but I wanted to ask for the most accurate way of doing this, and so far that is not my code, I blame it on my headache, but I have spent way too much time looking for a solution, getting caught up in using this Library and that one, when it seems there should be a library that does just this, without using floating-point math, and I have looked at a few arbitrary-precision arithmetic libraries, and have not been able to figure out how to set the number of Decimal Places, and have tried using the Precision, and came to stand still, because the numbers it gave me were not correct at the low range, between 0-42 decimal places.

I am using this in a Qt Qml Felgo App, so I will be calling it from Qml JavaScript, via a C++ back end call.

Update: Writing my string round function got ugly real quick, too many things to check for, its just as easy as it may sound till you start testing from them.

Looking at

CLN: https://ginac.de/CLN/cln.html

MPIR: https://github.com/wbhart/mpir

A fork of GMP: http://gmplib.org/

TTMath BigNum: https://sourceforge.net/projects/ttmath/

And I have an issue with Boost that ended in a bug report, and I thought I could write my own round function, so I could use their library, but what is the point, no matter what Library I use, I have a requirement for accuracy, and decimal point places.

People that have a need for Math, need a Math Library that can handle all their needs, and rounding to a specific decimal place is a given, yet I am having a hard time finding a function in any library that can to this well, so I have to test every library, and that is not easy, first you have to learn how to use it, and if it does not work the way you think it should, you end up spending more time, like I am doing here, trying to figure it out, and trust me, I read all the post about using big numbers, Floats specifically, that I could find, but none of them really dealt with arbitrary decimal point precision, meaning to do math at a specific decimal precision, most people think more is more accurate, that is not my case, I am looking for a way to do math that will return the correct number and not an estimation of it, float and long double is an estimate I cannot use, it was off by 100 miles, that bugs me, but not as much as just bad estimates, boost gave me the correct number, I just could not figure out how to use precision to adjust the number of digits, and that is not really boost thing, since they have never though it important enough to add a function to set decimal places, few people I know care about precision, they just care about how many decimal places it has, and specifically returning a set number of decimal places without having to use precision to do so, I thought that if I took the length of the left side, or whole number side, and add the precision to it, that it should work, I was wrong, and the results were too, so what I really need is a library that understands this.

I relize now that every calculator is actually an estimator at best.

I did find a library that worked with numbers, did not try it, but to be complete here is is:

https://www.codewars.com/kata/5324945e2ece5e1f32000370

I like the concept of libmpdec, so I will work on that now.

I ended up using https://github.com/MikeMcl/bignumber.js it works great in Qml, just import "bignumber.js" as BigNumberJs, and following the instructions.

Thanks for all the help.

user83395
  • 103
  • 1
  • 8
  • Maybe you just want to use a big math library. https://stackoverflow.com/questions/12988099/big-numbers-library-in-c – drescherjm Apr 02 '19 at 20:29
  • 1
    You're right. This `round` you'll have to write yourself. Me, I'd compare the digit at precision +1, if it exists in the string, to `'5'` and modify the digits ahead of it accordingly. – user4581301 Apr 02 '19 at 20:45
  • 1
    Upvoted as this question is well posed. If I were you I'd use the multiprecision library in Boost. – Bathsheba Apr 02 '19 at 20:47
  • Is your string guaranteed to be a number in fixed point notation or can there be other formats too? – Michael Kenzel Apr 02 '19 at 21:01
  • Yuck. Remember to test the special cases like `-9.9999999999999` or `0.00000000000001` – stark Apr 02 '19 at 21:19
  • All good answers thanks, I testing arbitrary-precision arithmetic libraries, I am done testing boost, ran into what I am calling a bug, reported it, will look at all that you have mentioned or link to, and yes Yuck, this is why my code sucks, so I really do need a good Library, so far :CLN looks the best, BigNum, and a fork of GMP, I will update question. – user83395 Apr 03 '19 at 00:06
  • The only question mark here is in the title, and the answer is of course “yes, you can write one”. You can show your attempt and the test case(s) it fails, or ask how to use a particular library to make this happen; anything else is off-topic. – Davis Herring Apr 03 '19 at 01:45
  • Trying to stay on topic, and using one of these libraries should be, if I can find one that can do this, so far I have not, still looking, and hoping someone knows this function in one of the libraries, as it is: I am looking at what they suggested, hoping to find what I am looking for. – user83395 Apr 03 '19 at 02:06
  • @user83395: Using one is fine; canvassing all of a given genre is iffy. Anyway, I’m pretty confident that this can be done with some very basic C-style loops over characters; why don’t you include your attempt? – Davis Herring Apr 03 '19 at 13:25
  • The reason I do not show my code is simple, it does not work for all cases, it is not that complex on the surface, you chose a method for rounding and stick with it, but which method is a question that makes this complex, in Qt I decided on a backward for loop: for (int i = (decimalSide.length() - 1); i > -1; i--) – user83395 Apr 03 '19 at 17:28

1 Answers1

2

Given you need precise decimal places, all of the libraries you mentioned are either out of bounds or will require setting absurd levels of precision to guarantee your results are rounded accurately.

The problem is they're all based on binary floating point, so when you specify a precision, e.g. with mpf_set_default_prec in GMP/MPIR (or the equivalent call in MPFR, a fork of GMP that provides more guarantees on exact, portable precision), you're saying how many bits will be used for the significand (roughly, how many bits of integer precision exist before it is scaled by the exponent). While there is a rough correspondence to decimal precision (so using four bits of significand for every one decimal digit of precision needed is usually good enough for most purposes), the lack of decimal precision means explicit output rounding is always needed to avoid false precision, and there is always the risk of inconsistent rounding: a number that logically ends in 5 and should be subjected to rounding rules like round half-even, round half-up or round half-down might end up with false precision that makes it appear to not end exactly with 5, so the rounding rules aren't applied.

What you want is a decimal math library, like Java's BigDecimal, Python's decimal, etc. The C/C++ standard library for this is libmpdec, the core of the mpdecimal package (on Python 3.3+, this is the backend for the decimal module, so you can experiment with its behavior on Python before committing to using it in your C/C++ project). Since it's decimal based, you have guaranteed precision levels; if you set the precision to 4 and mode to ROUND_HALF_EVEN, then parsing "XX.X45" will consistently produce "XX.X4", while "XX.X75" will consistently produce "XX.X8" (the final 5 rounds the next digit to even numbers). You can also "quantize" to round to a specific precision after the decimal point (so your precision might be 100 to allow for 100 digits combined on left and right hand sides of the decimal point, but you can quantize relative to "0.0000" to force rounding of the right hand of the decimal to exactly four digits).

Point is, if you want to perform exact decimal rounding of strings representing numbers in the reals in C/C++, take a look at libmpdec.

For your particular use case, a Python version of the code (using the moneyfmt recipe, because formatting numbers like "1.1E-26" to avoid scientific notation is a pain) would look roughly like:

from decimal import localcontext

def round_string(s, digits, rounding=None):
    '''
    Round string s representing a decimal floating point number to the specific digits of precision

    rounding is the current context's default mode if passed as None, otherwise
    any decimal module rounding mode is accepted.
    '''
    s = s.strip()  # Remove whitespace to get "real" length of string
    with localcontext() as ctx:
        # For inputs that may be in scientific notation, it's kind of annoying to
        # determine required precision, so we make a stab at it that's almost always
        # overestimating, but should work consistently
        decpart, sep, exp = s.lower().partition('e')
        ctx.prec = len(decpart) + digits + (abs(int(exp)) if sep else 0)
        if rounding is not None:
            ctx.rounding = rounding
        return moneyfmt(ctx.create_decimal(s), digits, sep='')

Obviously, equivalent C++ code will be somewhat more complicated, but not necessarily by much; the moneyfmt recipe can be simplified to avoid handling a bunch of monetary things we don't need, and if you don't need to handle scientific notation, the precision calculation is simpler. Regardless, libmpdec is the ultimate source of Python 3.3+'s decimal package, so all of it can be done, with guaranteed conformance with the IEEE 754 decimal floating point specification.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • That is what I call an answer! – David C. Rankin Apr 04 '19 at 18:53
  • I had libmpdec2 installed, so I must have known about it, but did not try it, I also see libmpdec-dev and libmpdec-doc, I am on Ubuntu Mate Cinnomon, so installing it is not a problem. Now to learn how to use it, thanks. – user83395 Apr 04 '19 at 19:21