Hi Fred,

could you please run the attached debug version of ComplexCell.cc?

I am getting:

      5J3 | 4J¯1
⎕CT is: 1e-13
modulus (A) is: (5,3)
A=0 is: (0,0)
A+A=0 is: (5,3)
B÷A+A=0 is: (0.5,-0.5)
⌊B÷A+A=0 is: (1,-1)
A×⌊B÷A+A=0 is: (8,-2)
B-A×⌊B÷A+A=0 is: (-4,1)
¯4J1


The above printout are the intermediate results of the following computation
in bif_resudue():
      //                           // op       Z before     Z after
   new (Z) IntCell(0);          // Z←0      any          0
   Z->bif_equal(Z, A);          // Z←A=Z    0            A=0
   Z->bif_add(Z, A);            // Z←A+Z    A=0          A+A=0
   Z->bif_divide(Z, this);      // Z←B÷Z    A+A=0        B÷A+A=0
   Z->bif_floor(Z);             // Z←⌊Z     B÷A+A=0      ⌊B÷A+A=0
   Z->bif_multiply(Z, A);       // Z←A×Z    ⌊B÷A+A=0     A×⌊B÷A+A=0
   Z->bif_subtract(Z, this);    // Z←A-Z    A×⌊B÷A+A=0   B-A×⌊B÷A+A=0


which I believe is a faithful (apart from errors) implementation of
Z←B-A×⌊B÷A+A=0
as given in the McDonnell paper.

The key issue are probably the following out put lines:

B÷A+A=0 is: (0.5,-0.5)
⌊B÷A+A=0 is: (1,-1)

Best Regards,
/// Jürgen



On 06/24/2017 01:48 AM, Frederick Pitts wrote:
Hello Jürgen,

This is a followup to my previous email.

I have found that if I replace | and ⌊ invocations in the test code with residue AND floor (¨ needed with floor) functions (see below) from Eugene McDonnell's article, the idempotency and complete residue system count tests are successful.

∇g ← floor z
  r ← 9 ○ z
  i ← 11 ○ z
  b ← ( ⌊ r ) + 0J1 × ⌊ i
  x ← 1 ∣ r
  y ← 1 ∣ i
  → ( 1 ≤ x + y ) / L0
  g ← b
  → 0
 L0:
  → ( x < y ) / L1
  g ← b + 1
  → 0
 L1:
  g ← b + 0J1

∇ r ← w residue z
  r ← z - w × floor z ÷ w + w = 0

Maybe a faithful implementation of both these function in Gnu APL is required.

Regards,

Fred

On Fri, 2017-06-23 at 21:58 +0200, Juergen Sauermann wrote:
Hi,

please disregard my comment on bif_floor() below.

The proper line should be this (note the - instead of +):

if (D < 1.0 - Workspace::get_CT())   return zv(Z, fr, fi);

With that change my testcases (which contain the examples of both the ISO standard and
the IBM language reference) are now properly working again. Fixed in SVN 970.

/// Jürgen


On 06/23/2017 09:02 PM, Juergen Sauermann wrote:
Hi Frederick,

I suppose that this is caused by a rounding error just before the complex floor.
I have attached a debug version of ComplexCell.cc that prints every partial result of the computation.

If I run it then I get:

      5J3 | 14J5
modulus (A) is: (5,3)
A=0 is: (0,0)
A+A=0 is: (5,3)
B÷A+A=0 is: (2.5,-0.5)
⌊B÷A+A=0 is: (3,-1)
A×⌊B÷A+A=0 is: (18,4)
B-A×⌊B÷A+A=0 is: (-4,1)
¯4J1


However if I use a slightly smaller B (kind of simulating a rounding error) then I get:

      5J3 | 14J4.999999999
modulus (A) is: (5,3)
A=0 is: (0,0)
A+A=0 is: (5,3)
B÷A+A=0 is: (2.4999999999118,-0.50000000014706)
⌊B÷A+A=0 is: (2,-1)
A×⌊B÷A+A=0 is: (13,1)
B-A×⌊B÷A+A=0 is: (1,3.999999999)
1J3.999999999


I suppose that is what happens on your machine but not on mine.


You may also want to try out the following. In ComplexCell::bif_floor() around line 231 ff it says:

   // ISO: if D is tolerantly less than 1 return fr + 0J1×fi
   // IBM: if D is            less than 1 return fr + 0J1×fi
   // However, ISO examples follow IBM (and so do we)
   //
// if (D < 1.0 + Workspace::get_CT())   return zv(Z, fr, fi);   // ISO
   if (D < 1.0)    return zv(Z, fr, fi);   // IBM and examples in ISO

If you uncomment the first if statement then you get the ISO variant of bif_floor() while otherwise you
get the IBM variant (which is also the one described in the McDonnel paper). It could be that the ISO variant
works better for the bif_residue() function, but it causes problems in other places. If uncommenting the if
line should fix your problem, then I can create two variants of bif_floor(): one for bif_residue() and one
for the rest.

Best Regards,
Jürgen Sauermann


On 06/23/2017 07:31 PM, Frederick Pitts wrote:
Hello Jürgen,

SVN 969 on my platform (Fedora 25, Intel(R) Core(TM) i7-6700 CPU)

      5J3 | 14J5 1J4 ¯4J1

gives

1J4 ¯4J1 ¯4J1

not

¯1J4 ¯4J1 ¯4J1

Regards,

Fred

On Fri, 2017-06-23 at 17:38 +0200, Juergen Sauermann wrote:
Hi,

I have changed A∣B to literally follow the paper pointed out by Jay.
The complex floor itself was already implemented like described in the paper,
but AB was not.

Now (SVN 969) the complex A∣B is computed as

Z←B-A×⌊B÷A+A=0

without any attempts to improve the performance of the operation.

The result in the 5J3 modulus are now the same as in IBM APL2 (and I suppose also in J)

      5J3 ∣ 14J5 1J4 ¯4J1
¯4J1 ¯4J1 ¯4J1


I hope this finally fixed it. Thanks a lot to all that helped fixing this bug.

/// Jürgen


On 06/23/2017 09:34 AM, Jay Foad wrote:
I urge you to read Eugene McDonnell's Complex Floor, which also discusses Residue. I believe the design he comes up with in this paper was adopted more or less verbatim in APL. Also bear in mind that Floor and Residue in APL have to work well on all complex numbers, not just the Gaussian integers.

Jay.

On 23 June 2017 at 01:42, Frederick Pitts <fred.pi...@comcast.net> wrote:
Hello Jürgen,

Some observations:

1) When performing a residue calculation on positive integers, a straight-forward integer division with remainder calculation
suffices. For example, 5 ∣ 13 is computed with 13 / 5 = 2 r 3 and so 5 ∣ 13 = 3 where 3 is in the complete residue system
{ 0, 1, 2, 3, 4 }. When performing the calculation on negative integers, one has to take advantage of the fact that the
integer division quotient and remainder are not unique in order to compute a residue that is in the complete residue system.
For 5 ∣ ¯13, ¯13 / 5 = ¯2 r ¯3 where ¯3 is not in the CRS. However, ¯13 / 5 = ¯3 r 2 where 3 is in the CSR. The same concept applies Gaussian integers.

2) I suspect the decision to have the APL2 floor function round toward negative infinity, instead of toward zero,
was made based on the desire to save cpu cycles and memory in the residue function code.

3) I read at least one math literature article discussing Gaussian integer Euclidean division algorithms, that recommended
rounding down to the nearest real and imaginary part toward negative infinity. Unfortunately I cannot find
the article right now. I will continue to look for it. None of the articles discussed using a complex integer floor function.

4) The reason MOD_TEST.apl shows total disagreement MODJ and the builtin residue function is that the complex floor function code change in SVN 965 relocated the CRS's on complex plane. Attached are CRS0-CRS1-6J-6-SVN964.out
CRS0-CRS1-6J-6-SVN965.out. The first file contains a CRS map for modulus ¯6J¯6 produced with the residue function
followed by a map for the same modulus produced with MODJ using SVN 964. The second file contains the same maps
using SVN 965. Observe that for SVN 964 the residue function CRS is in the bottom half of the complex plane, but for SVN 965 it is in the top half. The CRS for the MODJ function is in the bottom half in both SVN cases.
5)The complex floor code change did not help with the issue that the builtin residue function is not idempotent for all possible arguments and consequently generates too many residues. See attached CRSOTST0-SVN965.out. For a grid
of Gaussian integers with real and imaginary parts ranging from ¯15 to 15, using every value with every other value as modulus and second argument, there were 40 case where the order of CSR exceeded the modulus norm. I think that
was the failure count with the previous SVN.

Sincerely, I think the complex floor and ceiling functions should not be used by other functions even if IBM and ISO
imply they are in their documentations. I'm not seeing them used in the Gaussian integer literature. Again, please correct me if I'm wrong.

Regards,

Fred

On Thu, 2017-06-22 at 18:08 +0200, Juergen Sauermann wrote:
Hi again,

sorry a small typo below. Lines 19/20 should read:

      (¯6J¯5 - 0J¯11) ÷ ¯6J¯6
0J¯1

/// Jürgen

On 06/22/2017 05:44 PM, Juergen Sauermann wrote:
Hi Fred at al.,

I have made another attempt to fix the residue function, SVN 965.

For complex m∣b It now rounds down the real() and imag() parts of the quotient q←b÷m and returns b-q.
Instead of always rounding towards 0 or -infinity, the rounding direction is now (compared to the previous
attempt) determined by the quadrant in which the modulus m lies.

There are still differences to the results displayed by MOD_test.apl, but I suppose they are
caused by that program. For example, the first line of
MOD_test.apl, says:

  LA    RA   MODJ   |
¯6J¯6 ¯6J¯5   0J¯11  0J1

We have:

      (¯6J¯5 - 0J1) ÷ ¯6J¯6
1
      (0J¯11 - 0J1) ÷ ¯6J¯6
1J1

so both 0J¯11 and 0J1 are valid remainders modulo ¯6J¯6. However, the
magnitude of
0J¯11 (= 11) is larger than the magnitude of the divisor ¯6J¯6 (= around 8.4).
I suppose most people expect the remainder of a division to be in some sense
smaller than the divisor.

Regarding Jay's idempotency requirement we now have:

      f←{6J6|⍵}
      f ¯3 ¯2 ¯3 ¯1 0 1 2 3
3J6 4J6 3J6 5J6 0 ¯5J6 ¯4J6 ¯3J6
      f f ¯3 ¯2 ¯3 ¯1 0 1 2 3
3J6 4J6 3J6 5J6 0 ¯5J6 ¯4J6 ¯3J6


      f←{5J3|⍵}
      f ¯3 ¯2 ¯3 ¯1 0 1 2 3
2J3 3J3 2J3 4J3 0 ¯2J5 ¯1J5 0J5
      f f ¯3 ¯2 ¯3 ¯1 0 1 2 3
2J3 3J3 2J3 4J3 0 ¯2J5 ¯1J5 0J5

so at least the first modulus seems to work as well. The result is still different
from APL2 as reported by Jay, but I can't tell why:

IBM APL2:

      5J3 ∣ 14J5 1J4 ¯4J1

¯4J1 ¯4J1 ¯4J1

GNU APL:

      5J3 ∣ 14J5 1J4 ¯4J1
1J4 1J4 1J4


But both 1J4 and ¯4J1 are valid remainders. Interestingly Jay's idempotency requirement seems to
be fulfilled by both the GNU APL and by IBM APL2, so that that requirement alone does not suffice
to tell which result is correct.

On the other hand this matter seems to be like discussing if the square root of 4 is 2 or -2 with
both answers being correct.

Best Regards,
Jürgen Sauermann



