### Strange int() result

by menth0l (Monk)
 on Jun 18, 2014 at 09:11 UTC Need Help??
menth0l has asked for the wisdom of the Perl Monks concerning the following question:

Here is a problem that I've stumbled upon:
``` my \$amount = 35784.45;
\$amount *= 100;
print \$amount, "\n"; # gives 3578445
print int(\$amount); # gives 3578444 (!)
How can I *safely* get rid of fractions by multiplying by 100? I need to convert currency amounts this way and I'm out of ideas. I could do something like:
```\$amount =~ s/^(\d+)\.(\d{2})\$/\$1\$2/
but isn't there any easier way?

Replies are listed 'Best First'.
Re: Strange int() result
by choroba (Bishop) on Jun 18, 2014 at 09:19 UTC
35784.45 * 100 is in fact
```\$ perl -e 'printf "%.15f\n", 35784.45 * 100'
3578444.999999999534339

See What every computer scientist should know about floating-point arithmetic for explanation.

int just removes the part after the dot (for positive numbers, at least). Its documentation contains a list of functions you can use to get more pleasant results.

لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

3578444.999999999534339 suggests you are running into the limits of your Perl/PC combination's floating point handling.

Your problem would be better handled with fix point arithmetic. Look at Math::BigInt.

Re: Strange int() result
by Athanasius (Chancellor) on Jun 18, 2014 at 09:30 UTC

The simplest way to round to a whole number is to add 0.5 before truncating with int:

```19:26 >perl -wE "say int(35784.45 * 100); say int(35784.45 * 100 + 0.5
+);"
3578444
3578445

19:27 >

Hope that helps,

 Athanasius <°(((>< contra mundum Iustus alius egestas vitae, eros Piratica,

... and for negative values subtract 0.5
```perl -wE "say int(-35784.45 * 100 - 0.5);"
-3578445
Re: Strange int() result
by davido (Archbishop) on Jun 18, 2014 at 18:38 UTC

You didn't mention where your data comes from. One problem is that as soon as you import data as floating point, it is stored in an internal format that loses precision if it cannot be represented precisely as n/2^m (edited). Whatever the conversion, do it as early as possible, as the first thing you do with your data before any other mathematical operation.

MJD suggests in his blog about Moonpig: a billing system that doesn't suck that because floating-point numbers are among the things that suck, the law of the Moonpig project states that "...all money amounts are integers. Each money amount is an integral number of “millicents”..."

The blog's discussion of handling of financial numbers is very helpful. Please do read it. I don't think I would touch financial data again without first browsing through it to refresh my memory.

Dave

The only shop I ever worked in where I had to touch the money code did this too. We used cents as the integer instead of millicents but same idea, just grainier precision.

Also noticed MJD's note–

Your account is currently past due! Pay the outstanding balance of \$ 0 . 00 or we will be forced to refer your account for collection.

I got this exact letter from American Express once so plenty of devs who have no business making this class of mistake do so anyway.

I've had bills for amounts in the "few pennies" range; less than the value of the postage stamp. Back in the days when I paid via checks and snail mail, I recall on at least one (possibly more) occasion I intentionally sent a check rounded up to a dollar.

This accomplished two things. First, it threw any possible round-off-error ball back into their court. And second, it gained me the minor satisfaction of getting the last word -- forcing a refund check in a silly-small amount.

Dave

I twice got a similar letter, except the balance was \$(0.01), meaning they owed me money. Turns out there was a bug in the Point of Sale software where the sales tax was rounded down for purchases and up for returns. I pointed it out after the first letter. The rest of that billing cycle I had no purchases, thus the second letter.

My main client is a very large telecoms operator (35 million mobile phone customers, not counting those in the foreign and overseas subsidiaries) in Europe and I am working a lot on their mobile phone billing system. All the financial amounts are stored internally in the database as euro cents integers, with input and output masks providing for two decimal places.

Is euro-cents sufficient precision? In mjd's talk (and in his blog) he alludes to the fact that if you divide a term by a rate you'll end up with some rounding errors that are mitigated by millicents, and that in a larger scale might be best handled by microcents.

Dave

Re: Strange int() result
by Laurent_R (Canon) on Jun 18, 2014 at 18:04 UTC
I think the solution proposed by Athanasius is probably best. But if you want to use a regex, this one is simpler than yours:
```\$amount =~ s/\.//;

It is no use in simply removing the dot. Probably you mean \$amount =~ s/\..+//? int() is faster.

How so? The OP's problem comes from the use of int function on a number calculated as 35784.45 * 100 and turning out to be very very slightly smaller than 3578445. It seems to me you probably read too quickly the OP. Example under the debugger:
```  DB<10> \$amount = 35784.45;

DB<11> \$amount =~ s/\.//;

DB<12> print \$amount;
3578445
Re: Strange int() result
by sundialsvc4 (Abbot) on Jun 18, 2014 at 21:05 UTC

You will need to check exactly how this database internally represents these quantities, and do precisely as the Romans do.   If it is known to be an integer number-of-cents, then I suggest that you take it as an integer ... bypassing any sort of input/output mask (which can introduce problems of its own).   Multiply the integer by one hundred, do the math, and then divide by 100 ... adding (subtracting) one if the (absolute value of the) remainder is 50 or more.   (This is the way that some, but not all, database systems handle the “currency” data type, and they normally use BigInts.)

It is extremely difficult to completely avoid “off by one cent” problems.   Usually the most important thing to make very-sure of is that all of the numbers that are printed on any customer’s statement do add up, because there will be some old cratchit out there who adds-up his phone bill “just to make sure,” and you don’t want him doing QA for you.

I agree, but, without bragging, I am most probably the best technical expert on this database and, more generally, the best technical expert on this application still working on it. Add to that that I am also certainly one of the top-five or at least top-ten functional experts on the application. And I make quite good money as an independent consultant on that expert knowledge (although, with such knowledge and knowhow, in the US, I would probably earn at least twice what I get here, but it's OK, I am not complaining). Well, I think I really know what I am talking about when I talk about this application.

Excuse me, Laurent – I didn’t mean to insult you, and I publicly apologize to you now, if any offense was taken.   In part, my response was directed to “the peanut gallery” of others who might one day read this thread.   (Probably we have all experienced the “off by a penny” issue, and how can we explain to people that the computer’s total is actually more accurate?   I’ve long ago given-up on that ...)   By pointing out something that you already knew (of course), I did not mean in any way to suggest that you didn’t.   Again, I own my words, and I apologize sincerely.

Create A New User
Node Status?
node history
Node Type: perlquestion [id://1090277]
Approved by Eily
Front-paged by davido
help
Chatterbox?
and the daffodils sway...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (3)
As of 2018-05-26 13:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
World peace can best be achieved by:

Results (194 votes). Check out past polls.

Notices?