Django: 用于货币的浮动字段还是十进制字段? ?

我很好奇哪一个更适合作为货币领域?我会做一些简单的操作,比如取差价,新旧价格之间的百分比。我计划保持两个数字后面的零(即10.50) ,大多数情况下,如果这些数字是零,我将隐藏这些数字,并显示为“10”

Ps: 货币不是以美元为基础的:)

60771 次浏览

Always use DecimalField for money. Even simple operations (addition, subtraction) are not immune to float rounding issues:

>>> 10.50 - 0.20
10.300000000000001


>>> Decimal('10.50') - Decimal('0.20')
Decimal('10.30')

edit: The Satchmo Project is no longer active, so take a look at these alternatives for handling currency


The Django-based Satchmo Project has a CurrencyField and CurrencyWidget that are worth taking a look at.

Check out the satchmo_utils app directory for the source

The answer to the question is correct, however some users will stumble on this question to find out the difference between the DecimalField and the FloatField. The float rounding issue Seth brings up is a problem for currency.

The Django Docs States

The FloatField class is sometimes mixed up with the DecimalField class. Although they both represent real numbers, they represent those numbers differently. FloatField uses Python’s float type internally, while DecimalField uses Python’s Decimal type.

Read more here.

Here are other differences between the two fields:

DecimalField:

  • DecimalFields must define a decimal_places and a max_digits attribute.
  • You get two free form validations included here from the above required attributes, e.g. if you set max_digits to 4, and you type in a decimal that is 4.00000 (5 digits), you will get this error: Ensure that there are no more than 4 digits in total.
  • You also get a similar form validation done for decimal places (which in most browsers will also validate on the front end using the step attribute on the input field. If you set decimal_places = 1 and type in 0.001 as the value you will get an error that the minimum value has to be 0.1.
  • Returns a decimal.Decimal, type is <class 'decimal.Decimal'>
  • Does not have the extra validation of DecimalField
  • With a Decimal type, rounding is also handled for you due to the required attributes that need to be set as described above. So from the shell, if you
  • In the database (postgresql), the DecimalField is saved as a numeric(max_digits, decimal_places) Type, and Storage is set as "main", from above example the Type is numeric(4,1)

More on DecimalField from the Django Docs.

FloatField:

  • Returns the built in float type, <type 'float'>
  • No smart rounding, and can actually result in rounding issues as described in Seths answer.
  • Does not have the extra form validation that you get from DecimalField
  • In the database (postgresql), the FloatField is saved as a "double precision" Type, and Storage is set as "plain"

More on FloatField from the Django Docs.

Applies to Both:

  • Both fields extend from the Field class and can accept blank, null, verbose_name, name, primary_key, max_length, unique, db_index, rel, blank0, blank1, blank2, blank3, blank4, blank5, blank6, blank7, blank8, blank9, null0, null1, null2 attributes, as all Fields that extend from Field would have.
  • The default form widget for both fields is a TextInput.

I came across this question when looking for the difference between the two fields so I think this will help those in the same situation :)

UPDATE: To answer the question, I think you can get away with either to represent currency, although Decimal is a much better fit. There is a rounding issue when it counts to float's so you have to use round(value, 2) in order to keep your float representation rounded to two decimal places. Here is a quick example:

>>> round(1.13 * 50 + .01, 2)
56.51

You can still get in trouble with float and round. Like here we see it rounds down on a value of 5:

>>> round(5.685, 2)
5.68

But in this case, it will round up:

>>> round(2.995, 2)
3.0

It has all to do with how the float is stored in memory. See here.

I know this is super old, but I stumbled on it looking for something completely different, and I wanted to throw out there that in general it is inadvisable to use floating point numbers (float or decimal) for currency, as floating point math rounding will invariably lead to small errors in calculation that can add up to very large discrepancies over time.

Instead, use an integer field or a string at your preference. Multiply your currency to move the decimal place to the end and make a whole number when you store it, and then move that decimal place back where it belongs when you need to show it. This is basically how banks (and most currency libraries) handle storing data and will save you loads of trouble later on.

I learned this the hard way because it's not really a common topic; maybe this saves someone else from doing the same thing.