lmi
[Top][All Lists]
Advanced

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

Re: [lmi] Numerics


From: Greg Chicares
Subject: Re: [lmi] Numerics
Date: Sat, 14 May 2016 10:01:30 +0000
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Icedove/38.6.0

On 2016-05-13 14:55, Vadim Zeitlin wrote:
> On Mon, 9 May 2016 04:04:10 +0000 Greg Chicares <address@hidden> wrote:
> 
> GC> > 2. What API for rounding would you like to see.
> GC> 
> GC> No rounding API at all.
> 
>  I'd like to return to this discussion because I'm still very confused by
> your view of this currency class.
> 
> [discussing the relative tolerance]
> GC> Python makes an arbitrary choice:
> GC>   https://www.python.org/dev/peps/pep-0485/#defaults
> 
>  I'm not convinced by the reasoning here.

I suspect that this is actually not material to our apparent difficulty
in reaching consensus, so I'd like to address something else...

> GC> The problem was misstated; I should have written:
> GC>   double factor u = 1 + .0024662698;
> GC>   currency x = 123.45;      // Okay.
> GC>   double z = u * x.value(); // Okay: 123.75446100681
> GC>   x = from_value(z);        // Error: rounding details not specified.
> GC> 
> GC> In one circumstance, I might need the floor 123.75; in another, I
> GC> might need the ceiling 123.76 . If from_value() chooses a rounding
> GC> direction for me, then the answer must be wrong in one of those
> GC> circumstances.
> 
>  Very well. Then we must always pass it the rounding method. What bothers
> me with this approach is the line
> 
>       currency x = 123.45;
> 
> It doesn't use any rounding method, yet you expect it to compile. How can
> this work? The consistent thing to do here is to disallow such assignment
> as well.

I think the implementation should behave, e.g., as if written like this:

  currency::operator=(double new_value)
  {
    double z = std::round(100 * new_value);
    assert(materially_equal(new_value, 0.01 * z, some_epsilon));
    cents_ = z;
  }

Then:

  currency x0 = 123.45;     // Okay.
  currency x1 = 123.456789; // Throws.

Now what if, say, crediting interest for a fractional period produces a
value like 123.456789 above, and we need to assign that value to a
currency object? We round it:

  currency x2 = round_somehow_to_whole_cents(123.456789); // Okay.
                      ^^^^^^^

The "somehow" varies depending on context. In different contexts, we
might write various statements such as:

  currency x3 = round_up_to_whole_dollars(444.4444);
  currency x4 = round_down_to_whole_cents(555.5555);

The currency class remains the same regardless of context. The rounding
method varies by context. Those two concerns should be separated. That's
why I think rounding should be done independently, outside the currency
class.

