Topic: Some questions concerning 'big integer' proposal


Author: =?ISO-8859-1?Q?=22Daniel_Kr=FCgler_=28ne_Spangenberg=29=22?= <dsp@bdal.de>
Date: Mon, 11 Apr 2005 02:37:53 CST
Raw View
This posting deals with the current state of the "big integer" proposal for
C++0x described in:

http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1744.pdf

1) According to the proposal there should exist the following operators:

integer operator * ( integer const& , double ) ;
integer operator * ( double , integer const& ) ;
integer operator / ( integer const& , double );
integer & operator * = ( double );
integer & operator / = ( double );

I have to say that these operators are causing some headache to me:

Only multiplying and divisive operators accepting double exist, no + or -
operators, which causes an unusual asymmetry to existing build-in
datatypes. The
proposal explains the needlessness/complicateness of +/- ops (which is
fine), but
it wants to provide */ ops taking double values.

If these exist, code like the following would be accepted by the
compiler w/o
any warning:

integer i = ...;

i *= 3.1415;

although most modern compilers will warn me, if I do the same thing with
built-in integral types instead of the new integer type. Worth is, that
the more
natural syntax

i *= 3;

does **not** compile due to ambiguity, as explained below.

Why should this class support a programming style, which is dubious at
best? Why shouldn't it be sufficient to enforce programmers combining
double's (even
if they represent integral values) with integer either using a cast-like
style as in

i *= integer(3.1415);

or

i *= to_integer(3.1415);

by either introducing an explicit integer c'tor accepting a double or even
better by some additionally provided cast/conversion function?

Yes, the just shown alternative will construct a temporary integer, but
honestly:
I can't think of a **real** implementation which would not do that
internally.
A very (tiny) advantage could be some internal code equivalent to the
following logic:

integer & operator*=(double d)
{
   if (d <= LONGLONG_MAX && d >= LONGLONG_MIN) {
     return *this *= static_cast<long long>(d);
   } else {
     return *this *= to_integer(d);
   }
}

but that optimization seems to be not worth the mentioned possible source
of subtle errors.

And since we speak about it: Why is there no conversion accepting a long
double which most probably will have more significant digits than double
and would be
even more worth to convert?

Another asymmetry is that these operators return an integer instead of the
floating point type, which contrasts the built-in behaviour as well. The
reason for that
behaviour for built-ins makes sense, because in nearly all cases the
arithmetic conversion
rules from para. 5 p. 9 lead to the type with larger "representation
capacity". From the
standpoint of significant digits this might be ok with the new integer
type, but the
possible silent loss of fractional parts of the additional floating point
type argument is not so nice. I would like to add that even the proposed
arithmetic conversion rules of long long described in

http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1735.pdf

don't change the ordering between floating point and integral conversion.

Furtheron initializing an integer with a double would force users to write

double d = ..;
integer j(static_cast<long long>(d));

to get most modern compilers satisfied which seems much more afford then
the sequence:

double d = ..;
integer j(1);
j *= d;

which seems rather unnatural for me.

Another consequence results from the existing operator long long of integer
is that code like

integer i;
i == 0;
i == 0LL;

**both** comparisons will cause ambiguity due to candidates

bool operator ==(const integer &,const integer &);

and built-in

bool operator==(long long, int);

or

bool operator==(long long, long long);

respectively.
Furtheron will non-ambiguous code like

integer i;
i == integer(0);

be needed which is rather pedestrian and probably more inefficient than
adding further heterogenous comparison operators, i.e.

bool operator=(const integer &,long long);
bool operator=(long long, const integer &);
.

Note that "creating" arithmetic operators for *, /, +, and -
(funnily not for &, |, and ^!) are already proposed.

Another inconsistency is, that

integer operator/(long long, integer const &);
integer operator/(integer const &, long long);

are proposed, but only

integer operator%(integer const &, long long);

Another disadvantage of the existing integer::operator long long() combined
with the non-explicit c'tor accepting a long long is that the expression

i + 1;

is also ambiguous. In accordance to usual recommandations one should
not provide both implicit 1-argument c'tors and conversion operators.

2) The effects clause of the integer & integer::operator%=() overloads say:

"Sets *this to the reminder of (*this) / rhs"

In contrast to the operator/ overloads nothing is mentioned what happens in
case of 0 arguments. Is it guaranteed that such an argument will lead to an
invalid_argument exception?

3) Complexity descriptions are provided in O(N) notation but nowhere is
mentioned what N represents.
Some sentence like "All of the complexity requirements in this clause are
stated solely in terms of the number of operations on the internal storage
units of integer." or similar.

4) There exist no operator unspecified_bool_replacement() const. This has
the effect that a natural syntax

integer i = ..;
if (i) {
   ..
}

will chose operator long long() which has two disadvantages: First this
conversion operator is potentially unnecessary complex compared to the
needed task and much worse: It can raise an exception because
its internals cannot be represented as long long! I recommend to add one
further conversion operator, namely

integer::operator unspecified_bool_replacement() const;

to allow this operation.

5) The effect clause of :

template< class charT, class traits >
basic_ostream < charT , traits> & operator<<(basic_ostream< charT , traits>
& os, integer const& x)

says: "[..] inserts the complex number x onto [..]" which should
probably changed to
"[..] inserts the integer number x onto [..]"

Furtheron the part "is implemented as follows", which is described by

