Boost logo

Boost :

Subject: Re: [boost] New Boost.XInt Library, request preliminary review
From: Scott McMurray (me22.ca+boost_at_[hidden])
Date: 2010-03-30 20:51:13


On 30 March 2010 17:05, Chad Nelson <chad.thecomfychair_at_[hidden]> wrote:
>
> Now that's an argument I can finally agree with. :-)
>

Yay! =]

>
> Here's how I propose to solve it: make a "core" version of the integer
> class and functions, with no exception-blocking and no Not-a-Number
> value, in the namespace xint::core. Then make the main integer class and
> functions in namespace xint, with exception-blocking, the Not-a-Number
> value, and maybe infinity values.
>

I like the idea. Here's a brain dump of a slightly different way it could look:

The user would see two classes, finite_xint and nothrow_xint. (Names
subject to refinement, of course.) They'd share representation
somehow for move semantics, CoW, or whatever support (which means that
mixing the two is still efficient, since a temporary of one can turn
into a temporary of the other cheaply). A finite_xint maintains the
invariant that it always holds a specific, finite value. A
nothrow_xint, on the other hand, allows for special values.

Clearly converting a finite_xint to a nothrow_xint would always
succeed. Converting a nothrow_xint to a finite_xint would throw an
exception if the number isn't finite. Consider for this post at least
that they have bidirectional implicit conversions. (The throwing
implicit conversion is scary, so perhaps not a good idea, but you'll
see why I kept it anyways. It should also mean that mixed operations
would be ambiguous -- like finite_xint() + nothrow_xint() -- which
would be a good thing, imho.) Then just about every function
currently in the library would change to accept a finite_xint.

That pretty much gives the current library behaviour, but with at
least two advantages.
1) No need to explicitly spell out the "throws on NaN" behaviour for
every function, since it's clear from the argument type that passing a
non-finite number will give an exception in the implicit conversion.
(And there's no possible efficiency complaint about the check, since
if you just pass a finite_xint in the first place, there's no check
done.)
2) For sqrt, division, invmod (and any other functions that can give
NaN), you choose the behaviour by where you store it. finite_xint x =
sqrt(finite_xint(-1)); will throw even though the sqrt returned a NaN.
 If you'd rather check for NaN yourself, then you just do nothrow_xint
x = sqrt(finite_xint(-1)); (And, again, with move semantics, it's
guaranteed that there's no excess copying involved even when storing
to a different type.)

It does mean that the NaN is essentially still a signalling NaN,
though. Since I personally prefer a quiet(-as-possible) NaN, I'd want
it taken another step further.

Another version of every operation is added, taking nothrow_xints
instead; the current NaN-returning functions are renamed with a
nothrow_prefix, so for instance nothrow_sqrt(finite_xint(-1)) ==
nothrow_xint::NaN(); and the current NaN-returning functions are
re-writen to throw internally so they can return finite_xints.
(Initially all these functions would be simple wrappers, but plausibly
they'd eventually be rewriten using the unspecified common
representation either by someone with a slow try-catch implementation
or by the "exception-haters".) All of the nothrow_xint functions
would be noexcept, since bad_alloc or length_error would result in
+/-Inf and any domain error would give a NaN.

That would allow people to choose between fail-hard-and-fast code and
safely-do-as-much-as-we-can code as the situation requires. Mixing
them will still remain reasonable, since calling
is_odd(nothrow_xint::NaN()) would end up treating it like a signalling
NaN if -- as is likely -- there's no implementation of is_odd that
takes a nothrow_xint. And if only a nothrow_xint-taking version of
some function is available, finite_xint users will still be happy
since they'll still get the exception; it'll just be a bit later (when
they store the result instead of ASAP).

P.S. Upon further reflection, please keep x/0 resulting in NaN. I
agree that multiple zeros in an integer class is evil, and that means
that 1/(1/-Inf) and 1/(1/Inf) have to evaluate to the same thing, so
it should be NaN.


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