Numeric#coerce for complex number calculations with rational coefficients

Ruby allows for user-defined coercions when different types are used with arithmetic operators.
This powerful feature enables the following one-liner to perform complex number calculations with rational coefficients and automatic common denominator:

p Rational(12345678901234567890 ,(1 - 12345678901234567891i)) -
   Rational(12345678901234567891,(1 + 12345678901234567890i))

This ruby code outputs:

((-228623681298582551271376318164380429986
  /11615286144559076666048468336368822614758804202921592629334943372565917420041)
 +(23230572289118153332096936672737645229441400512076991074912761305743708030085
  /11615286144559076666048468336368822614758804202921592629334943372565917420041)
  *i)

The expression might appear to be of type "Rational < Complex < BigInt> >", but this is not the case.
The Numeric#coerce method ensures that the calculation is performed as "Complex <Rational < BigInt > >" to allow for different denominators in the real and imaginary parts.

In contrast, Rust requires around 40 lines of code to achieve the same functionality. right?
Please tell me how to write the code below more compactly.

Perhaps the Rust language's lack of redefinable coercion methods when giving different types to four arithmetic operators is preventing simple code.

use num::rational::{BigRational};
use num::{BigInt};
use num::complex::Complex;

fn main() {
    let x1 = Complex::new(
      BigRational::new(
        BigInt::parse_bytes(b"12345678901234567890", 10).unwrap(),
        BigInt::parse_bytes(b"1", 10).unwrap()),
      BigRational::new(
        BigInt::parse_bytes(b"0", 10).unwrap(),
        BigInt::parse_bytes(b"1", 10).unwrap()));
   let x2 = Complex::new(
    BigRational::new(
      BigInt::parse_bytes(b"1", 10).unwrap(),
      BigInt::parse_bytes(b"1", 10).unwrap()),
    BigRational::new(
      BigInt::parse_bytes(b"-12345678901234567891", 10).unwrap(),
      BigInt::parse_bytes(b"1", 10).unwrap()));
   let z1 = x1.clone()/x2.clone();
   println!("(x1/x2)={} + {}", z1.re, z1.im);

   let x3 = Complex::new(
      BigRational::new(
        BigInt::parse_bytes(b"12345678901234567891", 10).unwrap(),
        BigInt::parse_bytes(b"1", 10).unwrap()),
      BigRational::new(
        BigInt::parse_bytes(b"0", 10).unwrap(),
        BigInt::parse_bytes(b"1", 10).unwrap()));

   let x4 = Complex::new(
    BigRational::new(
      BigInt::parse_bytes(b"1", 10).unwrap(),
      BigInt::parse_bytes(b"1", 10).unwrap()),
    BigRational::new(
      BigInt::parse_bytes(b"12345678901234567890", 10).unwrap(),
      BigInt::parse_bytes(b"1", 10).unwrap()));
   println!("(x3/x4)={}", x1.clone()/x2.clone());
   println!("(x1/x2 - x3/x4)={}", x1/x2 - x3/x4);
  }

Since big integers are not builtin types, you can't define them in a way as easy as some languages with built-in big integer support (Python, Ruby, etc.).

