1

We are writing software doing arithmetic operations on number with 8 fixed decimal digits (8 decimal digits after the .).

We have been bitten by the limited precision of built-in floating point types such a double a few times, mainly on equality comparisons (which are failing around the 17th decimal). So we tried moving to some fixed-precision decimal type.

From its documentation, cpp_dec_float should be such a type. So we replaced our using Decimal = double with using Decimal = boost::multiprecision::number<boost::multiprecision::cpp_dec_float<8>>;

Everything works mostly fine if we instantiate Decimal from strings, but there is a complication when it is instantiated from a floating-point literal:

assert(Decimal{0.001} == Decimal{"0.001"});

Above assertion fails, since the left-hand-side Decimal instance seems to carry the inexact representation of the literal used to initialize it, even though this epsilon goes far beyond the requested precision of 8.

Is there a way to get a "truncate on instatiation" behaviour, so the above assertion is satisfied? (Ideally, the solution would not require touching all the call sites where such instantiation takes place)

Ad N
  • 7,930
  • 6
  • 36
  • 80
  • Not sure what's going wrong here. I'd expect the floating-point value to truncate to 0.001. But it's [generally unreliable](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) to initialise this type with floating-point fractionals. You can avoid floating-point by dividing, e.g. `Decimal{1}/1000` or using a UDL such as CNL's `""_cnl` ([example](https://godbolt.org/z/YroYoscYT)). – John McFarlane Jun 10 '21 at 07:58
  • Thank you for this comment. I also expected `cpp_dec_float` to truncate to the requested precision, but it is observably not the case. (I don't see any reference to `cpp_dec_float` in the link you provided). Actually, an easy way to avoid the floating point is to initialize from string, such as `Decimal{"0.001"}`, yet I was hoping for a *drop-in* solution, not requiring to touch existing call sites already initializing `Decimal` from `double` variables and literals. – Ad N Jun 10 '21 at 08:46
  • It is inexact: print("%1000.1000f\n") % (0.001) ==> 0.001000000000000000020816681711721685132943093776702880859375000... – Andrew Aug 08 '21 at 20:44
  • @Andrew Could you please elaborate on what is inexact, and what your code statement illustrates? – Ad N Aug 09 '21 at 09:14
  • @Ad N : An inexact float is one that cannot be accurately represented as a floating point. An accurate 0.001 would be 0.00100000000000000000000000000000000000000000.... But it cannot be stored that way as a float, as illustrated by this code statement which forces the print to show more accuracy: "print("%100.100f\n") % (0.00100000000000000000000000000000000000000000)". That statement returns: "0.0010000000000000000208166817117216851329430937767028808593750000000000000000000000000000000000000000", which is not exactly 0.001. – Andrew Aug 09 '21 at 15:52
  • @Andrew Yes, the question is asked exactly in this context: how to truncate a floating point (approximate) value to the exact precision requested for a `boost::multiprecision` type. I.e. getting the same result than when instantiating it from a `string`. (The question is not about getting exact representation with built-in floating point types.) – Ad N Aug 11 '21 at 08:12

1 Answers1

0

This might explain it:

0.001 as a 32 bit float is exactly:
0.0009999999310821295
    12345678 are the 8 digits of accuracy
0.0009999999 is 0.001 as a 32 bit float rounded to the 8 digits of accuracy
0.0010000000 is 0.001 truely ecarately 

VS 7 bits of accuracy:
0.0009999999310821295
    1234567
0.001000000
0.001000000
Andrew
  • 1
  • 4
  • 19