>  The only alternative I see is to use C++11 user-defined literals, then we
> could allow
> 
>       currency x = 123.45_dollars;
> 
> and check -- during compile-time! -- that there are at most two digits
> after the period here. Not sure if this is worth the trouble though (but
> I'd love to try).

I don't think we'll need to do that.

> GC> Note: "123.76" above is really something like
> GC>   123.75999999999997
> GC> but it's "near enough" to an integer (12376) with the decimal point 
> shifted
> GC> two places to the left. The same cannot be said of 123.75446100681 .
> 
>  The words "near enough" imply that you select an ε. I'd rather avoid this.

Consider this example from above:
  currency x4 = round_down_to_whole_cents(555.5555);
We've obtained the value 555.5555 from some operation that produces
non-integral results. We round it in a specific way, producing, e.g.,
  555.5499999999, or
  555.5500000001
and now we want to assign that rounded value to a currency instance.
To do that, we must ascertain how many cents that floating-point value
is supposed to represent. That's the only problem before us. That is,
we don't need to decide what currency amount the original (unrounded)
555.5555 means; we just need to determine what currency amount the
rounded value designates. IOW: we just rounded the value to cents, and
now we want to know how many cents we got. Surely we can answer that.

This doesn't require us to select an arbitrary ε, because we can
determine an appropriate ε. For example, we could do it heuristically:
start with bounds of 0 and 1, which will prove generally inappropriate,
and iterate to an value that works--where "works" might be defined as
passing lmi's regression tests, or passing some battery of unit tests.
Another heuristic: round a broad range of doubles with all of the
particular rounding functions we intend to use, and determine the
largest discrepancy. Or we could approach it analytically at first,
choosing 1 ulp because we can't hope for anything less to work, and
increase that tolerance only if we can demonstrate necessity.

> GC> >  If we need other rounding modes, I think they should be provided by 
> this
> GC> > class itself, e.g. from_value() could take rounding_style
> GC> 
> GC> It might be better to write supplemental rounding functions that take
> GC> double arguments and return currency.
> 
>  This is, IMHO, purely a question of taste. I don't have any strong
> preference here and could see either being used.

Rounding is not necessarily inherent in a variable's type. Suppose
you have an account to which interest is credited, and from which
taxes are deducted. It might work this way:

  currency balance = value_carried_forward_from_last_month;
  balance += round_down_to_cents(balance * .002222222); // interest
  balance -= round_up_to_dollars(balance * .0111); // tax

This wouldn't work:

  currency<round_down_to_cents> balance;
  balance += balance * .002222222; // okay: round_down_to_cents()
  balance -= balance * .0111; // wrong: want round_up_to_dollars()

Rounding method is a property of the operation, not of the
operand's type.

>  However, combined with your refusal of exposing "total_cents" in any way,
> this would leave us with a class with value-like semantics without any
> non-default constructors which is somewhat unusual.

Let's see whether we can accommodate each other this way. I'm quite
confident that we'll never need total_cents(). You aren't. So let's
leave it in there, and rework lmi to use the new class. Then, after
we're done, we'll see whether total_cents() was ever needed. If it
was needed, then it must remain public. Otherwise, we can make it
private; and if that demonstrates a solid use-case for a value-like
class with no non-default constructor, so be it.

>  In this way, the rounding discussion merges with the total_cents one as I
> think it's not really logical to both view the currency class as just a
> fancy double (which is, I think, your approach) and simultaneously disallow
> constructing it from double implicitly, without specifying the rounding
> method. It seems more consistent to view currency as an integer (total
> number of cents) which can be constructed from doubles rounded to two
> decimal places to me.

I'm not sure which of those two views either of us holds: I get
lost in the abstraction. When we've completed the work, I'm sure
I'll be able to understand it in retrospect.

>  If you happen to agree with the latter statement, then I really think we
> should expose total_cents and both allow constructing currency objects from
> integer amounts of cents and retrieving this amount from them.

Okay, let's do it that way for now. Later, if we find we didn't need
those things, then we can un-expose and dis-allow them, but only after
we've completed the double-->currency conversion globally.

>  To summarize: your arguments make it clear that currency can't be
> implicitly created from double because a rounding method always needs to be
> specified. This means that the only way to create a currency object without
> rounding is from its string representation.

I'm not sure that's exactly what I'm saying, but no matter: I don't
mind permitting things now that we may later un-permit if they prove
to serve no purpose.

> This is inconvenient at least
> for the tests

I do think, though, that unit tests should be done in a class that's a
friend of currency. Otherwise, if we later find that some public
features are not useful for any other purpose, we'll have a tough time
making them private because that would break the tests.

> and will IMO also be bothersome in normal use, which is why
> I'd like to also have a ctor from the total number of cents and the
> corresponding accessor. This would allow to use integers instead of strings
> when serializing currency objects too, as a side effect, which can't be
> bad.

Here, too, I think we should keep the design decision of privacy
open. I'm not convinced that it's better to serialize currency as
binary integers rather than as strings, but if serialization is
done by a member or by a friend, then it can access internals
freely anyway, so we're not locked into any decision on privacy.




reply via email to

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