Boost logo

Boost :

From: John Max Skaller (skaller_at_[hidden])
Date: 2001-05-11 11:58:32


Bill Seymour wrote:
 
> What I want to write is a type that deals with amounts of
> _currency_; and I want to keep it simple. I don't want to
> generalize it like std::string to have something for everyone.

        I think you must consider .. as your header file
suggests .. two levels of use: one is for a slightly
more basic use than currency, and the other for currency.

        To motivate this, just consider an invoice:

        item each number subtotal taxrate taxed
        ----------------------------------------------
        My Book 21.50 2 43.00 12.5% 46.95
        Wood 2.23 7.8 99.72 12.5% 108.22

The 'wood' quantity is square metres, and the tax rate
is a percent to 1 place. It makes sense to use fixed
point decimal (but not currency) for these quantities too.

> Some ideas from John Skaller:
> >
> > increment and decrement are undefined
> >
>
> What's undefined about adding and subtracting one? Sure,
> the operation could overflow if the type has no integral
> part; but by that argument, addition and subtraction are
> undefined in general.

        Addition and subtraction are only defined
for decimals of the same scale.

        Increment by 1 has no scale attached.
If you consider it as:

        x + decimal<0>("1")

then you see that it is undefined, unless the scale of x is 0.

        There is also no USE for incrementing non-integer
fixed point numbers. How do you 'increment' $20.35?

> > OPINION: All the non-assignment operations should be functions,
> > NOT members. NEVER use members for functional operations.
 
> I note that Fernando Cacciola agrees. What's the reason?

        Symmetry perhaps, but more precisely I think
that methods should be used for polymorphic attributes.

        Binary operations cannot be supported by OO style
(virtual function) polymorphism. Using them gives
a false impression that you can override the method
in a derived class (if 'virtual' is left off, you think
this is just an efficiency thing, not an 'in principle'
thing).

        A function

        add(X,X)

makes it clear that you can only add two things of the
same concrete type X. If this can be generalised,
then templates should be used to do it, virtual
functions CANNOT work here. (It's called the covariance problem).

> > OPINION: REMOVE the money class. This should be proposed
> > separately, and the discussion should be separate.
 
> That's actually _the_ class that's important, IMO. The
> decimal class is there principally because it doesn't
> make sense to multiply money by money. (This was inspired
> by the SI Units work.)

        I am not disputing that. I just think it is
confusing to mix the two things. The 'decimal'
class isn't an implementation detail, it is available
for use. But it is an implementation detail of the
money class that money is derived from decimal
(hence you use 'private' inheritance).

        So there are two distinct types being proposed.
Each should be considered separately on its own merits
for inclusion in the Standard.

        [It makes sense to multiply money by a decimal,
but not money by money]

> > OPINION: REMOVE the constructor taking the integral and
> > fractional parts. It just confuses things.
 
> What's confusing about it?

        Its existence clutters the interface.
I have to think carefully 'what does this mean'?

        For example:

        decimal<2>(1,1)

Um: is that 1.1 or 1.01? The answer has to be 1.01.
But that means that

        decimal<n>(1,1)

has a value dependent on n. That in turn means
it cannot sensibly be used in a template which
is parameterised by n.

I think this is a case of 'when in doubt, leave it out'.
Once the basic proposal is solid, and there is consensus,
the idea of adding this constructor could be discussed.

I'm not against it. But unlike the char * constructor,
which has an obvious use, even though the error handling
makes the semantics non-trivial, it just seems
problematic to me.

> And how would you initialize
> a decimal object with a non-integral value?

        decimal<n>("1.23")
        decimal<n>(1.23)

> The ctor that
> takes a const char* (or std::string) is an option, but it's
> computationally expensive (as George Heintzelman pointed out);
> and the ctor that takes a double is inexact.

        1. The expense is irrelevant unless you are silly
enough to put the evaluation into a critical loop.
         
        2. The floating point form is exact, provided the
number of digits is small enough.

        3. A better 'constructor' is probably:

        scale<3>(123) // value 1.23

since the overhead is zero: the given integer is exactly the
representation, no conversion required. The problem
with this is that

        decimal<3>(1) != scale<3>(1)

which is why I named it 'scale' rather than using a constructor.
[Implement with private constructor and friend]

> > OPINION: Provide a default constructor, which guarrantees
> > initial value 0.

> Already done if we don't remove the two-argument ctor above.

        Accepted.

> > [The decimal] class intends solely to use 'long int' to do
> > fixed point calculations, ...
> >
>
> No, it doesn't. A 64-bit integer is one possible internal
> representation; BCD is another.

        Let me rephrase. When I said 'long int' I didn't
mean that literally. What I meant was that one could
use a suitably long integral type.

> Indeed, I plan to try it
> both ways; but at this point, I'm still trying to get the
> public interface right.

        I know. But it is useful to think of
the representation model: to me the 'concept' of fixed
point is simply 'integer with implied point'.

        By the way: don't waste time with a BCD representation.
It has one use: a compact representation of a value
which is suitable for IO but not computation: that used
to be important on old mainframes where IO was much faster
than the CPU. Today, the IO devices are the big bottleneck:
and by an IO device, I mean stuff like RAM!

        Only a few seconds thought shows that when doing
multiple precision arithmetic, the only thing that
counts is minimising the number of 'words'.

        The minimum is always found by maximising
the word size: the maximum is limited by multiplication
needing a double length result. C is really badly
broken here: there is NO choice other than to use
a half of a long (or long long). It is a shame
that you must shift and mask, instead of
being able to assume 2 * SHORT_BITS < LONG_BITS.
In any case, a word of only 10 values is necessarily
much slower than a binary implementation on any
machine in which a byte has more than 4 bits :-)

-- 
John (Max) Skaller, mailto:skaller_at_[hidden]
10/1 Toxteth Rd Glebe NSW 2037 Australia voice: 61-2-9660-0850
checkout Vyper http://Vyper.sourceforge.net
download Interscript http://Interscript.sourceforge.net

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk