Engel expansion
You are encouraged to solve this task according to the task description, using any language you may know.
The Engel expansion of a positive real number x is the unique non-decreasing sequence of positive integers { a1, a2, a3 ... } such that
x = 1 / a1 + 1 / a1a2 + 1 / a1a2a3 ...
In other words, each term is the reciprocal of the cumulative product of the expansion and x is the sum of those terms.
Rational numbers have a finite Engel expansion, while irrational numbers have an infinite Engel expansion.
Tiny amounts of imprecision can cause wild variation from actual values as the (reciprocal) terms grow smaller. It can be quite challenging to maintain precision in later terms.
- Task
- Write routines (functions, procedures, whatever it may be called in your language) to convert a rational number to an Engel expansion representation and from an Engle expansion to a rational number.
- Demonstrate converting some rational numbers to an Engel expansion and back.
Test it with at least the following rational approximations of:
- 𝜋 - 3.14159265358979
- 𝑒 - 2.71828182845904
- √2 - 1.414213562373095
- Stretch
- If your language supports high precision rational numbers, do the same with at least:
- 𝜋 - 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211
- 𝑒 - 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743
- √2 - 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558
There almost definitely will be some imprecision in the later terms. Feel free to limit the display of the expansion to the first 30 terms.
- See also
C++
#include <cmath>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
class Rational {
public:
Rational(const uint64_t& aNumerator, const uint64_t& aDenominator)
: numerator(aNumerator), denominator(aDenominator) {
const uint64_t gcd = std::gcd(numerator, denominator);
numerator /= gcd;
denominator /= gcd;
}
Rational(const uint64_t& value) : numerator(value), denominator(1) { }
Rational(const std::string& decimal) {
const std::string::size_type index = decimal.find(".");
const uint32_t decimal_places = decimal.length() - 1 - index;
const uint64_t numer =
std::stoll(decimal.substr(0, index) + decimal.substr(index + 1, decimal.size()));
const uint64_t denom = std::pow(10, decimal_places);
const uint64_t gcd = std::gcd(numer, denom);
numerator = numer / gcd;
denominator = denom / gcd;
}
std::string to_decimal(const uint32_t& decimal_places) const {
std::string result = "";
uint64_t numer = numerator;
uint64_t denom = denominator;
uint64_t quotient = numer / denom;
for ( uint32_t i = 0; i <= decimal_places; ++i ) {
result += std::to_string(quotient);
numer -= denom * quotient;
if ( numer == 0 ) {
break;
}
numer *= 10;
quotient = numer / denom;
if ( i == 0 ) {
result += ".";
}
}
return result;
}
bool equals(const Rational& other) const {
return numerator == other.numerator && denominator == other.denominator;
}
Rational add(const Rational& other) const {
const uint64_t numer = ( numerator * other.denominator ) + ( denominator * other.numerator );
const uint64_t denom = denominator * other.denominator;
return Rational(numer, denom);
}
Rational subtract(const Rational& other) const {
const uint64_t numer = ( numerator * other.denominator ) - ( denominator * other.numerator );
const uint64_t denom = denominator * other.denominator;
return Rational(numer, denom);
}
Rational multiply(const Rational& other) const {
return Rational(numerator * other.numerator, denominator * other.denominator);
}
Rational inverse() const {
return Rational(denominator, numerator);
}
int64_t ceiling() const {
return ( numerator % denominator == 0 ) ? numerator / denominator : numerator / denominator + 1;
}
private:
uint64_t numerator, denominator;
};
const Rational RATIONAL_ZERO(0, 1);
const Rational RATIONAL_ONE(1, 1);
std::vector<uint64_t> to_engel(const std::string& decimal) {
std::vector<uint64_t> engel = { };
Rational rational(decimal);
while ( ! rational.equals(RATIONAL_ZERO) ) {
const int64_t term = rational.inverse().ceiling();
engel.emplace_back(term);
rational = rational.multiply(Rational(term)).subtract(RATIONAL_ONE);
}
return engel;
}
Rational from_engel(const std::vector<uint64_t>& engel) {
Rational sum = RATIONAL_ZERO;
Rational product = RATIONAL_ONE;
for ( const uint64_t& element : engel ) {
Rational rational = Rational(element).inverse();
product = product.multiply(rational);
sum = sum.add(product);
}
return sum;
}
int main() {
std::vector<std::string> rationals = { "3.14159265358979", "2.71828182845904", "1.414213562373095" };
for ( const std::string& rational : rationals ) {
std::vector<uint64_t> engel = to_engel(rational);
std::cout << "Rational number : " << rational << "\n";
std::cout << "Engel expansion : ";
for ( const uint64_t& element : engel ) {
std::cout << element << " ";
}
std::cout << "\n";
std::cout << "Number of terms : " << engel.size() << "\n";
// Due to integer overflow,
// C++ can only reconstruct the decimal numbers to a limted number of decimal places.
const uint64_t decimal_places = rational.length() - rational.find(".");
std::vector<uint64_t> reduced_engel = { engel.begin(), engel.begin() + 9 };
std::cout << "Back to rational: "
<< from_engel(reduced_engel).to_decimal(decimal_places / 2) << "\n";
std::cout << "\n";
}
}
- Output:
Rational number : 3.14159265358979 Engel expansion : 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Number of terms : 18 Back to rational: 3.1415926 Rational nuber : 2.71828182845904 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Number of terms : 27 Back to rational: 2.7182787 Rational number : 1.414213562373095 Engel expansion : 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 Number of terms : 17 Back to rational: 1.41421356
EasyLang
func ceil x .
f = floor x
if f = x
return f
.
return f + 1
.
func[] engelenc x .
while x > 0
ai = ceil (1 / x)
x = x * ai - 1
a[] &= ai
.
return a[]
.
func engeldec a[] .
p = 1
for ai in a[]
p *= ai
x = x + 1 / p
.
return x
.
numfmt 11 0
a[] = engelenc 3.14159265358979
print a[]
print engeldec a[]
- Output:
[ 1 1 1 8 8 17 19 300 1991 2767 8641 16313 1628438 7702318 25297938 431350188 765676622 776491263 1739733589 2329473788 6871947674 17179869184 ] 3.14159265359
FreeBASIC
#define ceil(x) (-((-x*2.0-0.5) Shr 1))
Type BigRational
As Double numerador
As Double denominador
End Type
Function BigRationalNew(n As Double, d As Double) As BigRational
Dim As BigRational result = Type(n, d)
Return result
End Function
Function BigRationalAdd(a As BigRational, b As BigRational) As BigRational
Return BigRationalNew(a.numerador * b.denominador + b.numerador * a.denominador, _
a.denominador * b.denominador)
End Function
Function BigRationalMultiply(a As BigRational, b As BigRational) As BigRational
Return BigRationalNew(a.numerador * b.numerador, a.denominador * b.denominador)
End Function
Function BigRationalSubtract(a As BigRational, b As BigRational) As BigRational
Return BigRationalNew(a.numerador * b.denominador - b.numerador * a.denominador, _
a.denominador * b.denominador)
End Function
Function BigRationalInverse(a As BigRational) As BigRational
Return BigRationalNew(a.denominador, a.numerador)
End Function
Function BigRationalCeiling(a As BigRational) As Long
Return Clng(Ceil(a.numerador / a.denominador))
End Function
Function BigRationalToString(a As BigRational) As String
Dim As Double value = a.numerador / a.denominador
Dim As String result = ""
' Get integer part using proper rounding
Dim As Double intPart = Fix(Abs(value))
' Get decimal part with high precision
Dim As Double decPart = Abs(value) - intPart
' Convert integer part
result = Str(Int(intPart))
' Add decimal places with controlled precision
If decPart > 0 Then
result &= "."
For i As Integer = 1 To 15
decPart *= 10
Dim As Integer digit = Int(decPart)
result &= Trim(Str(digit))
decPart -= digit
' Stop if we've reached sufficient precision
If decPart < 1e-15 Then Exit For
Next
End If
' Add negative sign if needed
If value < 0 Then result = "-" & result
' Clean up the result
result = Ltrim(result)
' Remove trailing zeros
While Right(result, 1) = "0" Andalso Instr(result, ".") > 0
result = Left(result, Len(result) - 1)
Wend
' Remove trailing decimal point if needed
If Right(result, 1) = "." Then result = Left(result, Len(result) - 1)
Return result
End Function
Sub toEngel(rat As BigRational, engel() As Long)
Dim As Integer i = 0
Dim As BigRational temp = rat
While temp.numerador > 1e-10 Andalso i < Ubound(engel)
Dim As Long a = BigRationalCeiling(BigRationalInverse(temp))
engel(i) = a
temp = BigRationalSubtract(BigRationalMultiply(temp, BigRationalNew(a, 1)), BigRationalNew(1, 1))
i += 1
Wend
If i < Ubound(engel) Then engel(i) = 0
End Sub
Function fromEngel(engel() As Long) As BigRational
Dim As BigRational sum = BigRationalNew(0, 1)
Dim As Double scale = 1.0
Dim As Integer i = 0
While engel(i) <> 0 Andalso i <= Ubound(engel)
scale /= engel(i)
sum.numerador += scale * sum.denominador
sum.denominador = 1
i += 1
Wend
Return sum
End Function
' Main program
Dim As BigRational rationals(3)
rationals(0) = BigRationalNew(314159265358979, 1e14) ' PI
rationals(1) = BigRationalNew(271828182845904, 1e14) ' e
rationals(2) = BigRationalNew(1414213562373095, 1e15) ' Sqrt(2)
rationals(3) = BigRationalNew(2562890625, 1e8) ' 1.5^8
For i As Integer = 0 To Ubound(rationals)
Dim As Long engel(100)
Print "Rational number : "; BigRationalToString(rationals(i))
toEngel(rationals(i), engel())
Print "Engel expansion :";
Dim As Integer terms = 0
While engel(terms) <> 0 Andalso terms < 30
Print engel(terms);
terms += 1
Wend
Print
Print "Number of terms :"; terms
Print "Back to rational: "; BigRationalToString(fromEngel(engel()))
Print
Next
Sleep
- Output:
Rational number : 3.14159265358979 Engel expansion : 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Number of terms : 18 Back to rational: 3.14159265358979 Rational number : 2.71828182845904 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Number of terms : 27 Back to rational: 2.71828182845904 Rational number : 1.414213562373094 Engel expansion : 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 Number of terms : 17 Back to rational: 1.414213562373095 Rational number : 25.62890625 Engel expansion : 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 32 Number of terms : 28 Back to rational: 25.62890625
J
to_engle=: {{>.@% ({.~ i.&0)<:@(* >.@%)^:(i.30) y}}
from_engle=: {{+/%*/\y}}
Task examples:
to_engle 3.14159265358979
1 1 1 8 8 17 19 300 1991 2767 8641 16313 1628438 7702318 25297938 431350188 765676622 776491263 1739733589 2329473788 6871947674 17179869184
from_engle to_engle 3.14159265358979
3.14159
3.14159265358979-from_engle to_engle 3.14159265358979
0
to_engle 2.71828182845904
1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 60 89 126 565 686 1293 7419 13529 59245 65443 133166 225384 655321 656924
from_engle to_engle 2.71828182845904
2.71828
2.71828182845904-from_engle to_engle 2.71828182845904
0
to_engle 1.414213562373095
1 3 5 5 16 18 78 102 120 144 277 286 740 38370 118617 120453 169594 5696244 6316129 10129640 67108864
from_engle to_engle 1.414213562373095
1.41421
1.414213562373095-from_engle to_engle 1.414213562373095
0
(by default, J displays the first six digits of floating point numbers)
Stretch goal (note that we seem to have a problem here with e, presumably because of the limited length of the series):
pi175=: (%10x^175)*<.@o.10x^175
e101=: +/ %@!@i. 101x
sq2_179=: (10x^179)%~<.@%:2*10x^2*179
177j175":pi175
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211
103j101":e101
2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743
180j178":sq2_179
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558
to_engle pi175
1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2366457522 4109464489 21846713216 27803071890 31804388758
to_engle e101
1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
to_engle sq2_179
1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413035 5345718057 27843871197 54516286513 334398528974 445879679626 495957494386 2450869042061 2629541150828 3557465729164
0.0+pi175-from_engle to_engle pi175
8.21074e_177
0.0+e101-from_engle to_engle e101
1.25532e_34
0.0+sq2_179-from_engle to_engle sq2_179
9.66281e_196
Java
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
public final class EngelExpansion {
public static void main(String[] args) {
List<String> rationals = List.of( "3.14159265358979", "2.71828182845904", "1.414213562373095",
"3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034"
+ "8253421170679821480865132823066470938446095505822317253594081284811174502841027019385211",
"2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382"
+ "17852516642743",
"1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534"
+ "3276415727350138462309122970249248360558507372126441214970999358314132226659275055927558" );
for ( String rational : rationals ) {
List<BigInteger> engel = toEngel(rational);
System.out.println("Rational number : " + rational);
System.out.println("Engel expansion : " + engel.stream().limit(30).toList());
System.out.println("Number of terms : " + engel.size());
final int decimalPlaces = rational.length() - rational.indexOf(".") - 1;
System.out.println("Back to rational: " +
fromEngel(engel.stream().limit(70).toList()).toDecimal(decimalPlaces));
System.out.println();
}
}
private static List<BigInteger> toEngel(String decimal) {
List<BigInteger> engel = new ArrayList<BigInteger>();
BigRational rational = new BigRational(decimal);
while ( ! rational.equals(BigRational.ZERO) ) {
BigInteger term = rational.inverse().ceiling();
engel.addLast(term);
rational = rational.multiply( new BigRational(term) ).subtract(BigRational.ONE);
}
return engel;
}
private static BigRational fromEngel(List<BigInteger> engel) {
BigRational sum = BigRational.ZERO;
BigRational product = BigRational.ONE;
for ( BigInteger element : engel ) {
BigRational rational = new BigRational(element).inverse();
product = product.multiply(rational);
sum = sum.add(product);
}
return sum;
}
}
final class BigRational {
public BigRational(BigInteger aNumerator, BigInteger aDenominator) {
numerator = aNumerator;
denominator = aDenominator;
BigInteger gcd = numerator.gcd(denominator);
numerator = numerator.divide(gcd);
denominator = denominator.divide(gcd);
}
public BigRational(BigInteger value) {
numerator = value;
denominator = BigInteger.ONE;
}
public BigRational(String decimal) {
final int index = decimal.indexOf(".");
final int decimalPlaces = decimal.length() - index - 1;
BigInteger numer = new BigInteger(decimal.substring(0, index) + decimal.substring(index + 1));
BigInteger denom = BigInteger.TEN.pow(decimalPlaces);
this(numer, denom);
}
public String toDecimal(int decimalPlaces) {
BigDecimal numer = new BigDecimal(numerator);
BigDecimal denom = new BigDecimal(denominator);
return numer.divide(denom, new MathContext(decimalPlaces + 1, RoundingMode.HALF_UP)).toString();
}
public boolean equals(BigRational other) {
return numerator.equals(other.numerator) && denominator.equals(other.denominator);
}
public BigRational add(BigRational other) {
BigInteger numer = numerator.multiply(other.denominator).add(denominator.multiply(other.numerator));
BigInteger denom = denominator.multiply(other.denominator);
return new BigRational(numer, denom);
}
public BigRational subtract(BigRational other) {
BigInteger numer =
numerator.multiply(other.denominator).subtract(denominator.multiply(other.numerator));
BigInteger denom = denominator.multiply(other.denominator);
return new BigRational(numer, denom);
}
public BigRational multiply(BigRational other) {
return new BigRational(numerator.multiply(other.numerator), denominator.multiply(other.denominator));
}
public BigRational inverse() {
return new BigRational(denominator, numerator);
}
public BigInteger ceiling() {
BigInteger[] pair = numerator.divideAndRemainder(denominator);
return pair[1].equals(BigInteger.ZERO) ? pair[0] : pair[0].add(BigInteger.ONE);
}
public static final BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
public static final BigRational ONE = new BigRational(BigInteger.ONE, BigInteger.ONE);
private BigInteger numerator;
private BigInteger denominator;
}
- Output:
Rational number : 3.14159265358979 Engel expansion : [1, 1, 1, 8, 8, 17, 19, 300, 1991, 2768, 4442, 4830, 10560, 37132, 107315, 244141, 651042, 1953125] Number of terms : 18 Back to rational: 3.14159265358979 Rational number : 2.71828182845904 Engel expansion : [1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 82, 144, 321, 2289, 9041, 21083, 474060, 887785, 976563, 1953125] Number of terms : 27 Back to rational: 2.71828182845904 Rational number : 1.414213562373095 Engel expansion : [1, 3, 5, 5, 16, 18, 78, 102, 120, 144, 260, 968, 18531, 46065, 63005, 65105, 78125] Number of terms : 17 Back to rational: 1.414213562373095 Rational number : 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion : [1, 1, 1, 8, 8, 17, 19, 300, 1991, 2492, 7236, 10586, 34588, 63403, 70637, 1236467, 5417668, 5515697, 5633167, 7458122, 9637848, 9805775, 41840855, 58408380, 213130873, 424342175, 2366457522, 4109464489, 21846713216, 27803071890] Number of terms : 231 Back to rational: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Rational number : 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion : [1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] Number of terms : 150 Back to rational: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Rational number : 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion : [1, 3, 5, 5, 16, 18, 78, 102, 120, 144, 251, 363, 1402, 31169, 88630, 184655, 259252, 298770, 4196070, 38538874, 616984563, 1975413035, 5345718057, 27843871197, 54516286513, 334398528974, 445879679626, 495957494386, 2450869042061, 2629541150527] Number of terms : 185 Back to rational: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558
jq
Adapted from Wren
Works with gojq, the Go implementation of jq
The following program assumes infinite-precision integer arithmetic as provided by gojq.
Note that the program depends on the jq module rational.jq, which provides functionality for rational arithmetic, where a rational number is represented by the JSON object {n,d} specifying the numerator and denominator.
include "rational" {search: "."}; # see above
# Convert a decimal to a Rational
# Input: a number or numeric string that is NOT expressed in scientific notation
def dec2r:
def ds2r:
if test("^-") then .[1:] | ds2r | rminus
elif test("-") then error
elif test("e|E") then "dec2r does not yet support scientific notation" | error
elif index(".")
then capture("(?<i>[0-9]*)[.](?<j>[0-9]*)")
| (.j | length) as $j
| r( (.i + .j) | tonumber; 10 | power($j) )
else r(tonumber; 1)
end;
if type == "number"
then if . == 0 then r(0;1)
elif . < 0 then -. | dec2r | rminus
else tostring | ds2r
end
else ds2r
end;
# Convert a Rational to a simple decimal string, at least in common
# cases of interest for this task
def r2dec:
def lpad($len): tostring | ($len - length) as $l | ("0" * $l)[:$l] + .;
def rpad: if length == 0 then "0" else . end;
# $n is the number of decimal places
def d($n):
tostring as $s
| if $n == 0 then $s + ".0"
else ($s[-$n:] | lpad($n) | rpad) as $right
| ($s[: ($s|length) - $n] | rpad) as $left
| $left + "." + $right
end;
. as $in
| (.d | tostring)
| length as $dlength
| if test("^10*$") then $in.n | d($dlength - 1)
elif test("^50*$") then (2 * $in.n) | d($dlength)
elif test("^20*$") then (5 * $in.n) | d($dlength)
elif test("^320*$") then (3125 * $in.n) | d( $dlength + 3) # 100,000
elif test("^6250*$") then (16 * $in.n) | d( $dlength + 1) # 10,000
else $in
end;
# itrunc/0 attempts to compute the "trunc" of the input number in such
# a way that gojq will recognize the result as an integer, while
# leveraging the support that both the C and Go implementations have
# for integer literals.
# It is mainly intended for numbers for which the tostring value does NOT contain an exponent.
# The details are complicated because the goal is to provide portability between jq implementations.
#
# Input: a number or a string matching ^[0-9]+([.][0-9]*)$
# Output: as specified by the implementation.
def itrunc:
if type == "number" and . < 0 then - ((-.) | itrunc)
else . as $in
| tostring as $s
| ($s | test("[Ee]")) as $exp
| ($s | test("[.]")) as $decimal
| if ($exp|not) and ($decimal|not) then $s | tonumber # do not simply return $in
elif ($exp|not) and $decimal then $s | sub("[.].*";"") | tonumber
else trunc
| tostring
| if test("[Ee]")
then if $exp then "itrunc cannot be used on \($in)" | error end
else tonumber
end
end
end;
# rtrunc is like trunc but for Rational numbers, though the input may be
# either a number or a Rational.
def rtrunc:
if type == "number" then r(itrunc;1)
elif 0 == .n or (0 < .n and .n < .d) then r(0;1)
elif 0 < .n or (.n % .d == 0) then .d as $d | r(.n | idivide($d) ; 1)
else rminus( r( - .n; .d) | rtrunc | rminus; 1)
end;
# For present purposes it is sufficient to assume .n >= 0
def rceil:
if .n == .d or .d == 1 then .
else radd(.; r(1;1))
| rtrunc
end;
### Engel expansion
# input: a rational expressed as an ordinary decimal number or a numeric string
# (Scientific notation cannot be used.)
def toEngel:
. as $x
| {u: dec2r,
engel: [] }
| until(.u.n == 0;
(.u | rinv | rceil | .n) as $a
| .engel += [ $a ]
| .u = rminus( rmult(.u ; r($a;1)) ; r(1;1)) )
| .engel;
def fromEngel:
foreach (.[], null) as $e (
{sum: r(0;1),
prod: r(1;1)};
if $e == null then .emit = .sum
else .prod = rmult(.prod; r(1; $e))
| .sum = radd(.sum; .prod)
end )
| select(.emit).emit
| r2dec;
# Input: a rational in the form of a simple decimal such as 3.1 or "3.1"
def task:
"Rational number : \(.)",
( index(".") + 1) as $dix
| (length - $dix) as $places
| toEngel as $engel
| "Engel expansion : \($engel[:30])",
"Number of terms : \($engel|length)",
"Back to rational: \($engel | fromEngel)\n"
;
def rats: [
"3.14159265358979",
"2.71828182845904",
"1.414213562373095",
"7.59375",
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211",
"2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743",
"1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558",
"25.628906"
];
rats[] | task
- Output:
Rational number : 3.14159265358979 Engel expansion : [1,1,1,8,8,17,19,300,1991,2768,4442,4830,10560,37132,107315,244141,651042,1953125] Number of terms : 18 Back to rational: 3.14159265358979 Rational number : 2.71828182845904 Engel expansion : [1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,82,144,321,2289,9041,21083,474060,887785,976563,1953125] Number of terms : 27 Back to rational: 2.71828182845904 Rational number : 1.414213562373095 Engel expansion : [1,3,5,5,16,18,78,102,120,144,260,968,18531,46065,63005,65105,78125] Number of terms : 17 Back to rational: 1.414213562373095 Rational number : 7.59375 Engel expansion : [1,1,1,1,1,1,1,2,6,8] Number of terms : 10 Back to rational: 7.59375 Rational number : 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion : [1,1,1,8,8,17,19,300,1991,2492,7236,10586,34588,63403,70637,1236467,5417668,5515697,5633167,7458122,9637848,9805775,41840855,58408380,213130873,424342175,2366457522,4109464489,21846713216,27803071890] Number of terms : 231 Back to rational: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Rational number : 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion : [1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29] Number of terms : 150 Back to rational: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Rational number : 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion : [1,3,5,5,16,18,78,102,120,144,251,363,1402,31169,88630,184655,259252,298770,4196070,38538874,616984563,1975413035,5345718057,27843871197,54516286513,334398528974,445879679626,495957494386,2450869042061,2629541150527] Number of terms : 185 Back to rational: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Rational number : 25.628906 Engel expansion : [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,4,33,33,35] Number of terms : 34 Back to rational: 25.628906
Julia
tobigrational(s) = (d = length(s) - something(findfirst(==('.'), s), 0); parse(BigInt, replace(s, '.' => "")) // big"10"^d)
toEngel(x) = (a = BigInt[]; while x != 0; y = ceil(big"1" // x); push!(a, y); x = x * y - 1; end; a)
fromEngel(a) = sum(accumulate((x, y) -> x // y, BigInt.(a)))
function testEngels(s)
biginput = length(s) > 21
r = tobigrational(s)
println("\nNumber: $s")
eng = toEngel(r)
println("Engel expansion: ", biginput ? eng[1:min(length(s), 30)] : Int64.(eng), " ($(length(eng)) components)")
r2 = fromEngel(eng)
println("Back to rational: ", biginput ? BigFloat(r2) : Float64(r2))
end
setprecision(700)
foreach(testEngels, [
"3.14159265358979",
"2.71828182845904",
"1.414213562373095",
"7.59375",
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211",
"2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743",
"1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558",
"25.628906",
])
- Output:
Number: 3.14159265358979 Engel expansion: [1, 1, 1, 8, 8, 17, 19, 300, 1991, 2768, 4442, 4830, 10560, 37132, 107315, 244141, 651042, 1953125] (18 components) Back to rational: 3.14159265358979 Number: 2.71828182845904 Engel expansion: [1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 82, 144, 321, 2289, 9041, 21083, 474060, 887785, 976563, 1953125] (27 components) Back to rational: 2.71828182845904 Number: 1.414213562373095 Engel expansion: [1, 3, 5, 5, 16, 18, 78, 102, 120, 144, 260, 968, 18531, 46065, 63005, 65105, 78125] (17 components) Back to rational: 1.414213562373095 Number: 7.59375 Engel expansion: [1, 1, 1, 1, 1, 1, 1, 2, 6, 8] (10 components) Back to rational: 7.59375 Number: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion: BigInt[1, 1, 1, 8, 8, 17, 19, 300, 1991, 2492, 7236, 10586, 34588, 63403, 70637, 1236467, 5417668, 5515697, 5633167, 7458122, 9637848, 9805775, 41840855, 58408380, 213130873, 424342175, 2366457522, 4109464489, 21846713216, 27803071890] (231 components) Back to rational: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211000000000000000000000000000000000001 Number: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion: BigInt[1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] (150 components) Back to rational: 2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003 Number: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion: BigInt[1, 3, 5, 5, 16, 18, 78, 102, 120, 144, 251, 363, 1402, 31169, 88630, 184655, 259252, 298770, 4196070, 38538874, 616984563, 1975413035, 5345718057, 27843871197, 54516286513, 334398528974, 445879679626, 495957494386, 2450869042061, 2629541150527] (185 components) Back to rational: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558000000000000000000000000000000001 Number: 25.628906 Engel expansion: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 33, 33, 35, 58, 62, 521, 3125] (34 components) Back to rational: 25.628906
Maxima
engel_encode(x) := block (
[a:[]],
while(x > 0) do (
ai: ceiling(1/x),
x: x*ai - 1,
a: append(a, [ai])
),
return(a)
);
engel_decode(a) := block (
[x:0, my_product:1],
for ai in a do (
my_product: my_product*ai,
x: x + 1/(my_product)
),
return(x)
);
- Output:
engel_encode(3.14159265358979); [1,1,1,8,8,17,19,300,1991,2767,8641,16313,1628438,7702318,25297938,431350188,765676622,776491263,1739733589,2329473788,6871947674,17179869184] engel_decode(%); 7074237752028433/2251799813685248 engel_encode(2.71828182845904); [1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,60,89,126,565,686,1293,7419,13529,59245,65443,133166,225384,655321,656924,2365071,2618883,5212339,107374183,178956971,536870912] engel_decode(%); 3060513257434031/1125899906842624 engel_encode(1.414213562373095); [1,3,5,5,16,18,78,102,120,144,277,286,740,38370,118617,120453,169594,5696244,6316129,10129640,67108864] engel_decode(%); 1592262918131443/1125899906842624
Nim
Task
We use the module “rationals” from the standard library which is limited to int64
numerators and denominators. We had to define a conversion function from string to Rational as using the provided conversion function from float to Rational gave inaccurate results.
import std/[math, rationals, strutils]
type Fract = Rational[int64]
func engel(x: Fract): seq[Natural] =
## Return the Engel expansion of rational "x".
var u = x
while u.num != 0:
let a = ceil(u.den.float / u.num.float).toInt
result.add a
u = u * a - 1
func toRational(s: string): Fract =
## Convert the string representation of a real to a rational
## without using an intermediate float representation.
var num = 0i64
var den = 1i64
var i = 0
var c = s[0]
while c != '.':
num = 10 * num + ord(c) - ord('0')
inc i
c = s[i]
inc i
while i < s.len:
num = 10 * num + ord(s[i]) - ord('0')
den *= 10
inc i
result = num // den
for val in ["3.14159265358979", "2.71828182845904", "1.414213562373095"]:
let e = engel(val.toRational)
echo "Value: ", val
echo "Engel expansion: ", e.join(" ")
echo()
- Output:
Value: 3.14159265358979 Engel expansion: 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Value: 2.71828182845904 Engel expansion: 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Value: 1.414213562373095 Engel expansion: 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125
Stretch task
The package “bignum” provides a “Rat” type but lacks a function to convert the string representing a real number to a Rat
.
import std/strutils
import bignum
func engel(x: Rat): seq[Int] =
## Return the Engel expansion of rational "x".
var u = x
while u.num != 0:
let a = (u.denom + u.num - 1) div u.num
result.add a
u = u * a - 1
func toRat(s: string): Rat =
## Convert the string representation of a real to a rational.
var num = newInt(0)
var den = newInt(1)
var i = 0
var c = s[0]
while c != '.':
num = 10 * num + ord(c) - ord('0')
inc i
c = s[i]
inc i
while i < s.len:
num = 10 * num + ord(s[i]) - ord('0')
den *= 10
inc i
result = newRat(num, den)
for val in ["3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211",
"2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743",
"1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558"]:
let e = engel(val.toRat)
echo "Value: ", val
echo "Engel expansion: ", e[0..29].join(" ")
echo "Number of terms: ", e.len
echo()
- Output:
Value: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion: 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2366457522 4109464489 21846713216 27803071890 Number of terms: 231 Value: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion: 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Number of terms: 150 Value: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion: 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413035 5345718057 27843871197 54516286513 334398528974 445879679626 495957494386 2450869042061 2629541150527 Number of terms: 185
PascalABC.NET
uses numlibabc;
function engel(x: fraction): sequence of biginteger;
begin
result := new list<biginteger>;
while x.numerator <> 0 do
begin
var a := (x.denominator + x.numerator - 1) div x.numerator;
result := result + a;
x := x * a - 1;
end;
end;
function tofraction(s: string): fraction;
begin
var period := s.IndexOf('.');
result := Frc(s.Remove(period, 1).ToBigInteger, Power(10bi, s.Length - period - 1));
end;
function fromEngel(engel: sequence of biginteger): decimal;
begin
var sum: decimal := 0;
var prod: decimal := 1;
foreach var e in engel do
begin
var r: decimal := decimal(1 / e);
prod := prod * r;
sum := sum + prod;
end;
result := sum;
end;
begin
foreach var val in |'3.14159265358979', '2.71828182845904', '1.414213562373095', '7.59375',
'3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211',
'2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743',
'1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558'| do
begin
var e := engel(tofraction(val));
println('Value: ', val);
println('Engel expansion: ', e.Take(30));
println('Number of terms: ', e.count);
println('Back to rational:', fromengel(e));
println;
end;
end.
- Output:
Value: 3.14159265358979 Engel expansion: [1,1,1,8,8,17,19,300,1991,2768,4442,4830,10560,37132,107315,244141,651042,1953125] Number of terms: 18 Back to rational: 3.1415926535897899998836589242 Value: 2.71828182845904 Engel expansion: [1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,82,144,321,2289,9041,21083,474060,887785,976563,1953125] Number of terms: 27 Back to rational: 2.7182818284590397851717057197 Value: 1.414213562373095 Engel expansion: [1,3,5,5,16,18,78,102,120,144,260,968,18531,46065,63005,65105,78125] Number of terms: 17 Back to rational: 1.4142135623730945858229945294 Value: 7.59375 Engel expansion: [1,1,1,1,1,1,1,2,6,8] Number of terms: 10 Back to rational: 7.5937500000000001875 Value: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion: [1,1,1,8,8,17,19,300,1991,2492,7236,10586,34588,63403,70637,1236467,5417668,5515697,5633167,7458122,9637848,9805775,41840855,58408380,213130873,424342175,2366457522,4109464489,21846713216,27803071890] Number of terms: 231 Back to rational: 3.1415926535897932383463023076 Value: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion: [1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29] Number of terms: 150 Back to rational: 2.7182818284590450205319931911 Value: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion: [1,3,5,5,16,18,78,102,120,144,251,363,1402,31169,88630,184655,259252,298770,4196070,38538874,616984563,1975413035,5345718057,27843871197,54516286513,334398528974,445879679626,495957494386,2450869042061,2629541150527] Number of terms: 185 Back to rational: 1.4142135623730946346246832537
Perl
use v5.36;
use bigrat;
use experimental <builtin for_list>;
use List::Util <min product>;
sub ceiling ($n) { $n == int $n ? $n : int $n + 1 }
sub abbr ($d) { my $l = length $d; $l < 61 ? $d : substr($d,0,30) . '..' . substr($d,-30) . " ($l digits)" }
sub to_engel ($rat) {
my @E;
while ($rat) {
push @E, ceiling 1/$rat;
$rat = $rat*$E[-1] - 1;
}
@E
}
sub from_engel (@expanded) {
my @a;
sum( map { push @a, $_; 1/product(@a) } @expanded )
}
for my($rat,$p) (
# low precision 𝜋, 𝑒, √2 and 1.5 to powers
3.14159265358979, 15,
2.71828182845904, 15,
1.414213562373095, 16,
1.5**5, 6,
1.5**8, 10,
# high precision 𝜋, 𝑒, and √2
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211, 176,
2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743, 102,
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558, 179,
) {
say "Rational number: " . abbr $rat->as_float($p);
my $terms = join ' ', my @expanded = to_engel $rat;
say "Engel expansion: " . (length($terms) > 100 ? $terms =~ s/^(.{90}\S*).*$/$1/r . '... (' . +@expanded . ' terms)' : $terms);
say " Converted back: " . abbr from_engel(@expanded)->as_float($p);
say '';
}
- Output:
Rational number: 3.14159265358979 Engel expansion: 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Converted back: 3.14159265358979 Rational number: 2.71828182845904 Engel expansion: 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Converted back: 2.71828182845904 Rational number: 1.414213562373095 Engel expansion: 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 Converted back: 1.414213562373095 Rational number: 7.59375 Engel expansion: 1 1 1 1 1 1 1 2 6 8 Converted back: 7.59375 Rational number: 25.62890625 Engel expansion: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 32 Converted back: 25.62890625 Rational number: 3.1415926535897932384626433832..081284811174502841027019385211 (177 digits) Engel expansion: 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167... (231 terms) Converted back: 3.1415926535897932384626433832..081284811174502841027019385211 (177 digits) Rational number: 2.7182818284590452353602874713..035354759457138217852516642743 (103 digits) Engel expansion: 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33... (150 terms) Converted back: 2.7182818284590452353602874713..035354759457138217852516642743 (103 digits) Rational number: 1.4142135623730950488016887242..999358314132226659275055927558 (180 digits) Engel expansion: 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874... (185 terms) Converted back: 1.4142135623730950488016887242..999358314132226659275055927558 (180 digits)
Phix
with javascript_semantics include mpfr.e mpfr_set_default_precision(-134) -- see notes function toEngel(string x) sequence engel = {} mpfr u = mpfr_init(x), a = mpfr_init() while mpfr_cmp_si(u,0)!=0 do mpfr_si_div(a,1,u) mpfr_ceil(a,a) engel &= mpfr_get_si(a) mpfr_mul(u,u,a) mpfr_sub_si(u,u,1) end while return engel end function function fromEngel(sequence engel) mpfr res = mpfr_init(0), prod = mpfr_init(1), r = mpfr_init() for e in engel do mpfr_set_d(r,e) mpfr_si_div(r,1,r) mpfr_mul(prod,prod,r) mpfr_add(res,res,prod) end for return res end function constant rats = { "3.14159265358979", "2.71828182845904", "1.414213562373095", "7.59375", "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384", "2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743", "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387", "25.628906" } for rat in rats do printf(1,"Rational number : %s\n", rat) sequence engel = toEngel(rat) integer dix = find('.',rat), places = length(rat)-dix, l = length(engel), cp = 0 string s = mpfr_get_fixed(fromEngel(engel), places) for i=1 to places do dix += 1 if rat[dix]!=s[dix] then exit end if cp = i end for printf(1,"Engel expansion : %s\n", join(engel[1..min(l,30)]," ",fmt:="%d")) printf(1,"Number of terms : %d, places : %d (%d correct)\n", {l,places,cp}) printf(1,"Back to rational: %s\n\n", s) end for
- Output:
I could only get pi accurate to 125 decimal places and root2 to 87, so cut the input strings accordingly.
Increasing the precision helps but only up to a (relatively small) point, ie that 134 is needed, nowt greater helps at all.
You may or may not have better luck with completely rewriting this to use mpq (rationals).
In fact it works slightly better in a browser (which uses rationals behind the scenes) than on desktop/Phix, as shown below.
Rational number : 3.14159265358979 Engel expansion : 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 Number of terms : 83, places : 14 (14 correct) Back to rational: 3.14159265358979 Rational number : 2.71828182845904 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 2147483647 2147483647 2147483647 Number of terms : 101, places : 14 (14 correct) Back to rational: 2.71828182845904 Rational number : 1.414213562373095 Engel expansion : 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 Number of terms : 67, places : 15 (15 correct) Back to rational: 1.414213562373095 Rational number : 7.59375 Engel expansion : 1 1 1 1 1 1 1 2 6 8 Number of terms : 10, places : 5 (5 correct) Back to rational: 7.59375 Rational number : 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384 Engel expansion : 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2147483647 2147483647 2147483647 2147483647 Number of terms : 181, places : 125 (125 correct) Back to rational: 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384 Rational number : 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Number of terms : 222, places : 101 (101 correct) Back to rational: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Rational number : 1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387 Engel expansion : 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413038 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 2147483647 Number of terms : 175, places : 87 (87 correct) Back to rational: 1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387 Rational number : 25.628906 Engel expansion : 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 33 33 35 Number of terms : 65, places : 6 (6 correct) Back to rational: 25.628906
Output under p2js:
Rational number : 3.14159265358979 Engel expansion : 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Number of terms : 18, places : 14 (14 correct) Back to rational: 3.14159265358979 Rational number : 2.71828182845904 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Number of terms : 27, places : 14 (14 correct) Back to rational: 2.71828182845904 Rational number : 1.414213562373095 Engel expansion : 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 Number of terms : 17, places : 15 (15 correct) Back to rational: 1.414213562373095 Rational number : 7.59375 Engel expansion : 1 1 1 1 1 1 1 2 6 8 Number of terms : 10, places : 5 (5 correct) Back to rational: 7.59375 Rational number : 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384 Engel expansion : 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2717375531 323878055376 339280401894 386771504748 Number of terms : 161, places : 125 (125 correct) Back to rational: 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384 Rational number : 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Number of terms : 150, places : 101 (101 correct) Back to rational: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Rational number : 1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387 Engel expansion : 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413038 7855284583 34680535992 47012263568 82957997141 1709576125547 42630379527673 164312229775505 404736776022426 Number of terms : 110, places : 87 (87 correct) Back to rational: 1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387 Rational number : 25.628906 Engel expansion : 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 33 33 35 Number of terms : 34, places : 6 (6 correct) Back to rational: 25.628906
Quackery
Quackery uses bignum rationals and only generates approximations when the programmer deems it necessary, so loss of precision is not an issue.
[ $ "bigrat.qky" loadfile ] now!
[ /mod 0 != + ] is ceiling ( n/d --> n )
[ [] unrot
[ 2dup 1/v ceiling
dip rot
dup dip
[ join unrot ]
1 v* 1 1 v-
2dup v0= until ]
2drop ] is v->engel ( n/d --> [ )
[ 0 1 rot
1 1 rot
witheach
[ n->v v/
2swap 2over v+
2swap ]
2drop ] is engel->v ( [ --> n/d )
$ "3.14159265358979 2.71828182845904 1.414213562373095"
nest$
witheach
[ $->v drop
2dup 200 point$ echo$ cr
v->engel
dup witheach [ echo i if sp ] cr
engel->v
200 point$ echo$ cr
cr ]
$ "3.1415926535897932384626433832795028841971693993751058"
$ "209749445923078164062862089986280348253421170679821480" join
$ "865132823066470938446095505822317253594081284811174502" join
$ "841027019385211" join
nested
$ "2.7182818284590452353602874713526624977572470936999595"
$ "7496696762772407663035354759457138217852516642743" join
nested join
$ "1.4142135623730950488016887242096980785696718753769480"
$ "731766797379907324784621070388503875343276415727350138" join
$ "462309122970249248360558507372126441214970999358314132" join
$ "226659275055927558" join
nested join
witheach
[ $->v drop
2dup 200 point$ echo$ cr cr
v->engel
dup 30 split drop
witheach [ echo i if sp ]
say "... " cr cr
engel->v
200 point$ echo$ cr cr
cr ]
- Output:
3.14159265358979 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 3.14159265358979 2.71828182845904 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 2.71828182845904 1.414213562373095 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 1.414213562373095 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2366457522 4109464489 21846713216 27803071890... 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29... 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413035 5345718057 27843871197 54516286513 334398528974 445879679626 495957494386 2450869042061 2629541150527... 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558
Raku
sub to-engel ($rat is copy) { do while $rat { my $a = ceiling 1 / $rat; $rat = $rat × $a - 1; $a } }
sub from-engel (@expanded) { sum [\×] @expanded.map: { FatRat.new: 1, $_ } }
for # low precision 𝜋, 𝑒, √2 and 1.5 to a power
3.14159265358979, 2.71828182845904, 1.414213562373095, 1.5 ** 5,
# high precision 𝜋, 𝑒, and √2 and 1.5 to a power
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211.FatRat,
2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743.FatRat,
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558.FatRat,
1.5 ** 8
-> $rat {
say "Rational number: $rat";
my @expanded = $rat.&to-engel;
put "Engel expansion: " ~ @expanded.head(30);
say " Converted back: " ~ @expanded.&from-engel;
put '';
}
- Output:
Rational number: 3.14159265358979 Engel expansion: 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Converted back: 3.14159265358979 Rational number: 2.71828182845904 Engel expansion: 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Converted back: 2.71828182845904 Rational number: 1.414213562373095 Engel expansion: 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 Converted back: 1.414213562373095 Rational number: 7.59375 Engel expansion: 1 1 1 1 1 1 1 2 6 8 Converted back: 7.59375 Rational number: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion: 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2366457522 4109464489 21846713216 27803071890 Converted back: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Rational number: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion: 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Converted back: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Rational number: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion: 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413035 5345718057 27843871197 54516286513 334398528974 445879679626 495957494386 2450869042061 2629541150527 Converted back: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Rational number: 25.628906 Engel expansion: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 32 Converted back: 25.628906
Wren
As in the case of the Raku example, I've limited the display of the Engel expansion to a maximum of 30 terms though I've also shown the total number of terms.
However, I've also limited the number of terms accumulated by the 'fromEngel' function to 70 which is just enough to reproduce the high precision rationals in decimal notation. To accumulate all the terms in a reasonable time would require the use of Wren-gmp which I've tried to avoid so the solution will run under Wren-CLI.
import "./big" for BigRat
import "./fmt" for Fmt
var toEngel = Fn.new { |x|
var engel = []
var u = BigRat.fromDecimal(x)
while (true) {
var a = u.inverse.ceil
engel.add(a.toBigInt)
u = u * a - BigRat.one
if (u == 0) return engel
}
}
var fromEngel = Fn.new { |engel|
var sum = BigRat.zero
var prod = BigRat.one
for (e in engel) {
var r = BigRat.new(e).inverse
prod = prod * r
sum = sum + prod
}
return sum
}
var rats = [
"3.14159265358979", "2.71828182845904", "1.414213562373095", "7.59375",
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211",
"2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743",
"1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558",
"25.628906"
]
for (rat in rats) {
Fmt.print("Rational number : $s", rat)
var dix = rat.indexOf(".") + 1
var places = rat.count - dix
var engel = toEngel.call(rat)
Fmt.print("Engel expansion : $i", engel.take(30).toList)
Fmt.print("Number of terms : $d", engel.count)
Fmt.print("Back to rational: $s\n", fromEngel.call(engel.take(70).toList).toDecimal(places))
}
- Output:
Rational number : 3.14159265358979 Engel expansion : 1 1 1 8 8 17 19 300 1991 2768 4442 4830 10560 37132 107315 244141 651042 1953125 Number of terms : 18 Back to rational: 3.14159265358979 Rational number : 2.71828182845904 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 82 144 321 2289 9041 21083 474060 887785 976563 1953125 Number of terms : 27 Back to rational: 2.71828182845904 Rational number : 1.414213562373095 Engel expansion : 1 3 5 5 16 18 78 102 120 144 260 968 18531 46065 63005 65105 78125 Number of terms : 17 Back to rational: 1.414213562373095 Rational number : 7.59375 Engel expansion : 1 1 1 1 1 1 1 2 6 8 Number of terms : 10 Back to rational: 7.59375 Rational number : 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Engel expansion : 1 1 1 8 8 17 19 300 1991 2492 7236 10586 34588 63403 70637 1236467 5417668 5515697 5633167 7458122 9637848 9805775 41840855 58408380 213130873 424342175 2366457522 4109464489 21846713216 27803071890 Number of terms : 231 Back to rational: 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211 Rational number : 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Engel expansion : 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Number of terms : 150 Back to rational: 2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642743 Rational number : 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Engel expansion : 1 3 5 5 16 18 78 102 120 144 251 363 1402 31169 88630 184655 259252 298770 4196070 38538874 616984563 1975413035 5345718057 27843871197 54516286513 334398528974 445879679626 495957494386 2450869042061 2629541150527 Number of terms : 185 Back to rational: 1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927558 Rational number : 25.628906 Engel expansion : 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 33 33 35 Number of terms : 34 Back to rational: 25.628906