On 06/21/2017 10:25 PM, Frederick Pitts wrote:
Jürgen,

The proposed change to DIVJ does not work because 'q1' is a complex number, so the '×' in '× q1' is the complex complement function, not the sign function. I tried the proposed change and every test fails.

I will try to hack DIVJ to use a floor towards zero instead of towards minus infinity for the real and imaginary
parts of the quotient and see what happens.

Correct me if I am wrong, but my mind set is that the APL residue function has to satisfy the following invariants:
1) The order of the complete residue system (residue count) for a given modulo 'n' has to equal the norm of 'n'.
2) And as Jay Foad so succinctly expressed it, the residue function has to be idempotent with respect to its right argument,
e.g., ( n | m ) = n | n | m .
regardless of the implementation of the residue function.

Later,

Fred




On Wed, 2017-06-21 at 19:46 +0200, Juergen Sauermann wrote:
Hi Fred,

I have a question about the MOD_test.apl that you kindly provided.

In function DIVJ on line 57 ff it says:

z ← q , a - b × q ← CMPLX ⌊ ( 9 11 ) ○ a ÷ b

so the quotient is rounded down towards minus infinity.

I wonder if that should be something like

z ← q , (× q1) × a - b × q ← CMPLX ⌊ ∣ 9 11 ○ q1 ← a ÷ b

so that the quotient is rounded towards 0? Interestingly IBM and ISO
give different definitions for the residue in terms of APL:

IBM (language reference, page 227):
Z←L∣R
Z is R-L×⌊ R÷L+L=0


ISO (chapter 7.2.9 Residue):
R←Q∣P
R←P-(×P)×|Q×⌊|P÷Q
and return R if (×R)=×Q, or R+Q
otherwise.

That suggest that IBM rounds the quotient down towards minus infinity while ISO rounds
 towards 0.

My naive view on remainder is that the nearest integer quotient shall be smaller in
magnitude and not smaller in value. Regarding your proposal (which is different from
both IBM and ISO) my concern is that may lead to different results for modulo N and
modulo N×1J0

Best Regards,
Jürgen Sauermann


On 06/21/2017 03:08 AM, Frederick Pitts wrote:
Jürgen,

This message is being resent because last minute changes I made to CRS0.apl and CRS1.apl do not output the
data I intended.  This message has corrected versions of those files attached.  Please discard the old CRS0.apl and CRS1.apl files.  The first line of output is the modulo basis, the second line is the calculated complete residue system values and the third line is the number of residues in the CRS on the previous line.

CRSOTST0.apl and CRSOTST1.apl are unchanged.

Also please find attached MOD_TEST.apl which compares the residues calculated by MODJ and the builtin residue function and reports discrepancies.  The first column of output is the modulo basis, the second column the right argument to the functions, the third column the MODJ result and the fourth column is the builtin residue function result.

Regards

Fred

Hello Jürgen,
	SVN 964 moved us in the right direction but not completely out of the
woods.  SVN 964 still exhibits errors.  For instance
      2J6 | 5J5
¯1J7
      2J6 | ¯1J7
¯3J1
      2J6 | ¯3J1
¯3J1
	I found this and previous residue function errors using the attached APL
code files.  The files with base name ending in '0' use the builtin residue
function.  Those with base name ending in '1' use a residue function implemented
in APL.  The files with base name beginning with 'CRSOTST' test if the order of
the complete residue system (CRS) equals the norm of the modulo basis.  That
test fails for several modulo bases, 2J6 being one of them, using the builtin
residue function. No errors are detected with the APL implementation.  The other files
can be used to plot the CRS for a given modulo basis where 'a' and 'b' in
'a + b * i' are limited to +15 to -15 range.  A full screen terminal window is
needed to see the plot.
	My APL implementation of the residue function is very close to what you
described in your previous email.  Maybe comparing the two implementations will
give insight into why the builtin residue function fails for some modulo bases.
	I make no assertion that my implementation is correct in all
aspects.
Regards,
Fred
On Tue, 2017-06-20 at 14:14 +0200, Juergen Sauermann wrote:
Hi Frederick,

the algorithm for A ∣ B used in GNU APL is this:

- compute the quotient QB÷A,
- "round down" Q to the next (complex) integer Q1,
- return B - Q1×A


