3

I was performing addition and subtraction on an input of decimals that are precise to the second decimal place. I tried to improve accuracy by converting them to integers through multiplying 100, but achieved opposite effects.

Consider the following code and output:

double d = 2.01;
int a = (int) (d * 100.0);
cout << a << endl;

The output is:

200

Once and for all, what are some of the best practices regarding floating-point arithmetics? Is it at all feasible to first convert the double to an int using some variant of the code above, and then casting it back?

lwxted
  • 2,419
  • 2
  • 16
  • 22
  • `(int)(d * 100.0 + 0.5)`? – Kerrek SB Jul 09 '14 at 23:53
  • 9
    Solution: Don't use floating-point for money. – Mysticial Jul 09 '14 at 23:53
  • If you only have two decimal places worth (is this money?). Use integers everywhere and just format it properly when displaying it. – FDinoff Jul 09 '14 at 23:54
  • @Kerrek SB, it seems to work for some cases but not for others. What would you do to cast it back though? Do you just do `(double) (a / 100)`? – lwxted Jul 09 '14 at 23:55
  • 1
    Just stop assuming that a micro-penny is important to accountants and you'll have few practical problems. Elevating that micro-penny to a penny certainly *will* get you into trouble. As you found out. – Hans Passant Jul 09 '14 at 23:58
  • @lwxted: You'd use `a / 100.0`? What are the cases where it doesn't work? Negative values? – Kerrek SB Jul 09 '14 at 23:58
  • @Kerrek SB, 100.0 actually seemed to make the difference. I'll try out on more cases. – lwxted Jul 10 '14 at 00:01
  • I don't know if this answers your question or not, but if you want to have arithmetic correct to second decimal place, you can do the math in higher accuracy and then at the end just report the two decimal points. Your method (int with +0.5) is going to introduce an error of at most 0.5% for each number and these errors can add up quickly. – triple_r Jul 10 '14 at 03:31
  • 1
    @lwxted It is not so much about 100 being special as it is about 2.01 being closer to the power of two immediately below than 201 is. http://stackoverflow.com/a/18036308/139746 – Pascal Cuoq Jul 10 '14 at 10:37
  • @Mysticial decimal floating point can work just fine with money. – user1095108 Jun 07 '20 at 00:51

4 Answers4

7

If you print out the result of d*100.0 to (say) 20 decimal places, the problem will quickly become apparent:

200.99999999999997

Since that's (ever so minutely) less than 201, it gets truncated to 200 when you cast to int.

The obvious cure is to force rounding. At least if your inputs are all positive that can be as simple as adding 0.5 to the result:

int a = (int)(d*100.0 + 0.5);

If you can count on your compiler supporting it, it's even easier to use the standard library's round:

long a = lround(d*100.0);

This works correctly for both positive and negative numbers.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Last time I suggested this solution someone complained that it doesn't work if 2^52 < d < 2^53 ... – M.M Jul 10 '14 at 00:17
  • 1
    Also, this doesn't work for negative amounts (since truncation is towards-zero, not down) – M.M Jul 10 '14 at 00:20
  • 1
    @MattMcNabb: Yes, thus the: "at least if your inputs are all positive". – Jerry Coffin Jul 10 '14 at 01:34
  • You should probably just use the `rint()` function. – tmyklebu Jul 10 '14 at 01:37
  • @tmyklebu, `rint` returns a floating-point value, and so does not solve the OP's problem. `rint` is for changing 3.2 to 3.0, not converting 3.0 to 3. (The correct one to use is `lround`, as in my answer below.) – Dan R Jul 10 '14 at 02:30
  • @DanRoche: `lround` returns an integral value, and so it does not solve the OP's problem. Note the last sentence where he talks about "[converting] it back." – tmyklebu Jul 10 '14 at 03:29
3

Use the standard math library's rounding functions. In C++ this means you will have to #include <cmath> and compile with -lm.

Then, for your example:

double d = 2.01;
long x = lround(d*100);
cout << x << endl; // prints 201, for sure.

This will be the same as the "add .5" trick, but it will work correctly for positive and negative numbers.

Dan R
  • 1,412
  • 11
  • 21
2

Read this and reach enlightenment:

What Every Computer Scientist Should Know About Floating-Point Arithmetic

Floating-point arithmetic is considered an esoteric subject by many people. This is rather surprising because floating-point is ubiquitous in computer systems. Almost every language has a floating-point datatype; computers from PCs to supercomputers have floating-point accelerators; most compilers will be called upon to compile floating-point algorithms from time to time; and virtually every operating system must respond to floating-point exceptions such as overflow. This paper presents a tutorial on those aspects of floating-point that have a direct impact on designers of computer systems. It begins with background on floating-point representation and rounding error, continues with a discussion of the IEEE floating-point standard, and concludes with numerous examples of how computer builders can better support floating-point.

markgz
  • 6,054
  • 1
  • 19
  • 41
  • 4
    *This paper presents a tutorial on those aspects of floating-point that have a direct impact on designers of computer systems.* Precisely, it's not a paper for the general user of floating-point numbers and arithmetic. The Wikipedia article (http://en.wikipedia.org/wiki/Floating_point) is a far better starting point for most programmers and for many of them provides all they need to know. I do wish people would stop reflexively recommending Goldberg's paper to anyone who asks a beginner-level question about floating-point arithmetic here on SO. – High Performance Mark Jul 10 '14 at 00:22
  • 1
    @HighPerformanceMark: But it makes you look all enlightened when you cite it! As a bonus, when you say something unspeakably daft two comments later, everyone will assume you're preaching the gospel. – tmyklebu Jul 10 '14 at 01:43
0

In order to get well defined rouding, I would use floor() and ceil().

So, for the following code,

 (int)floor( 2.01*100.)

I get:

200

But for:

(int)ceil(2.01*100.)

I get:

201
kwierman
  • 441
  • 4
  • 11
  • 1
    You could just as easily get 201 for floor and 202 for ceil ; this approach does not make any difference – M.M Jul 10 '14 at 00:16