lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Numerics


From: Vadim Zeitlin
Subject: Re: [lmi] Numerics
Date: Sun, 15 May 2016 01:06:29 +0200

On Sat, 14 May 2016 20:47:49 +0000 Greg Chicares <address@hidden> wrote:

GC> On 2016-05-14 14:14, Vadim Zeitlin wrote:
GC> > On Sat, 14 May 2016 10:01:30 +0000 Greg Chicares <address@hidden> wrote:
GC> [...]
GC> > GC> Now what if, say, crediting interest for a fractional period produces 
a
GC> > GC> value like 123.456789 above, and we need to assign that value to a
GC> > GC> currency object? We round it:
GC> > GC> 
GC> > GC>   currency x2 = round_somehow_to_whole_cents(123.456789); // Okay.
GC> > GC>                       ^^^^^^^
GC> > 
GC> >  What does round_somehow_to_whole_cents() return? I assume it returns a
GC> > "currency" object and in this case it must be either intimately coupled to
GC> > the currency class (i.e. be its member method or a friend) or we must have
GC> > some way to create a currency object from the exact amount of cents. And I
GC> > strongly prefer the second approach.
GC> 
GC> I've been assuming something completely different: that it returns 'double'.

 I'm sorry, I have to strenuously disagree in this case. If this function
returns double, it means that you do want to assign doubles to currency
implicitly, without specifying the rounding rules. This in turn means that
the currency class ctor must throw an exception if it's passed a value
having more than 2 (significant) decimal digits. And having value-like
classes throwing from their ctor/assignment operator is just a horrible
idea in C++ because it allows writing completely innocently looking code
unexpectedly throwing exceptions.

GC> Let's consider an example from production code:
GC> 
GC> // Rounded interest increment.
GC> double AccountValue::InterestCredited
GC>     (double principal
GC>     ,double monthly_rate
GC>     ) const
GC> {
GC>     return round_interest_credit()(principal * 
ActualMonthlyRate(monthly_rate));
GC> }
GC> ...
GC>     // We may want to display credited interest separately.
GC>     // Each interest increment is rounded separately.
GC>     RegLnIntCred = InterestCredited(AVRegLn, YearsRegLnIntCredRate);
GC>     PrfLnIntCred = InterestCredited(AVPrfLn, YearsPrfLnIntCredRate);
GC> 
GC> The result and the first argument represent currency amounts; the second
GC> argument and ActualMonthlyRate() are interest rates, not currency. I'm
GC> thinking that, at least as a first step, we'd change the type of the
GC> 'RegLnIntCred' and 'PrfLnIntCred' member variables to currency. Later,
GC> if we wish, we might make InterestCredited() return currency.

 If we make RegLnIntCred and PrfLnIntCred currency objects, we must make
InterestCredited() return currency.

GC> The currency class doesn't need to know what sort of rounding has been
GC> applied. If the value assigned here:
GC>     RegLnIntCred = InterestCredited(AVRegLn, YearsRegLnIntCredRate);
GC> has been rounded in any way consistent with the Currency concept (i.e.,
GC> it is within some ε of 0.01 times an integer), then class currency
GC> performs the assignment; otherwise, it throws.

 Yes, I see what you mean now. I had, probably subconsciously, refused to
understand it before because I just didn't want to think about working with
objects with throwing ctors/assignment operators. But I remain absolutely
convinced that it is a bad idea, mixing doubles and currencies implicitly
like this is just too error-prone and it's much better to ensure that
mistakes such as assigning ActualMonthlyRate to a currency can't happen at
compile-time rather than throwing exceptions when (not if) they happen at
run-time.


GC> Perhaps that's what you have in mind here:
GC> 
GC> > [...] or we must have
GC> > some way to create a currency object from the exact amount of cents. And I
GC> > strongly prefer the second approach.
GC> 
GC> What is the datatype of "the exact amount of cents"?

 int64_t, of course.

GC> >  Very well, I can see the appeal of this (although in practice I still
GC> > don't think it's going to matter much because the set of all possible
GC> > rounding methods is quite finite and closed for extension).
GC> 
GC> Yet the set in 'rounding_rules.hpp' was extended just a few months ago:
GC>   
http://svn.savannah.nongnu.org/viewvc/lmi/trunk/rounding_rules.hpp?root=lmi&r1=6081&r2=6414
GC> and the meaning of any rounding rule can be changed at runtime in the
GC> product editor.

 Thanks, so I was wrong about this too. It doesn't change my main point
however: we must have a way of rounding, using arbitrary, and maybe
dynamically-defined, way double values to _integer_ amounts of cents.

GC> In theory, 1 ulp would be enough if lmi's rounding functions return
GC> "correctly rounded" results. If not, there must be some ε that works.
GC> Given a number of dollars like 123.4500000000001, we can determine
GC> what integral number of cents is meant.

 Choosing the right ε is not even the most pressing of my worries now. It's
the total absence of type-safety with the approach you propose that I
strongly dislike. I know that, in theory, it's possible to not make
mistakes. But the theory and practice are only the same in theory, not in
practice...

 We should have compile-time safety instead of relying on never making
mistakes, especially because there doesn't seem to be absolutely any reason
to sacrifice it here.

 Regards,
VZ


reply via email to

[Prev in Thread] Current Thread [Next in Thread]