Now the problem seems to be what is meant by "round down". There are two candidates:

  Q1 ← ⌊ Q                                          i.e. use APL floor to round down Q
  Q1 ← Complex( floor(Q.real(),
floor(Q.imag()) )   i,e, use C/C++ floor() to round down Q.

In your  5J3 ∣ 14J5 example, the quotient is 2.5J¯0.5, which gives different results for the APL floor and the C/C++ floor().

The APL floor
2.5J¯0.5 is 3J¯1 (a somewhat dubious invention in the ISO standard on page 19, which I used up to
including SVN 963), while the C/C++ floor() is
2J¯1. The difference between the APL floor and the C/C++ floor is 1.0 which,
multiplied by the divisor, explains the differences that we see.

As of SVN 964 I have changed the residue function () to use the C/C++ floor instead of the APL floor. The APL floor and
Ceiling functions ( and ) are still using the apparently broken definition in the ISO standard.

I hope this works better for you. At least I am getting this in SVN 964:

      5J3 | 14J5
1J4
      5J3 | 1J4
1J4


whereas SVN 963 was giving:

      5J3 | 14J5
¯4J1
      5J3 | 1J4
¯4J1



Best Regards,
/// Jürgen



On 06/19/2017 07:03 PM, Frederick Pitts wrote:
Jürgen,

	With gnu apl (svn 961 on Fedora 25, Intel(R) Core(TM) i7-6700
CPU), the residue function (∣) yields the following:

      5J3 ∣ 14J5
1J4
      5J3 | 1J4
¯4J1
      5J3 | ¯4J1
¯4J1
The above result means that two elements in the complete residue system
(CSR) for mod 5J3 are equal, i.e. 1J4 = ¯4J1 mod 5J3, which is not
allowed.  None of the elements of a CSR can be equal modulo the CSR's
basis.

Regards,

Fred











/*
    This file is part of GNU APL, a free implementation of the
    ISO/IEC Standard 13751, "Programming Language APL, Extended"

    Copyright (C) 2008-2016  Dr. Jürgen Sauermann

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <errno.h>
#include <fenv.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

#include "ComplexCell.hh"
#include "ErrorCode.hh"
#include "FloatCell.hh"
#include "IntCell.hh"
#include "Output.hh"
#include "Workspace.hh"

#include "Cell.icc"

//-----------------------------------------------------------------------------
ComplexCell::ComplexCell(APL_Complex c)
{
   value.cval_r  = c.real();
   value2.cval_i = c.imag();
}
//-----------------------------------------------------------------------------
ComplexCell::ComplexCell(APL_Float r, APL_Float i)
{
   value.cval_r  = r;
   value2.cval_i = i;
}
//-----------------------------------------------------------------------------
APL_Float
ComplexCell::get_real_value() const
{
   return value.cval_r;
}
//-----------------------------------------------------------------------------
APL_Float
ComplexCell::get_imag_value() const
{
   return value2.cval_i;
}
//-----------------------------------------------------------------------------
bool
ComplexCell::is_near_int() const
{
   return Cell::is_near_int(value.cval_r) &&
          Cell::is_near_int(value2.cval_i);
}
//-----------------------------------------------------------------------------
bool
ComplexCell::is_near_zero() const
{
   if (value.cval_r  >=  INTEGER_TOLERANCE)   return false;
   if (value.cval_r  <= -INTEGER_TOLERANCE)   return false;
   if (value2.cval_i >=  INTEGER_TOLERANCE)   return false;
   if (value2.cval_i <= -INTEGER_TOLERANCE)   return false;
   return true;
}
//-----------------------------------------------------------------------------
bool
ComplexCell::is_near_one() const
{
   if (value.cval_r >  (1.0 + INTEGER_TOLERANCE))   return false;
   if (value.cval_r <  (1.0 - INTEGER_TOLERANCE))   return false;
   if (value2.cval_i >  INTEGER_TOLERANCE)           return false;
   if (value2.cval_i < -INTEGER_TOLERANCE)           return false;
   return true;
}
//-----------------------------------------------------------------------------
bool
ComplexCell::is_near_real() const
{
const APL_Float B = REAL_TOLERANCE*REAL_TOLERANCE;
const double I = value2.cval_i * value2.cval_i;

   if (I < B)     return true;   // I is absolutely small

const double R = value.cval_r * value.cval_r;
   if (I < R*B)   return true;   // I is relatively small
   return false;
}
//-----------------------------------------------------------------------------
bool
ComplexCell::equal(const Cell & A, APL_Float qct) const
{
   if (!A.is_numeric())   return false;
   return tolerantly_equal(A.get_complex_value(), get_complex_value(), qct);
}
//-----------------------------------------------------------------------------
// monadic build-in functions...
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_factorial(Cell * Z) const
{
   if (is_near_real())
      {
        const FloatCell fc(get_real_value());
        fc.bif_factorial(Z);
        return E_NO_ERROR;
      }

const APL_Float zr = get_real_value() + 1.0;
const APL_Float zi = get_imag_value();
const APL_Complex z(zr, zi);

   new (Z) ComplexCell(gamma(get_real_value(), get_imag_value()));
   if (errno)   return E_DOMAIN_ERROR;
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_conjugate(Cell * Z) const
{
   new (Z) ComplexCell(conj(cval()));
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_negative(Cell * Z) const
{
   new (Z) ComplexCell(-cval());
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_direction(Cell * Z) const
{
const APL_Float mag = abs(cval());
   if (mag == 0.0)   return IntCell::zv(Z, 0);
   else              return ComplexCell::zv(Z, cval()/mag);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_magnitude(Cell * Z) const
{
   return FloatCell::zv(Z, abs(cval()));
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_reciprocal(Cell * Z) const
{
   if (is_near_zero())  return E_DOMAIN_ERROR;

   if (is_near_real())
      {
        const APL_Float z = 1.0/value.cval_r;
        if (!isfinite(z))   return E_DOMAIN_ERROR;
        return FloatCell::zv(Z, z);
      }

const APL_Complex z = 1.0 / cval();
   if (!isfinite(z.real()))   return E_DOMAIN_ERROR;
   if (!isfinite(z.imag()))   return E_DOMAIN_ERROR;
   return zv(Z, z);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_roll(Cell * Z) const
{
const APL_Integer qio = Workspace::get_IO();
   if (!is_near_int())   return E_DOMAIN_ERROR;

const APL_Integer set_size = get_checked_near_int();
   if (set_size <= 0)   return E_DOMAIN_ERROR;

const uint64_t rnd = Workspace::get_RL(set_size);
   return IntCell::zv(Z, qio + (rnd % set_size));
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_pi_times(Cell * Z) const
{
   new (Z) ComplexCell(M_PI * cval());
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_pi_times_inverse(Cell * Z) const
{
   new (Z) ComplexCell(cval() / M_PI);
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_ceiling(Cell * Z) const
{
const APL_Float cr = ceil(value.cval_r);    // cr ≥ value.cval_r
const APL_Float Dr = cr - value.cval_r;     // Dr ≥ 0
const APL_Float ci = ceil(value2.cval_i);   // ci ≥ value2.cval_i
const APL_Float Di = ci - value2.cval_i;    // Di ≥ 0
const APL_Float D = Dr + Di;                // 0 ≤ D < 2

   // ISO: if D is tolerantly less than 1 return fr + 0J1×fi
   // IBM: if D is            less than 1 return fr + 0J1×fi
   // However, ISO examples follow IBM (and so do we)
   //
// if (D < 1.0 + Workspace::get_CT())   return zv(Z, cr, ci);   // ISO
   if (D < 1.0)   return zv(Z, cr, ci);   // IBM and examples in ISO

   if (Di > Dr)   return zv(Z, cr, ci - 1.0);
   else           return zv(Z, cr - 1.0, ci);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_floor(Cell * Z) const
{
const APL_Float fr = floor(value.cval_r);    // fr ≤ value.cval_r
const APL_Float Dr = value.cval_r - fr;      // 0 ≤ Dr < 1
const APL_Float fi = floor(value2.cval_i);   // fi ≤ value2.cval_i
const APL_Float Di = value2.cval_i - fi;     // 0 ≤ Di < 1
const APL_Float D = Dr + Di;                 // 0 ≤ D < 2

   if (D < 1.0 - Workspace::get_CT())   return zv(Z, fr, fi);   // ISO

   if (Dr < Di)   return zv(Z, fr, fi + 1.0);
   else           return zv(Z, fr + 1.0, fi);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_exponential(Cell * Z) const
{
   new (Z) ComplexCell(exp(cval()));
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_nat_log(Cell * Z) const
{
   new (Z) ComplexCell(log(cval()));
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
// dyadic build-in functions...
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_add(Cell * Z, const Cell * A) const
{
   new (Z) ComplexCell(A->get_complex_value() + get_complex_value());
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_subtract(Cell * Z, const Cell * A) const
{
   new (Z) ComplexCell(A->get_complex_value() - get_complex_value());
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_multiply(Cell * Z, const Cell * A) const
{
const APL_Complex z = A->get_complex_value() * cval();
   if (!isfinite(z.real()))   return E_DOMAIN_ERROR;
   if (!isfinite(z.imag()))   return E_DOMAIN_ERROR;

   new (Z) ComplexCell(z);
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_divide(Cell * Z, const Cell * A) const
{
   if (cval().real() == 0.0 && cval().imag() == 0.0)   // A ÷ 0
      {
        if (A->get_real_value() != 0.0)   return E_DOMAIN_ERROR;
        if (A->get_imag_value() != 0.0)   return E_DOMAIN_ERROR;
        return IntCell::zv(Z, 1);   // 0÷0 is 1 in APL
      }

   new (Z) ComplexCell(A->get_complex_value() / get_complex_value());
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_residue(Cell * Z, const Cell * A) const
{
   if (!A->is_numeric())   return E_DOMAIN_ERROR;

const APL_Complex mod = A->get_complex_value();
const APL_Complex b = get_complex_value();

   // if A is zero , return B
   //
   if (mod.real() == 0.0 && mod.imag() == 0.0)
      return zv(Z, b);

   // IBM: if B is zero , return 0
   //
   if (b.real() == 0.0 && b.imag() == 0.0)
      return IntCell::z0(Z);

   // compliant implementation: B-A×⌊B÷A+A=0
   // The b=0 case may still exist due to ⎕CT
   //
   //                          	// op       Z before     Z after
CERR << std::setprecision(14);
CERR << "⎕CT is: " << Workspace::get_CT() << endl;
CERR << "modulus (A) is: " << A->get_complex_value() << endl;
   new (Z) IntCell(0);          // Z←0      any          0
   Z->bif_equal(Z, A);          // Z←A=Z    0            A=0
CERR << "A=0 is: " << Z->get_complex_value() << endl;
   Z->bif_add(Z, A);           	// Z←A+Z    A=0          A+A=0
CERR << "A+A=0 is: " << Z->get_complex_value() << endl;
   Z->bif_divide(Z, this);      // Z←B÷Z    A+A=0        B÷A+A=0
CERR << "B÷A+A=0 is: " << Z->get_complex_value() << endl;
   Z->bif_floor(Z);             // Z←⌊Z     B÷A+A=0      ⌊B÷A+A=0
CERR << "⌊B÷A+A=0 is: " << Z->get_complex_value() << endl;
   Z->bif_multiply(Z, A);       // Z←A×Z    ⌊B÷A+A=0     A×⌊B÷A+A=0
CERR << "A×⌊B÷A+A=0 is: " << Z->get_complex_value() << endl;
   Z->bif_subtract(Z, this);    // Z←A-Z    A×⌊B÷A+A=0   B-A×⌊B÷A+A=0
CERR << "B-A×⌊B÷A+A=0 is: " << Z->get_complex_value() << endl;
   return E_NO_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_maximum(Cell * Z, const Cell * A) const
{
   // maximum of complex numbers gives DOMAN ERROR if one of the cells
   // is not near-real.
   //
   if (!is_near_real())         return E_DOMAIN_ERROR;
   if (!A->is_near_real())      return E_DOMAIN_ERROR;

const APL_Float breal = get_real_value();

   if (A->is_integer_cell())
      {
        const APL_Integer a = A->get_int_value();
        return a >= breal ? IntCell::zv(Z, a) : FloatCell::zv(Z, breal);
      }

   // both A and B are near-real, therefore they must be numeric and have no
   // non-0 imag part
   //
const APL_Float a = A->get_real_value();
   return FloatCell::zv(Z, a >= breal ? a : breal);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_minimum(Cell * Z, const Cell * A) const
{
   // minimum of complex numbers gives DOMAN ERROR if one of the cells
   // is not near real.
   //
   if (!is_near_real())      return E_DOMAIN_ERROR;
   if (!A->is_near_real())   return E_DOMAIN_ERROR;

const APL_Float breal = get_real_value();

   if (A->is_integer_cell())
      {
        const APL_Integer a = A->get_int_value();
        return a <= breal ? IntCell::zv(Z, a) : FloatCell::zv(Z, breal);
      }

   // both A and B are near-real, therefore they must be numeric and have no
   // non-0 imag part
   //
const APL_Float a = A->get_real_value();
   return FloatCell::zv(Z, a <= breal ? a : breal);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_equal(Cell * Z, const Cell * A) const
{
const APL_Float qct = Workspace::get_CT();
   if (A->is_complex_cell())
      {
        const APL_Complex diff = A->get_complex_value() - get_complex_value();
        if (diff.real() >  qct)   return IntCell::zv(Z, 0);
        if (diff.real() < -qct)   return IntCell::zv(Z, 0);
        if (diff.imag() >  qct)   return IntCell::zv(Z, 0);
        if (diff.imag() < -qct)   return IntCell::zv(Z, 0);
        return IntCell::zv(Z, 1);
      }

   if (A->is_numeric())
      {
        if (get_imag_value() >  qct)   return IntCell::zv(Z, 0);
        if (get_imag_value() < -qct)   return IntCell::zv(Z, 0);

        const APL_Float diff = A->get_real_value() - get_real_value();
        if (diff >  qct)   return IntCell::zv(Z, 0);
        if (diff < -qct)   return IntCell::zv(Z, 0);
        return IntCell::zv(Z, 1);
      }

   return IntCell::zv(Z, 1);
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_power(Cell * Z, const Cell * A) const
{
   // some A to the complex B-th power
   //
   if (!A->is_numeric())   return E_DOMAIN_ERROR;

const APL_Float ar = A->get_real_value();
const APL_Float ai = A->get_imag_value();

   // 1. A == 0
   //
   if (ar == 0.0 && ai == 0.0)
       {
         if (cval().real() == 0.0)   return IntCell::z1(Z);   // 0⋆0 is 1
         if (cval().imag() > 0)      return IntCell::z0(Z);   // 0⋆N is 0
         return E_DOMAIN_ERROR;                               // 0⋆¯N = 1÷0
       }

   // 2. complex result
   {
     const APL_Complex a(ar, ai);
     const APL_Complex z = pow(a, cval());
     if (!isfinite(z.real()))   return E_DOMAIN_ERROR;
     if (!isfinite(z.imag()))   return E_DOMAIN_ERROR;

     new (Z) ComplexCell(z);
     return E_NO_ERROR;
   }
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_logarithm(Cell * Z, const Cell * A) const
{
   // ISO p. 88
   //
   if (!A->is_numeric())          return E_DOMAIN_ERROR;

   if (get_real_value() == A->get_real_value() && 
       get_imag_value() == A->get_imag_value())   return IntCell::z1(Z);


   if (value.cval_r == 0.0 && value2.cval_i == 0.0)   return E_DOMAIN_ERROR;
   if (A->is_near_one())                              return E_DOMAIN_ERROR;

   if (A->is_real_cell())
      {
        const APL_Complex z = log(cval()) / log(A->get_real_value());
        if (!isfinite(z.real()))   return E_DOMAIN_ERROR;
        if (!isfinite(z.imag()))   return E_DOMAIN_ERROR;
        return zv(Z, z);
      }

   if (A->is_complex_cell())
      {
        const APL_Complex z = log(cval()) / log(A->get_complex_value());
        if (!isfinite(z.real()))   return E_DOMAIN_ERROR;
        if (!isfinite(z.imag()))   return E_DOMAIN_ERROR;
        return zv(Z, z);
      }

   return E_DOMAIN_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_circle_fun(Cell * Z, const Cell * A) const
{
   if (!A->is_near_int())   return E_DOMAIN_ERROR;
const APL_Integer fun = A->get_checked_near_int();

   new (Z) FloatCell(0);   // prepare for DOMAIN ERROR

const ErrorCode ret = do_bif_circle_fun(Z, fun, cval());
   if (!Z->is_finite())   return E_DOMAIN_ERROR;
   return ret;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::bif_circle_fun_inverse(Cell * Z, const Cell * A) const
{
   if (!A->is_near_int())   return E_DOMAIN_ERROR;
const APL_Integer fun = A->get_checked_near_int();

   new (Z) FloatCell(0);   // prepare for DOMAIN ERROR

ErrorCode ret = E_DOMAIN_ERROR;
   switch(fun)
      {
        case 1: case -1:
        case 2: case -2:
        case 3: case -3:
        case 4: case -4:
        case 5: case -5:
        case 6: case -6:
        case 7: case -7:
                ret = do_bif_circle_fun(Z, -fun, cval());
                if (!Z->is_finite())   return E_DOMAIN_ERROR;
                return ret;

        case -10:  // +A (conjugate) is self-inverse
                ret = do_bif_circle_fun(Z, fun, cval());
                if (!Z->is_finite())   return E_DOMAIN_ERROR;
                return ret;

        default: return E_DOMAIN_ERROR;
      }

   // not reached
   return E_DOMAIN_ERROR;
}
//-----------------------------------------------------------------------------
ErrorCode
ComplexCell::do_bif_circle_fun(Cell * Z, int fun, APL_Complex b)
{
   switch(fun)
      {
        case -12: {
                    ComplexCell cb(-b.imag(), b.real());
                    cb.bif_exponential(Z);
                  }
                  return E_NO_ERROR;

        case -11: return zv(Z, -b.imag(), b.real());

        case -10: return zv(Z, b.real(), -b.imag());

        case  -9: return zv(Z,             b   );

        case  -7: // arctanh(z) = 0.5 (ln(1 + z) - ln(1 - z))
                  {
                    const APL_Complex b1      = ONE() + b;
                    const APL_Complex b_1     = ONE() - b;
                    const APL_Complex log_b1  = log(b1);
                    const APL_Complex log_b_1 = log(b_1);
                    const APL_Complex diff    = log_b1 - log_b_1;
                    const APL_Complex half    = 0.5 * diff;
                    return zv(Z, half);
                  }

        case  -6: // arccosh(z) = ln(z + sqrt(z + 1) sqrt(z - 1))
                  {
                    const APL_Complex b1     = b + ONE();
                    const APL_Complex b_1    = b - ONE();
                    const APL_Complex root1  = sqrt(b1);
                    const APL_Complex root_1 = sqrt(b_1);
                    const APL_Complex prod   = root1 * root_1;
                    const APL_Complex sum    = b + prod;
                    const APL_Complex loga   = log(sum);
                    return zv(Z, loga);
                  }

        case  -5: // arcsinh(z) = ln(z + sqrt(z^2 + 1))
                  {
                    const APL_Complex b2 = b*b;
                    const APL_Complex b2_1 = b2 + ONE();
                    const APL_Complex root = sqrt(b2_1);
                    const APL_Complex sum =  b + root;
                    const APL_Complex loga = log(sum);
                    return zv(Z, loga);
                  }

        case  -4: if (b.real() >= 0 ||
                      (b.real() > -1 && Cell::is_near_zero(b.imag()))
                     )   return zv(Z,  sqrt(b*b - 1.0));
                  else   return zv(Z, -sqrt(b*b - 1.0));

        case  -3: // arctan(z) = i/2 (ln(1 - iz) - ln(1 + iz))
                  {
                    const APL_Complex iz = APL_Complex(- b.imag(), b.real());
                    const APL_Complex piz = ONE() + iz;
                    const APL_Complex niz = ONE() - iz;
                    const APL_Complex log_piz = log(piz);
                    const APL_Complex log_niz = log(niz);
                    const APL_Complex diff = log_niz - log_piz;
                    const APL_Complex prod = APL_Complex(0, 0.5) * diff;
                    return zv(Z, prod);
                  }

        case  -2: // arccos(z) = -i (ln( z + sqrt(z^2 - 1)))
                  {
                    const APL_Complex b2 = b*b;
                    const APL_Complex diff = b2 - ONE();
                    const APL_Complex root = sqrt(diff);
                    const APL_Complex sum = b + root;
                    const APL_Complex loga = log(sum);
                    const APL_Complex prod = MINUS_i() * loga;
                    return zv(Z, prod);
                  }

        case  -1: // arcsin(z) = -i (ln(iz + sqrt(1 - z^2)))
                  {
                    const APL_Complex b2 = b*b;
                    const APL_Complex diff = ONE() - b2;
                    const APL_Complex root = sqrt(diff);
                    const APL_Complex sum  = APL_Complex(-b.imag(), b.real())
                                           + root;
                    const APL_Complex loga = log(sum);
                    const APL_Complex prod = MINUS_i() * loga;
                    return zv(Z, prod);
                  }

        case   0: return zv(Z, sqrt(1.0 - b*b));

        case   1: return zv(Z, sin       (b));

        case   2: return zv(Z, cos       (b));

        case   3: return zv(Z, tan       (b));

        case   4: return zv(Z, sqrt(1.0 + b*b));

        case   5: return zv(Z, sinh      (b));

        case   6: return zv(Z, cosh      (b));

        case   7: return zv(Z, tanh      (b));

        case  -8: // ¯8○B is - 8○B
        case   8: {
                    // Let b = X + iY and sq = (¯1 - R*2)*.5
                    // Then 8○X+JY:  Z is: + sq  if  X > 0 and Y > 0
                    //                           or  X = 0 and Y > 1
                    //                           or  X < 0 and Y ≥ 0, and
                    //                     - sq  otherwise
                    bool pos_8 = false;
                    if      (b.real() >  0) { if (b.imag() > 0)  pos_8 = true; }
                    else if (b.real() == 0) { if (b.imag() > 1)  pos_8 = true; }
                    else                    { if (b.imag() >= 0) pos_8 = true; }

                    if (fun == 8)   pos_8 = ! pos_8;

                    const APL_Complex sq = sqrt(-(1.0 + b*b));  // (¯1 - R*2)*.5

                    return zv(Z, pos_8 ? sq : -sq);
                  }

        case   9: return FloatCell::zv(Z,      b.real());

        case  10: return FloatCell::zv(Z, sqrt(b.real()*b.real()
                                             + b.imag()* b.imag()));
        case  11: return FloatCell::zv(Z, b.imag());

        case  12: return FloatCell::zv(Z, atan2(b.imag(), b.real()));
      }

   // invalid fun
   //
   return E_DOMAIN_ERROR;
}
//-----------------------------------------------------------------------------
APL_Complex
ComplexCell::gamma(APL_Float x, const APL_Float y)
{
   if (x < 0.5)
      return M_PI / sin(M_PI * APL_Complex(x, y)) * gamma(1 - x, -y);

   // coefficients for lanczos approximation of the gamma function.
   //
#define p0 APL_Complex(  1.000000000190015   )
#define p1 APL_Complex( 76.18009172947146    )
#define p2 APL_Complex(-86.50532032941677    )
#define p3 APL_Complex( 24.01409824083091    )
#define p4 APL_Complex( -1.231739572450155   )
#define p5 APL_Complex(  1.208650973866179E-3)
#define p6 APL_Complex( -5.395239384953E-6   )

   errno = 0;
   feclearexcept(FE_ALL_EXCEPT);

   x += 1.0;

const APL_Complex z(x, y);

const APL_Complex ret( (sqrt(2*M_PI) / z)
                      * (p0                            +
                          p1 / APL_Complex(x + 1.0, y) +
                          p2 / APL_Complex(x + 2.0, y) +
                          p3 / APL_Complex(x + 3.0, y) +
                          p4 / APL_Complex(x + 4.0, y) +
                          p5 / APL_Complex(x + 5.0, y) +
                          p6 / APL_Complex(x + 6.0, y))
                       * pow(z + 5.5, z + 0.5)
                       * exp(-(z + 5.5))
                     );

   // errno may be set and is checked by the caller

   return ret;

#undef p0
#undef p1
#undef p2
#undef p3
#undef p4
#undef p5
#undef p6
}
//=============================================================================
// throw/nothrow boundary. Functions above MUST NOT (directly or indirectly)
// throw while funcions below MAY throw.
//=============================================================================

#include "Error.hh"   // throws

//-----------------------------------------------------------------------------
bool
ComplexCell::get_near_bool()  const   
{
   if (value2.cval_i >  INTEGER_TOLERANCE)   DOMAIN_ERROR;
   if (value2.cval_i < -INTEGER_TOLERANCE)   DOMAIN_ERROR;

   if (value.cval_r > INTEGER_TOLERANCE)   // 1 or invalid
      {
        if (value.cval_r < (1.0 - INTEGER_TOLERANCE))   DOMAIN_ERROR;
        if (value.cval_r > (1.0 + INTEGER_TOLERANCE))   DOMAIN_ERROR;
        return true;
      }
   else
      {
        if (value.cval_r < -INTEGER_TOLERANCE)   DOMAIN_ERROR;
        return false;
      }
}
//-----------------------------------------------------------------------------
APL_Integer
ComplexCell::get_near_int() const
{
// if (value2.cval_i >  qct)   DOMAIN_ERROR;
// if (value2.cval_i < -qct)   DOMAIN_ERROR;

const double val = value.cval_r;
const double result = round(val);
const double diff = val - result;
   if (diff > INTEGER_TOLERANCE)    DOMAIN_ERROR;
   if (diff < -INTEGER_TOLERANCE)   DOMAIN_ERROR;

   return APL_Integer(result + 0.3);
}
//-----------------------------------------------------------------------------
bool
ComplexCell::greater(const Cell & other) const
{
   switch(other.get_cell_type())
      {
        case CT_CHAR:    return true;
        case CT_INT:
        case CT_FLOAT:
        case CT_COMPLEX: break;
        case CT_POINTER: return false;
        case CT_CELLREF: DOMAIN_ERROR;
        default:         Assert(0 && "Bad celltype");
      }

const Comp_result comp = compare(other);
   if (comp == COMP_EQ)   return this > &other;
   return (comp == COMP_GT);
}
//-----------------------------------------------------------------------------
Comp_result
ComplexCell::compare(const Cell & other) const
{
   if (other.is_character_cell())   return COMP_GT;
   if (!other.is_numeric())   DOMAIN_ERROR;

const APL_Float qct = Workspace::get_CT();
   if (equal(other, qct))   return COMP_EQ;

APL_Float areal = other.get_real_value();
APL_Float breal = get_real_value();

   // we compare complex numbers by their real part unless the real parsts
   // are equal. In that case we compare the imag parts. The reason for
   // comparing this way is compatibility with the comparison of real numbers
   //
   if (tolerantly_equal(areal, breal, qct))
      {
        areal = other.get_imag_value();
        breal = get_imag_value();
      }

   return (breal < areal) ? COMP_LT : COMP_GT;
}
//-----------------------------------------------------------------------------
PrintBuffer
ComplexCell::character_representation(const PrintContext & pctx) const
{
   if (pctx.get_PP() < MAX_Quad_PP)
      {
         // 10⋆get_PP()
         //
         APL_Float ten_to_PP = 1.0;
         loop(p, pctx.get_PP())   ten_to_PP *= 10.0;
      
         // lrm p. 13: In J notation, the real or imaginary part is not
         // displayed if it is less than the other by more than ⎕PP orders
         // of magnitude (unless ⎕PP is at its maximum).
         //
         const APL_Float pos_real = value.cval_r < 0
                                  ? -value.cval_r : value.cval_r;
         const APL_Float pos_imag = value2.cval_i < 0
                                  ? -value2.cval_i : value2.cval_i;

         if (pos_real >= pos_imag)   // pos_real dominates pos_imag
            {
              if (pos_real > pos_imag*ten_to_PP)
                 {
                   const FloatCell real_cell(value.cval_r);
                   return real_cell.character_representation(pctx);
                 }
            }
         else                        // pos_imag dominates pos_real
            {
              if (pos_imag > pos_real*ten_to_PP)
                 {
                   const FloatCell imag_cell(value2.cval_i);
                   PrintBuffer ret = imag_cell.character_representation(pctx);
                   ret.pad_l(UNI_ASCII_J, 1);
                   ret.pad_l(UNI_ASCII_0, 1);
                   
                   ret.get_info().flags |= CT_COMPLEX;
                   ret.get_info().imag_len = 1 + ret.get_info().real_len;
                   ret.get_info().int_len = 1;
                   ret.get_info().fract_len = 0;
                   ret.get_info().real_len = 1;
                   return ret;
                 }
            }
      }

bool scaled_real = pctx.get_scaled();   // may be changed by print function
UCS_string ucs(value.cval_r, scaled_real, pctx);

ColInfo info;
   info.flags |= CT_COMPLEX;
   if (scaled_real)   info.flags |= real_has_E;
int int_fract = ucs.size();
   info.real_len = ucs.size();
   info.int_len = ucs.size();
   loop(u, ucs.size())
      {
       if (ucs[u] == UNI_ASCII_FULLSTOP)
           {
             info.int_len = u;
             if (!scaled_real)   break;
             continue;
           }

        if (ucs[u] == UNI_ASCII_E)
           {
             if (info.int_len > u)   info.int_len = u;
             int_fract = u;
             break;
           }
      }
   info.fract_len = int_fract - info.int_len;

   if (!is_near_real())
      {
        ucs.append(UNI_ASCII_J);
        bool scaled_imag = pctx.get_scaled();  // may be changed by UCS_string()
        const UCS_string ucs_i(value2.cval_i, scaled_imag, pctx);

        ucs.append(ucs_i);

        info.imag_len = ucs.size() - info.real_len;
        if (scaled_imag)   info.flags |= imag_has_E;
      }

   return PrintBuffer(ucs, info);
}
//-----------------------------------------------------------------------------
bool
ComplexCell::need_scaling(const PrintContext &pctx) const
{
   // a complex number needs scaling if the real part needs it, ot
   // the complex part is significant and needs it.
   return FloatCell::need_scaling(value.cval_r, pctx.get_PP()) ||
          (!is_near_real() && 
          FloatCell::need_scaling(value2.cval_i, pctx.get_PP()));
}
//-----------------------------------------------------------------------------

Reply via email to