However, thanks to the power of rust macros, you can do this with the crate dashu:
rbig!(12345678901234567890) / (ubig!(1) - ubig!(12345678901234567891)) - rbig!(12345678901234567891) / (ubig!(1) + ubig!(12345678901234567890)) (dashu doesn't support complex numbers yet)

Unfortunately, analyze the math expression in a macro is a little complicate and currently I'm not aware of any crates that can evaluate math expression in compile time with arbitrary precision support.

1 Like

Thank you for your response.

I would like to see a deeper analysis of what is missing in the Rust compiler and escalate it to compiler implementers.

the crate dashu:( doesn't support complex numbers yet)

That's not enough.
I would like Rust to have a way to define commutations and reductions manually, like Ruby does.

  • Traditionally: Operator extension was a compiler developer's privilege.

  • However, in recent years: Operator extension is now available to compiler users.

  • In traditional compilers, operator definitions were fixed in the compiler and could not be extended by users.

  • However, in recent years, compilers that provide extension features for users to define their own operators have become increasingly common.

That ruby code shown earlier uses Numeric#coerce internally as shown below.

operator "+" (a: Rational < Complex < BigInt>,  Complex < BigInt> >,
              b:  Rational < Complex < BigInt>,  Complex < BigInt> >) 

calculates c= Rational((a.numerator * b.denominator)  + (b.numerator * a.denominator) ,
               a.  denominator * b.denominator)
             as type Rational < Complex < BigInt>,  Complex < BigInt> >;
calculates d = Rational(c. numerator * c.conjugate, c.denominator * c.conjugate)
             as type Rational < Complex < BigInt>,  <BigInt>>;

calculates e = Complex(
                   Rational(d.numerator.real as <BigInt>, d.denominator) +
                   Rational(d.numerator.imag as <BigInt>, d.denominator)
               );
Rational#reduce,coerce:  
           f= Complex(
                   Rational(
                        (d.numerator.real as <BigInt>/
                         BigInt#gcd(d.numerator.real,d.denominator )),
                        (d.denominator)/
                         BigInt#gcd(d.numerator.real,d.denominator )),
                   Rational(
                       (d.numerator.imag as <BigInt>/
                         BigInt#gcd(d.numerator.imag,d.denominator )),
                       (d.denominator /
			 BigInt#gcd(d.numerator.imag,d.denominator ))
                        )
               )
              as type Complex< Rational<BigInt>>;
# Ruby instance method Numeric#coerce
# lib/rational.rb`
def coerce(other)
  if other.kind_of?(Float)
    return other, self.to_f
  elsif other.kind_of?(Integer)
    return Rational.new!(other, 1), self
  else
    super
  end
end

def + (a)
  if a.kind_of?(Rational)
    # ............
  elsif a.kind_of?(Integer)
    # .........
  elsif a.kind_of?(Float)
    Float(self) + a
  else
    x, y = a.coerce(self)
    x + y
  end
end

Rust only has coercions if there is only one sensible way to coerce i.e closures to function pointers, sized to unsized, etc. Type coercions - The Rust Reference

When I see this correctly Ratio<Complex<I>> could implement Mul<Self, Output=Complex<Ratio<I>>> where I: Integer with specialization. The question is whether this is the best way to handle Gaussian integers. Maybe using GaussianInt in num_irrational::quadratic - Rust would be better.

1 Like

Thank you for your wonderful response.

Did you also notice that something was missing from the Rust compiler?

You have found two constraints and expressed them in a Rust specification.

  • It is assumed that Bigint meets the requirements for Gaussian integers;
  • Ratio<Complex<I>> could implement {Mul, add, sub,div} <Self, Output=Complex<Ratio<I>> where I: Gaussian integers.
    because Complex<Ratio<I>> is "allmost simply" value than Ratio<Complex<I>> where I is integers.

This problem, that is,
the implementation of a class system in which calculation formulas can be drawn as compactly as high-school math calculation problems,
and the calculation results are considered correct by high-school students,
is a delicate issue.
And you will find many more constraints.

In the case of the Ruby language, the generic type of the operation result is "semi-fixed" for all combinations of operators between the Numeric class and the generic {Integer, Float Rational, Complex}.
Furthermore, Numeric#coerce allows overloading of the generic type of the operation result for each combination of operator and argument types.

p z1 = Rational(12345678901234567890 ,(1 - 12345678901234567891i)) -
   Rational(12345678901234567891,(1 + 12345678901234567890i))

p h2=((z1 - z1) == 0)

* z1===Ratio<Complex<I>> minus Ratio<Complex<I>> Output=Complex<Ratio<I>> 
* h2=== (Complex<Ratio<I>> minus Complex<Ratio<I>> ) output Complex<Ratio<I>>
         Complex<Ratio<I>> comparable to  I:Gaussian_integers.

In Rust, I think it is an incorrect implementation to define the generic type of the operation result as “fixed”(impossible "semi-fixed") for all operator combinations between the generic {Integer, Float Rational, Complex}.

But,Numeric#coerce Macro or "like C++20's Concepts function" is missing in Rust?

I would like Rust to have a way to define commutations and reductions manually, like Ruby does.

The is very unlikely to happen in rust: http://lang-team.rust-lang.org/frequently-requested-changes.html#arbitrary-custom-operator-syntax

1 Like

I understand that.
Arbitrary custom operator syntax

Arbitrary custom operator syntax
Rust allows overloading existing operators;
for instance, you can implement the Add trait to overload the + operator.
However, Rust does not allow creating new operators.
.....
which tends to encourage using overloaded operators for the same semantic purposes,
rather than for building arbitrary domain-specific languages.

A bad example of this is the use of APL (A Programming Language), also known as a ``write only language''.

APL was able to combine the inner product operator "+/" by concatenating operator "" and aggregation operator "*" before and after the reduce operator "/".

 APL: vector1 +/* vector2
 Ruby: vector1.zip(vector2).map{|e1,e2| e1 * e2}.reduce(:+)

I also understand how overloading an operator that adheres to "the same semantic purposes" may impose any derivative constraints.
For example, the relationship between "+" and "*" is

  num3 = num1 + num1 + num1 = num1 * 3;    (num1 * 0) === 0
  array3 = array1 + array1 + array1 = array1 * 3; (array1 * 0) === []
  string3 = string1 + string1 + string1 = string1 * 3;   (string1 * 0) === "" 

  require 'matrix'
  vector43 = (vector41 = Vector[1, 2, 3,4])  + vector41 + vector41 
                 = vector41 * 3 === Vector[3,6,9,12];
                   (vector41 * 0) === Vector[0,0,0,0]

I also understand that consideration of constraints on relationships between several operators like this requires consideration of category theory in linear algebra.

I think Rust lacks the ability to implement "the same semantic purposes" compared to Ruby.
(Numeric#coerce Macro )