2019-01-07 02:45

# 对于货币应用，使用整数作为小数系数而不是浮点数是个好主意吗？

My application requires a fractional quantity multiplied by a monetary value.

For example, `\$65.50 × 0.55 hours = \$36.025` (rounded to `\$36.03`).

I know that floats should not be used to represent money, so I'm storing all of my monetary values as cents. `\$65.50` in the above equation is stored as `6550` (integer).

For the fractional coefficient, my issue is that `0.55` does not have a 32-bit float representation. In the use case above, `0.55 hours == 33 minutes`, so `0.55` is an example of a specific value that my application will need to account for exactly. The floating point representation of `0.550000012` is insufficient, because the user will not understand where the additional `0.000000012` came from. I cannot simply call a rounding function on `0.550000012` because it will round to the whole number.

## Multiplication solution

To solve this, my first idea was to store all quantities as integers and multiply × 1000. So `0.55` entered by the user would become `550` (integer) when stored. All calculations would happen without floats, and then simply divide by 1000 (integer division, not float) when presenting the result to the user.

• I realize that this would permanently limit me to 3 decimal places of precision. If I decide that 3 is adequate for the lifetime of my application, does this approach make sense?

• Are there potential rounding issues if I were to use integer division?

• Is there a name for this process? EDIT: As indicated by @SergGr, this is fixed-point arithmetic.

• Is there a better approach?

EDIT:

I should have clarified, this is not time-specific. It is for generic quantities like `1.256 pounds of flour`, `1 sofa`, or `0.25 hours` (think invoices).

What I'm trying to replicate here is a more exact version of Postgres's `extra_float_digits = 0` functionality, where if the user enters `0.55` (float32), the database stores `0.550000012` but when queried for the result returns `0.55` which appears to be exactly what the user typed.

I am willing to limit this application's precision to 3 decimal places (it's business, not scientific), so that's what made me consider the `&times; 1000` approach.

I'm using the Go programming language, but I'm interested in generic cross-language solutions.

• 点赞
• 写回答
• 关注问题
• 收藏
• 邀请回答

#### 3条回答默认 最新

• dongsonghen9931 2019-01-09 02:17
已采纳

Note: This is an attempt to merge different comments into one coherent answer as was requested by Matt.

# TL;DR

1. Yes, this approach makes sense but most probably is not the best choice
2. Yes, there are rounding issues but there inevitably will be some no matter what representation you use
3. What you suggest using is called Decimal fixed point numbers
4. I'd argue yes, there is a better approach and it is to use some standard or popular decimal floating point numbers library for your language (Go is not my native language so I can't recommend one)

In PostgreSQL it is better to use `Numeric` (something like `Numeric(15,3)` for example) rather than a combination of `float4`/`float8` and `extra_float_digits`. Actually this is what the first item in the PostgreSQL doc on Floating-Point Types suggests:

• If you require exact storage and calculations (such as for monetary amounts), use the `numeric` type instead.

# Some more details on how non-integer numbers can be stored

First of all there is a fundamental fact that there are infinitely many numbers in the range `[0;1]` so you obviously can't store every number there in any finite data structure. It means you have to make some compromises: no matter what way you choose, there will be some numbers you can't store exactly so you'll have to round.

Another important point is that people are used to 10-based system and in that system only results of division by numbers in a form of `2^a*5^b` can be represented using a finite number of digits. For every other rational number even if you somehow store it in the exact form, you will have to do some truncation and rounding at the formatting for human usage stage.

Potentially there are infinitely many ways to store numbers. In practice only a few are widely used:

• floating point numbers with two major branches of binary (this is what most today's hardware natively implements and what is support by most of the languages as `float` or `double`) and decimal. This is the format that store mantissa and exponent (can be negative), so the number is `mantissa * base^exponent` (I omit sign and just say it is logically a part of the mantissa although in practice it is usually stored separately). Binary vs. decimal is specified by the `base`. For example `0.5` will be stored in binary as a pair `(1,-1)` i.e. `1*2^-1` and in decimal as a pair `(5,-1)` i.e. `5*10^-1`. Theoretically you can use any other base as well but in practice only 2 and 10 make sense as the bases.

• fixed point numbers with the same division in binary and decimal. The idea is the same as in floating point numbers but some fixed exponent is used for all the numbers. What you suggests is actually a decimal fixed point number with the exponent fixed at `-3`. I've seen a usage of binary fixed-point numbers on some embedded hardware where there is no built-in support of floating point numbers, because binary fixed-point numbers can be implemented with reasonable efficiency using integer arithmetic. As for decimal fixed-point numbers, in practice they are not much easier to implement that decimal floating-point numbers but provide much less flexibility.

• rational numbers format i.e. the value is stored as a pair of `(p, q)` which represents `p/q` (and usually `q>0` so sign stored in `p` and either `p=0, q=1` for `0` or `gcd(p,q) = 1` for every other number). Usually this requires some big integer arithmetic to be useful in the first place (here is a Go example of math.big.Rat). Actually this might be an useful format for some problems and people often forget about this possibility, probably because it is often not a part of a standard library. Another obvious drawback is that as I said people are not used to think in rational numbers (can you easily compare which is greater `123/456` or `213/789`?) so you'll have to convert the final results to some other form. Another drawback is that if you have a long chain of computations, internal numbers (`p` and `q`) might easily become very big values so computations will be slow. Still it may be useful to store intermediate results of calculations.

In practical terms there is also a division into arbitrary length and fixed length representations. For example:

In practical terms I'd says that the best solution for your problem is some big enough fixed length floating point decimal representations (like .Net `decimal`). An arbitrary length implementation would also work. If you have to make an implementation from scratch, than your idea of a fixed length fixed point decimal representation might be OK because it is the easiest thing to implement yourself (a bit easier than the previous alternatives) but it may become a burden at some point.

点赞 评论
• dopii22884 2019-01-07 03:25

As mentioned in the comments, it would be best to use some builtin Decimal module in your language to handle exact arithmetic. However, since you haven't specified a language, we cannot be certain that your language may even have such a module. If it does not, here is how to go about doing so.

Consider using Binary Coded Decimal to store your values. The way it works is by restricting the values that can be stored per byte to 0 through 9 (inclusive), "wasting" the rest. You can encode a decimal representation of a number byte by byte that way. For example, 613 would become

``````6 -> 0000 0110
1 -> 0000 0001
3 -> 0000 0011

613 -> 0000 0110 0000 0001 0000 0011
``````

Where each grouping of 4 digits above is a "nibble" of a byte. In practice, a packed variant is used, where two decimal digits are packed into a byte (one per nibble) to be less "wasteful". You can then implement a few methods to do your basic addition, subtract, multiplication, etc. Just iterate over an array of bytes, and perform your classic grade school addition / multiplication algorithms (keep in mind for the packed variant that you may need to pad a zero to get an even number of nibbles). You just need to keep a variable to store where the decimal point is, and remember to carry where necessary to preserve the encoding.

点赞 评论
• doudun2565 2019-01-07 05:04

Another solution to store the result is using the rational form of the value. You can explain the number by two integer value which the number is equal `p/q`, such that both `p` and `q` are integers. Hence, you can have more precision for your numbers and do some math with the rational numbers in the format of two integers.

点赞 评论