1

I don't mean precision as in how many numbers are displayed after the decimal. I mean precision as in the decimal I am trying to use in this pictograph function keeps coming up one tenth shy of what it should be. I have tried using multiple different strategies including importing the decimal module. Here is the function I am trying to use.

values = [('tens', 10), ('fives', 5), ('ones', 1), ('tenths', 0.1)]

def get_digits(num):
    num = int(num * 10)
    num = float(num) / 10

    output_dict = {}
    for place, value in values:
        output_dict[place] = int(num // value)
        num = num % value

    return output_dict

using get_digits(277.9), yields {'tens': 27, 'ones': 2, 'tenths': 8, 'fives': 1}

I need for it to say {'tens': 27, 'ones': 2, 'tenths': 9, 'fives': 1}

Fixing it by adding 1 to the tenths after the dictionary is populated does not work, because not every decimal comes out imprecisely.

get_digits(277.6) returns {'fives': 1, 'tenths': 6, 'tens': 27, 'ones': 2}

I've been scouring the forums for an answer, but nothing quite gets it. Any assistance would be greatly appreciated.

Ryan Prince
  • 27
  • 2
  • 7
  • This is simply a result of using floating point numbers and the computer's inability to *perfectly* store them. See [1](http://en.wikipedia.org/wiki/Floating_point), [2](http://docs.python.org/3/tutorial/floatingpoint.html). If you want accuracy, use fixed point integer types. – jedwards Jan 18 '14 at 22:35
  • So, am I just dreaming in terms of being able to do what I am trying to do? – Ryan Prince Jan 18 '14 at 22:36
  • If imprecise floats are indeed the problem, then using decimal.Decimal() should help, as they store base 10 numbers precisely. You could also try fractions.Fraction(). – dstromberg Jan 18 '14 at 22:38
  • No, computers are used all the time when accuracy is important (financial, scientific, etc). The key is just to avoid using float, and either handle it yourself (usually by multiplying any would-be decimal by a consistent power of 10 that results in a whole number) or use a library (e.g. Python's [**decimal**](http://docs.python.org/2/library/decimal.html) library) – jedwards Jan 18 '14 at 22:40

1 Answers1

7

As I tried to explain in comments, the problem is that you're using floating point numbers.

For more information on floating point, see 1, 2.

The issue is that 277.9 is not actually stored as 277.9, but rather something "very close":

print('%.40f' % a)
277.8999999999999772626324556767940521240234

The solution isn't to use some arbitrary heuristic to tell whether the math is slightly off and try to correct for it. We know the math is slightly off -- that's the gift and curse given to us by floating point.

The real solution is to use fixed point math, for example, with Python's decimal module.


Edit

from decimal import Decimal

values = [
    ('tens',   Decimal(10)),
    ('fives',  Decimal(5)),
    ('ones',   Decimal(1)),
    ('tenths', Decimal('0.1'))
]

def get_digits(num):
    output_dict = {}
    for place, value in values:
        output_dict[place] = int(num // value)  # Cast from Decimal to int
        num = num % value
    return output_dict

num = Decimal('277.9')
print(get_digits(num))
# {'tens': 27, 'ones': 2, 'tenths': 9, 'fives': 1}

num = Decimal('277.6')
print(get_digits(num))
#{'tens': 27, 'ones': 2, 'tenths': 6, 'fives': 1}

The above code is very similar to yours, but uses Python's decimal module. No heuristic check is needed. The code just works, because decimals are represented accurately.

jedwards
  • 29,432
  • 3
  • 65
  • 92
  • Fixed point math can still have precision errors - if anything, it's more prone to errors than floating point. Python's decimal module is good at storing base 10 numbers, because decimal's implementation is also base 10. – dstromberg Jan 18 '14 at 22:58
  • I agree that fixed point can have precision errors, but unlike floating point representation, fixed point representation allows the user to specify the precision. Moreover, this question appears especially suited for fixed point representation, since it deals in base 10 modular arithmetic. These numbers can never be stored exactly in floating point representation but will always be stored exactly in fixed point (provided there's sufficient precision). – jedwards Jan 18 '14 at 23:08
  • Thank you. Both explanations helped me understand more. – Ryan Prince Jan 19 '14 at 04:16