template<class charT, class traits>
basic_ostream<charT , traits> & operator<<(basic_ostream<charT , traits>
& os,
integer const& x) {
   if( x<0 )
     return os << '-' << (- x);
   else
     return os << (x/10) << char(x %10) ;
}

seems rather odd to me:
- "if( x<0 )" should not compile according to my description of ambiguity
effects caused by operator long long(). The same applies to the expressions
"x/10" and "x%10"
- The mechanism of the part handling x>=0 is not understandable to me, it
seems to cause infinite recursion and other funny effects (consider x == 0)

----------------

Recapitulating I would like to propose the following changes to the current
proposal of the new std::integer type:

(a) Remove **all member functions and operators** accepting a double.

(b) Add

integer::operator unspecified_bool_replacement() const;

with the following semantic:

"Returns sign(*this) != 0"

(c) In addition to the currently proposed homogenous comparison operators
==, !=, <, <=, >, >=
add corresponding heterogenous comparison operators taking long long
arguments.

(d) For reasons of consistency add

integer operator%(long long, integer const &);

and

integer operator&(long long, integer const &);
integer operator&(integer const &, long long);
integer operator|(long long, integer const &);
integer operator|(integer const &, long long);
integer operator^(long long, integer const &);
integer operator^(integer const &, long long);

as well as member functions

integer& integer::operator&=(long long);
integer& integer::operator|=(long long);
integer& integer::operator^=(long long);

(e) I think that the proposed

integer::operator long long() const;

does more harm than it helps (also considering my personal proposal
changes) and should be removed. A minimum required function would
be something like

long long to_longlong() const;

equivalent to bitset's to_ulong() member function.

Personally I would like much more something like

template <typename Number>
Number integer_cast(const integer& arg);

defined for e.g. all arithmetic types, but at least for long long and
possibly for
all fundamental floating point types.

For corresponding floating-point -> std::integer conversions explicit
"factory" functions would be nice, e.g. either

integer to_integer(float);
integer to_integer(double);
integer to_integer(long double);

or similar template cast functions:

template <typename Number>
integer integer_cast(Number arg);

specialized for at least all floating point types.

Greetings from Bremen,

Daniel Kr   gler



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 11 Apr 2005 17:56:42 GMT
Raw View
dsp@bdal.de (=?ISO-8859-1?Q?=22Daniel_Kr=FCgler_=28ne_Spangenberg=29=22?=)
wrote (abridged):
> http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1744.pdf
>
> [...]
>
> Only multiplying and divisive operators accepting double exist, no + or
> - operators, which causes an unusual asymmetry to existing build-in
> datatypes. The proposal explains the needlessness/complicateness of
> +/- ops (which is fine), but it wants to provide */ ops taking double
> values.

I imagine it intends:

     integer i(1000);
     i *= 3.1415;
     assert( i == integer(31415) );

In other words, the double is not truncated. It is not like:
     i *= integer(3.1415);

-- Dave Harris, Nottingham, UK.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dsp@bdal.de (=?ISO-8859-1?Q?=22Daniel_Kr=FCgler_=28ne_Spangenberg=29=22?=)
Date: Thu, 14 Apr 2005 05:01:08 GMT
Raw View
Hello Dave Harris,

Dave Harris schrieb:

>I imagine it intends:
>
>     integer i(1000);
>     i *= 3.1415;
>     assert( i == integer(31415) );
>
>In other words, the double is not truncated. It is not like:
>     i *= integer(3.1415);
>
Thanks for pointing that out! (Indeed I overlooked the fact that integer
*= double does not truncate the double value **before** the arithmetic step)

Although the attempt to provide at least a small set of mixed-type
arithmetic
in big integer is a noble one, I fear that its introduction opens further
questions. The main reason for the expected problems are, to which "slot"
of the "usual arithmetic conversions" described in 5 p. 9 the "virtual"
double-integer arithmetic should belong.

I could not find any effect description in N1744 concerning the
double/integer arithmetic of * and /. So its unclear what the effect is in
one of the following situations:

1) Which behaviour results if e.g. the product of double and integer
exceeds std::numeric_limits<double>::max()? Does the internal mechanism
of the
product have to behave as if some imaginary unlimited floating point number
is multiplied by an unlimited integral value?

2) Even simpler: Do we have to expect that usual floating-integral may
happen and that such 4.9 p.1 may hit us by saying "The behavior is
undefined if the truncated value cannot be represented in the destination
type"? From the standpoint of unlimited integer values I could expect that
it is not possible that any finite floating point number could not be
represented by the new integer type (Note that this does not conflict with
the proposed overflow exception in case of out-of-memory situations).

Another unfortunate effect is, that the proposed mixed arithmetic (I
mean here
the arithmetic between integer and floating point, not that of integer
with fundamental
integral types like long long) does not behave as built-in arithmetic.
E.g. in

double d = 3.14;
integer i = 10;

double d2 = i * d;

returns 31 and not 31.4, because the result of operator*(const integer&,
double) is
integer and not double.

All these problems could be solved, if one would provide an infinite
floating point type
as a counterpart to integer ("bigfloat" or "real" or similar). In this
case the natural
solution would be that all mixed arithmetic of integer would use
"bigfloat" as intermediate
type:

bigfloat operator*(const integer&, float);
bigfloat operator*(const integer&, double);
bigfloat operator*(const integer&, long double);
bigfloat operator*(const integer&, const bigfloat&);
.

Greetings from Bremen,

Daniel



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]