0.2.0 Fraction

This commit is contained in:
Rob Tillaart 2024-04-24 15:50:43 +02:00
parent 32d8399ace
commit 78ec633f1e
20 changed files with 847 additions and 179 deletions

View File

@ -6,11 +6,32 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.0] - 2024-04-22
- removed **Printable** interface, braking change
- improve quality of **fractionize()** search
- split of integer part before search improves precision.
- add support for arrays
- default value for constructor (0, 1)
- add **fraction_array.ino** + **fraction_sizeof.ino**
- add **toString()**
- add **isInteger()**
- update examples
- add **fraction_extensive.ino** test range and accuracy sketch
- add **fraction_sqrts.ino** test sketch
- add **fraction_fast.ino**, fast determination of fraction with 9900 as denominator.
- this is very fast, with an accuracy ~1e-4
- add **fraction_full_scan.ino** for a full scan search.
- optimized **FractionMediant.ino** determine fraction with mediant.
- add **fraction_setDenominator.ino** demo
- add **FactionPowers2.ino**, fast determination of fraction with powers of 2.- add examples including tests.
- update readme.md
----
## [0.1.16] - 2023-11-02
- update readme.md
- minor edits
## [0.1.15] - 2023-02-02
- update GitHub actions
- update license 2023

View File

@ -1,11 +1,12 @@
//
// FILE: FractionFindSum.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
// DATE: 13-feb-2015
// PURPOSE: demo fraction math.
// URL: https://github.com/RobTillaart/Fraction
//
// Find a sum of fractions that (within accuracy)
// adds up to a given fraction.
//
#include "fraction.h"
@ -39,7 +40,7 @@ void findSum(Fraction f)
{
Fraction z(0, 1);
Serial.print(f);
Serial.print(f.toString());
Serial.print(" =\t ");
for (long i = 1; i < 10000; i++)
{
@ -48,7 +49,7 @@ void findSum(Fraction f)
{
f -= g;
z += g;
Serial.print(g);
Serial.print(g.toString());
Serial.print(" + ");
}
if (f == Fraction(0, 1))
@ -57,7 +58,7 @@ void findSum(Fraction f)
}
}
Serial.print("\t => ");
Serial.println(z);
Serial.println(z.toString());
Serial.println();
}
@ -67,5 +68,4 @@ void loop()
}
// -- END OF FILE --
// -- END OF FILE --

View File

@ -2,10 +2,10 @@
// FILE: FractionMediant.ino
// AUTHOR: Rob Tillaart
// PURPOSE: Find fraction by binary search with mediant.
// DATE: 2020-04-21
// URL: https://github.com/RobTillaart/Fraction
//
// this method is not that fast but it shows a nice application for
// the mediant.
// This method is not fast but it shows an application for the mediant().
// Works for positive values only (for now).
#include "fraction.h"
@ -26,7 +26,7 @@ void setup()
Fraction x = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(x);
Serial.println(x.toString());
Serial.println(x.toDouble(), 10);
Serial.println();
@ -35,7 +35,7 @@ void setup()
Fraction y = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(y);
Serial.println(y.toString());
Serial.println(y.toDouble(), 10);
Serial.println();
@ -55,7 +55,7 @@ void loop()
Serial.println();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y);
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
@ -70,7 +70,7 @@ void loop()
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y);
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
@ -82,27 +82,24 @@ void loop()
Fraction fractionize(float f)
{
float acc = 1e-6;
float d1 = 0;
float d2 = 0;
float accuracy = 1e-6;
int i;
Fraction a(0, 1);
Fraction b(9999, 1);
Fraction q(f);
Fraction b(uint16_t(f) + 1, 1);
Fraction c;
for (int i = 0; i < 500; i++)
for (i = 0; i < 500; i++)
{
Fraction c = Fraction::mediant(a, b); // NOTE middle(a,b) is slower and worse!
if ( c.toDouble() < f) a = c;
c = Fraction::mediant(a, b); // NOTE middle(a, b) is slower and worse!
float t = c.toDouble();
if ( t < f) a = c;
else b = c;
d1 = abs(f - a.toDouble());
d2 = abs(f - b.toDouble());
if (d1 < acc && d2 < acc) break;
// check the last found value.
if (abs(f - t) < accuracy) break;
}
if (d1 < d2) return a;
return b;
Serial.println(i);
return c;
}
// -- END OF FILE --
// -- END OF FILE --

View File

@ -0,0 +1,108 @@
//
// FILE: FractionPower2.ino
// AUTHOR: Rob Tillaart
// PURPOSE: Find fraction with powers of 2.
// URL: https://github.com/RobTillaart/Fraction
//
// this method is very fast as it calculates a fraction in one step with an accuracy
// of 1/8192. Quality is limited due the limited range of denominators.
//
// a slow variant that adds powers of two is added to show some math.
#include "fraction.h"
uint32_t start, stop;
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.println();
Serial.println();
float f = PI;
start = micros();
Fraction x = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(x.toString());
Serial.println(x.toDouble(), 10);
Serial.println();
f = EULER;
start = micros();
Fraction y = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(y.toString());
Serial.println(y.toDouble(), 10);
Serial.println();
Serial.println("done...\n");
}
void loop()
{
float f = random(1000000) * 0.000001;
// reference
start = micros();
Fraction y(f);
stop = micros();
Serial.println();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
Serial.print(y.toDouble(), 10);
Serial.print("\t");
Serial.println(f - y.toDouble(), 10);
// mediant method.
start = micros();
y = fractionize(f);
stop = micros();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
Serial.print(y.toDouble(), 10);
Serial.print("\t");
Serial.println(f - y.toDouble(), 10);
}
Fraction fractionize(float f)
{
Fraction sum(round(f*8192), 8192);
return sum;
}
// Slow_fractionize works only for range 0..1
Fraction slow_fractionize(float f)
{
Fraction sum(0, 1);
Fraction b(1, 2);
for (int i = 0; i < 25; i++) // might need less
{
Fraction tmp = sum + b;
if (tmp.toFloat() < f) sum = tmp;
b /= 2;
}
return sum;
}
// -- END OF FILE --

View File

@ -0,0 +1,102 @@
//
// FILE: Fraction_fast.ino
// AUTHOR: Rob Tillaart
// PURPOSE: Calculate fraction with well chosen number.
// URL: https://github.com/RobTillaart/Fraction
//
// This method is very fast as it calculates a fraction in one step.
// It uses a default denominator of 9900 which is consists of several
// small prime factors, to improve the chance of simplifying.
// - very fast
// - constant accuracy in the order of 1e-4.
// - limited set of denominators after simplifying.
// P(9900) = {2, 2, 3, 3, 5, 5, 11}
//
// other explored options:
// P(19800) = {2, 2, 2, 3, 3, 5, 5, 11} (add factor 2 factor)
// P(10800) = {2, 2, 2, 2, 3, 3, 3, 5, 5}
// P(9240) = {2, 2, 2, 3, 5, 7, 11}
// P(13860) = {2, 2, 3, 3, 5, 7, 11}
// P(30030) = {2, 3, 5, 7, 11, 13}
#include "fraction.h"
uint32_t start, stop;
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.println();
Serial.println();
float f = PI;
start = micros();
Fraction x = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(x.toString());
Serial.println(x.toDouble(), 10);
Serial.println();
f = EULER;
start = micros();
Fraction y = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(y.toString());
Serial.println(y.toDouble(), 10);
Serial.println();
Serial.println("done...\n");
}
void loop()
{
float f = random(1000000) * 0.000001;
// reference
start = micros();
Fraction y(f);
stop = micros();
Serial.println();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
Serial.print(y.toDouble(), 10);
Serial.print("\t");
Serial.println(f - y.toDouble(), 10);
// mediant method.
start = micros();
y = fractionize(f);
stop = micros();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
Serial.print(y.toDouble(), 10);
Serial.print("\t");
Serial.println(f - y.toDouble(), 10);
}
Fraction fractionize(float f)
{
Fraction value(round(f * 9900), 9900);
return value;
}
// -- END OF FILE --

View File

@ -0,0 +1,106 @@
//
// FILE: Fraction_full_scan.ino
// AUTHOR: Rob Tillaart
// PURPOSE: Find fraction by full scan possible values.
// URL: https://github.com/RobTillaart/Fraction
//
// This method is very slow but it scans the whole range (in fact only half)
// for all possible fractions, resulting in very good approximations.
// Works for positive values only (for now).
//
// Takes up to 3 seconds per search on a 16 MHz UNO.
// Takes up to 12 milliseconds per search on a 240 MHz ESP32.
#include "fraction.h"
uint32_t start, stop;
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.println();
Serial.println();
float f = PI;
start = micros();
Fraction x = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(x.toString());
Serial.println(x.toDouble(), 10);
Serial.println();
f = EULER;
start = micros();
Fraction y = fractionize(f);
stop = micros();
Serial.println(stop - start);
Serial.println(y.toString());
Serial.println(y.toDouble(), 10);
Serial.println();
Serial.println("done...\n");
}
void loop()
{
float f = random(1000000) * 0.000001;
// reference
start = micros();
Fraction y(f);
stop = micros();
Serial.println();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
Serial.print(y.toDouble(), 10);
Serial.print("\t");
Serial.println(f - y.toDouble(), 10);
// mediant method.
start = micros();
y = fractionize(f);
stop = micros();
Serial.print(stop - start);
Serial.print("\t");
Serial.print(y.toString());
Serial.print("\t");
Serial.print(f, 10);
Serial.print("\t");
Serial.print(y.toDouble(), 10);
Serial.print("\t");
Serial.println(f - y.toDouble(), 10);
}
// very very slow but very good.
Fraction fractionize(float f)
{
int best = 1;
float smallest = 1.0;
// smaller denominators will all be detected as 5000..10000 are multiples.
for (int d = 5000; d < 10000; d++)
{
Fraction frac(f * d + 0.5, d);
float tmp = abs(f - frac.toFloat());
if ( tmp < smallest)
{
smallest = tmp;
best = d;
}
}
Fraction frac(round(f * best), best);
return frac;
}
// -- END OF FILE --

View File

@ -2,7 +2,6 @@
// FILE: fractionTest01.ino
// AUTHOR: Rob Tillaart
// PURPOSE: test sketch for fraction math
// DATE: 2015-01-25
// URL: https://github.com/RobTillaart/Fraction
@ -51,14 +50,14 @@ void test_constructor()
Serial.print("TIME: \t");
Serial.println(stop - start);
Serial.println(q);
Serial.println(a);
Serial.println(aa);
Serial.println(b);
Serial.println(n);
Serial.println(p);
Serial.println(pi);
Serial.println(e);
Serial.println(q.toString());
Serial.println(a.toString());
Serial.println(aa.toString());
Serial.println(b.toString());
Serial.println(n.toString());
Serial.println(p.toString());
Serial.println(pi.toString());
Serial.println(e.toString());
}
@ -76,7 +75,7 @@ void test_math()
stop = micros();
Serial.print("TIME +: \t");
Serial.println(stop - start);
Serial.println(a);
Serial.println(a.toString());
delay(10);
start = micros();
@ -84,7 +83,7 @@ void test_math()
stop = micros();
Serial.print("TIME -: \t");
Serial.println(stop - start);
Serial.println(a);
Serial.println(a.toString());
delay(10);
start = micros();
@ -92,7 +91,7 @@ void test_math()
stop = micros();
Serial.print("TIME *: \t");
Serial.println(stop - start);
Serial.println(a);
Serial.println(a.toString());
delay(10);
start = micros();
@ -100,7 +99,7 @@ void test_math()
stop = micros();
Serial.print("TIME /: \t");
Serial.println(stop - start);
Serial.println(a);
Serial.println(a.toString());
}
@ -151,7 +150,7 @@ void test_misc()
stop = micros();
Serial.print("TIME mediant(): \t");
Serial.println(stop - start);
Serial.println(a);
Serial.println(a.toString());
delay(10);
start = micros();
@ -159,7 +158,7 @@ void test_misc()
stop = micros();
Serial.print("TIME middle(): \t");
Serial.println(stop - start);
Serial.println(a);
Serial.println(a.toString());
}

View File

@ -0,0 +1,51 @@
//
// FILE: Fraction_setDenominator.ino
// AUTHOR: Rob Tillaart
// PURPOSE: Calculate fraction with well chosen number.
// URL: https://github.com/RobTillaart/Fraction
#include "fraction.h"
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.println();
Serial.println();
Fraction pi(PI);
Fraction appr = Fraction::setDenominator(pi, 32);
Serial.println(pi.toString());
Serial.println(pi.toDouble(), 10);
Serial.println(appr.toString());
Serial.println(appr.toDouble(), 10);
Serial.println();
Fraction ee(EULER);
appr = Fraction::setDenominator(ee, 32);
Serial.println(ee.toString());
Serial.println(ee.toDouble(), 10);
Serial.println(appr.toString());
Serial.println(appr.toDouble(), 10);
Serial.println();
Fraction tt(0.125);
appr = Fraction::setDenominator(tt, 32);
Serial.println(tt.toString());
Serial.println(tt.toDouble(), 10);
Serial.println(appr.toString());
Serial.println(appr.toDouble(), 10);
Serial.println();
Serial.println("done...\n");
}
void loop()
{
}
// -- END OF FILE --

View File

@ -2,9 +2,7 @@
// FILE: fractionExerciser.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo sketch for fraction math
// DATE: 2015-03-29
// URL: https://github.com/RobTillaart/Fraction
//
#include "fraction.h"
@ -88,14 +86,14 @@ int add(int n)
{
Fraction a(1 + random(9), 1 + random(9));
Fraction b(1 + random(9), 1 + random(9));
Serial.print(a);
Serial.print(a.toString());
Serial.print(" + ");
Serial.print(b);
Serial.print(b.toString());
Serial.print(" = ");
Fraction c = readFraction();
Serial.print(c);
Serial.print(c.toString());
Serial.print("\t\t");
Serial.println(a + b);
Serial.println((a + b).toString());
if (c == a + b ) count++;
}
@ -111,14 +109,14 @@ int sub(int n)
{
Fraction a(1 + random(9), 1 + random(9));
Fraction b(1 + random(9), 1 + random(9));
Serial.print(a);
Serial.print(a.toString());
Serial.print(" - ");
Serial.print(b);
Serial.print(b.toString());
Serial.print(" = ");
Fraction c = readFraction();
Serial.print(c);
Serial.print(c.toString());
Serial.print("\t\t");
Serial.println(a - b);
Serial.println((a - b).toString());
if (c == a - b ) count++;
}
@ -134,14 +132,14 @@ int mul(int n)
{
Fraction a(1 + random(9), 1 + random(9));
Fraction b(1 + random(9), 1 + random(9));
Serial.print(a);
Serial.print(a.toString());
Serial.print(" * ");
Serial.print(b);
Serial.print(b.toString());
Serial.print(" = ");
Fraction c = readFraction();
Serial.print(c);
Serial.print(c.toString());
Serial.print("\t\t");
Serial.println(a * b);
Serial.println((a * b).toString());
if (c == a * b ) count++;
}
@ -157,14 +155,14 @@ int div(int n)
{
Fraction a(1 + random(9), 1 + random(9));
Fraction b(1 + random(9), 1 + random(9));
Serial.print(a);
Serial.print(a.toString());
Serial.print(" / ");
Serial.print(b);
Serial.print(b.toString());
Serial.print(" = ");
Fraction c = readFraction();
Serial.print(c);
Serial.print(c.toString());
Serial.print("\t\t");
Serial.println(a / b);
Serial.println((a / b).toString());
if (c == a / b ) count++;
}
@ -180,9 +178,9 @@ int equ(int n)
{
Fraction a(1 + random(9), 1 + random(9));
Fraction b(1 + random(9), 1 + random(9));
Serial.print(a);
Serial.print(a.toString());
Serial.print("\t?\t");
Serial.print(b);
Serial.print(b.toString());
char c = choice();
Serial.print("\t");
@ -196,4 +194,4 @@ int equ(int n)
}
// -- END OF FILE --
// -- END OF FILE --

View File

@ -2,7 +2,6 @@
// FILE: fractionTest01.ino
// AUTHOR: Rob Tillaart
// PURPOSE: test sketch for fraction math
// DATE: 2015-01-25
// URL: https://github.com/RobTillaart/Fraction
@ -17,6 +16,7 @@ Fraction n(0, 5);
Fraction p(5, 1);
Fraction pi(PI);
Fraction e(EULER);
Fraction gr(1.6180339887498948482); // golden ratio
void setup()
@ -26,16 +26,17 @@ void setup()
Serial.println(FRACTION_LIB_VERSION);
Serial.println();
Serial.println(a);
Serial.println(aa);
Serial.println(b);
Serial.println(n);
Serial.println(p);
Serial.println(q);
Serial.println(pi);
Serial.println(e);
Serial.println(Fraction::middle(pi, e));
Serial.println(Fraction::mediant(pi, e));
Serial.println(a.toString());
Serial.println(aa.toString());
Serial.println(b.toString());
Serial.println(n.toString());
Serial.println(p.toString());
Serial.println(q.toString());
Serial.println(pi.toString());
Serial.println(e.toString());
Serial.println(Fraction::middle(pi, e).toString());
Serial.println(Fraction::mediant(pi, e).toString());
Serial.println(gr.toString());
Serial.println();
testPlus();
@ -60,12 +61,12 @@ void loop()
void testPlus()
{
Serial.println("testPlus");
Serial.println(a + b);
Serial.println((a + b).toString());
Fraction c = a + b;
Serial.println(c);
Serial.println(c.toString());
c = a;
c += b;
Serial.println(c);
Serial.println(c.toString());
Serial.println();
Serial.println();
}
@ -74,12 +75,12 @@ void testPlus()
void testMin()
{
Serial.println("testMin");
Serial.println(a - b);
Serial.println((a - b).toString());
Fraction c = a - b;
Serial.println(c);
Serial.println(c.toString());
c = a;
c -= b;
Serial.println(c);
Serial.println(c.toString());
Serial.println();
Serial.println();
}
@ -88,12 +89,12 @@ void testMin()
void testMul()
{
Serial.println("testMul");
Serial.println(a * b);
Serial.println((a * b).toString());
Fraction c = a * b;
Serial.println(c);
Serial.println(c.toString());
c = a;
c *= b;
Serial.println(c);
Serial.println(c.toString());
Serial.println();
Serial.println();
}
@ -102,12 +103,12 @@ void testMul()
void testDiv()
{
Serial.println("testDiv");
Serial.println(a / b);
Serial.println((a / b).toString());
Fraction c = a / b;
Serial.println(c);
Serial.println(c.toString());
c = a;
c /= b;
Serial.println(c);
Serial.println(c.toString());
Serial.println();
Serial.println();
}
@ -175,5 +176,4 @@ void testGE()
}
// -- END OF FILE --
// -- END OF FILE --

View File

@ -0,0 +1,45 @@
//
// FILE: fraction_array.ino
// AUTHOR: Rob Tillaart
// PURPOSE: test fraction memory
// URL: https://github.com/RobTillaart/Fraction
//
// (0.1.16) On AVR it uses 10 bytes per element, where 8 were expected.
// (0.2.0) solved this
#include "fraction.h"
Fraction arr[50];
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.print("FRACTION_LIB_VERSION: ");
Serial.println(FRACTION_LIB_VERSION);
Serial.println();
for ( int n = 0; n < 33; n++)
{
arr[n] = Fraction(n, 32);
Serial.print(n);
Serial.print("\t");
Serial.print(arr[n].toString());
Serial.print("\t");
Serial.print(arr[n].toFloat(), 7);
Serial.print("\t");
Serial.println(n / 32.0, 7);
}
Serial.println("\ndone...");
}
void loop()
{
}
// -- END OF FILE --

View File

@ -0,0 +1,81 @@
//
// FILE: fraction_extensive_test.ino
// AUTHOR: Rob Tillaart
// PURPOSE: reasonable extensive fraction test
// URL: https://github.com/RobTillaart/Fraction
// step size to test, typical 100000
// for 100.000 tests
// - AVR UNO 16 MHz takes ~500 seconds at 115200 baud
// - ESP32 240 MHz takes ~400 seconds at 115200 baud
// - ESP32 240 MHz takes ~85 seconds at 500000 baud
const uint32_t N = 100000;
#include "fraction.h"
uint32_t start, stop;
float maxError = 0;
uint32_t pos = 0;
void setup()
{
// NOTE BAUDRATE!
Serial.begin(115200);
Serial.print(__FILE__);
Serial.print("FRACTION_LIB_VERSION: ");
Serial.println(FRACTION_LIB_VERSION);
Serial.println();
delay(100);
Fraction pi(PI);
Serial.println(pi.toString());
delay(1000);
start = millis();
for (uint32_t n = 100; n <= N; n++)
{
float g = n * (1.0 / N);
Fraction frac( g );
float f = frac.toFloat();
// find the maxError so far.
float relError = abs(abs(f / g) - 1);
if (relError > maxError)
{
maxError = relError;
pos = n;
}
Serial.print(n);
Serial.print("\t");
Serial.print(g, 6);
Serial.print("\t");
Serial.print(f, 6);
Serial.print("\t");
Serial.print(100 * relError, 3); // as percentage
Serial.print("\t");
Serial.print(frac.toString());
Serial.println();
}
stop = millis();
Serial.println();
Serial.print("MILLIS: ");
Serial.println(stop - start);
Serial.print("MAXERR: ");
Serial.println(maxError);
Serial.print(" POS: ");
Serial.println(pos);
}
void loop()
{
}
// -- END OF FILE --

View File

@ -0,0 +1,41 @@
//
// FILE: fraction_sizeof.ino
// AUTHOR: Rob Tillaart
// PURPOSE: test fraction memory
// URL: https://github.com/RobTillaart/Fraction
//
// On AVR it uses 10 bytes per element, where 8 were expected.
//
#include "fraction.h"
Fraction a(0.42);
Fraction arr[20];
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.print("FRACTION_LIB_VERSION: ");
Serial.println(FRACTION_LIB_VERSION);
Serial.println();
arr[19] = a;
Serial.println(a.toFloat());
Serial.print("sizeof: \t");
Serial.println(sizeof(a));
Serial.println(arr[19].toFloat());
Serial.print("sizeof: \t");
Serial.println(sizeof(arr));
}
void loop()
{
}
// -- END OF FILE --

View File

@ -0,0 +1,49 @@
//
// FILE: fraction_sqrts.ino
// AUTHOR: Rob Tillaart
// PURPOSE: fraction version of first 10000 square roots
// URL: https://github.com/RobTillaart/Fraction
#include "fraction.h"
Fraction sqr;
void setup()
{
Serial.begin(115200);
Serial.print(__FILE__);
Serial.print("FRACTION_LIB_VERSION: ");
Serial.println(FRACTION_LIB_VERSION);
Serial.println();
for ( int n = 0; n <= 10000; n++)
{
Fraction sqr( sqrt(n));
float f = sqr.toFloat();
// test for relative error 1e-5
// if (abs((f * f / n) - 1) > 0.00001)
{
Serial.print(n);
Serial.print("\t");
Serial.print(sqr.toString());
Serial.print("\t\t");
Serial.print(sqrt(n), 7);
Serial.print("\t\t");
Serial.print(f, 7);
Serial.print("\t\t");
Serial.println(abs((f * f / n) - 1), 7);
}
}
Serial.println("\ndone...");
}
void loop()
{
}
// -- END OF FILE --

View File

@ -1,7 +1,7 @@
//
// FILE: fraction.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.1.16
// VERSION: 0.2.0
// PURPOSE: Arduino library to implement a Fraction data type
// URL: https://github.com/RobTillaart/Fraction
@ -35,7 +35,8 @@ void Fraction::split(float f)
// EULER = 1264/465; // 2.2e-6
// get robust for small values. (effectively zero)
if (abs(f) < 0.00001)
// as 1/10000 is smallest
if (abs(f) < 0.0001)
{
n = 0;
d = 1;
@ -47,29 +48,20 @@ void Fraction::split(float f)
d = 1;
return;
}
// Normalize to 0.0 ... 1.0
// handle sign
bool negative = f < 0;
if (negative) f = -f;
// TODO investigate different strategy:
// intpart = int32_t(f); // strip of the integer part.
// f = f - intpart; // determine remainder
// determine n, d
// n += intpart * d; // add integer part * denominator to fraction.
bool reciproke = f > 1;
if (reciproke) f = 1/f;
// strip of the integer part and process only remainder (0.0..1.0)
int32_t integerPart = int32_t(f);
f = f - integerPart;
fractionize(f);
simplify();
// denormalize
if (reciproke)
{
int32_t t = n;
n = d;
d = t;
}
// add integerPart again, but in units of denominator.
n += (integerPart * d);
if (negative)
{
n = -n;
@ -83,28 +75,6 @@ Fraction::Fraction(int32_t p, int32_t q) : n(p), d(q)
}
//////////////////////////////////////
//
// PRINTING
//
size_t Fraction::printTo(Print& p) const
{
size_t s = 0;
// TODO split of sign first
//
// vs 22/7 => 3_1/7
// if (n >= d)
// {
// s += p.print(n/d, DEC);
// s += p.print("_");
// }
// s += p.print(n%d, DEC);
s += p.print(n, DEC);
s += p.print('/');
s += p.print(d, DEC);
return s;
};
//////////////////////////////////////
//
@ -250,6 +220,11 @@ Fraction& Fraction::operator /= (const Fraction &c)
}
//////////////////////////////////////
//
// CONVERSION and PRINTING
//
double Fraction::toDouble()
{
return double(n) / d;
@ -262,6 +237,18 @@ float Fraction::toFloat()
}
String Fraction::toString()
{
String s = "(";
// if (n < 0) s += "-";
s += n;
s += "/";
s += d;
s += ")";
return s;
}
// fraction is proper if abs(fraction) < 1
bool Fraction::isProper()
{
@ -269,6 +256,13 @@ bool Fraction::isProper()
}
// fraction is proper if abs(fraction) < 1
bool Fraction::isInteger()
{
return (d == 1);
}
// visualize fraction as an angle in degrees
float Fraction::toAngle()
{
@ -363,7 +357,7 @@ void Fraction::simplify()
// in preventing overflow
while (q > 10000)
{
// rounding might need improvement
// rounding need improvement
p = (p + 5)/10;
q = (q + 5)/10;
x = gcd(p, q);
@ -380,9 +374,16 @@ void Fraction::simplify()
// fractionize() - finds the fraction representation of a float
// PRE: 0 <= f < 1.0
//
// minimalistic is fast and small
// minimalistic, fast and small accuracy ~1e-4
// void Fraction::fractionize(float val)
// {
// n = round(val * 9900);
// d = 9900;
// }
//
// check for a discussion found later
// check for a discussion found later (link is dead)
// - http://mathforum.org/library/drmath/view/51886.html
// - http://www.gamedev.net/topic/354209-how-do-i-convert-a-decimal-to-a-fraction-in-c/
//
@ -392,18 +393,24 @@ void Fraction::simplify()
// - http://mathforum.org/library/drmath/view/51886.html
// (100x) micros()=96048
// showed errors for very small values around 0
void Fraction::fractionize(float val)
{
// find nearest fraction
float Precision = 0.0000001;
// Fraction low(int(val * 9900), 9900); // "A" = 0/1
// Fraction high(int(val * 9240) + 1, 9240); // "B" = 1/1
Fraction low(0, 1); // "A" = 0/1
Fraction high(1, 1); // "B" = 1/1
// max 100 iterations
for (int i = 0; i < 100; ++i)
{
float testLow = low.d * val - low.n;
float testHigh = high.n - high.d * val;
if (testHigh < Precision * high.d)
break; // high is answer
break; // high is answer
if (testLow < Precision * low.d)
{ // low is answer
@ -416,7 +423,7 @@ void Fraction::fractionize(float val)
int32_t count = (int32_t)test; // "N"
int32_t n = (count + 1) * low.n + high.n;
int32_t d = (count + 1) * low.d + high.d;
if ((n > 0x8000) || (d > 0x10000))
if ((n > 0x8000) || (d > 0x10000)) // 0x8000 0x10000
break;
high.n = n - low.n; // new "A"
high.d = d - low.d;
@ -429,7 +436,7 @@ void Fraction::fractionize(float val)
int32_t count = (int32_t)test; // "N"
int32_t n = low.n + (count + 1) * high.n;
int32_t d = low.d + (count + 1) * high.d;
if ((n > 0x10000) || (d > 0x10000))
if ((n > 0x10000) || (d > 0x10000)) // 0x10000 0x10000
break;
low.n = n - high.n; // new "A"
low.d = d - high.d;

View File

@ -2,22 +2,22 @@
//
// FILE: fraction.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.16
// VERSION: 0.2.0
// PURPOSE: Arduino library to implement a Fraction data type
// URL: https://github.com/RobTillaart/Fraction
#include "Arduino.h"
#define FRACTION_LIB_VERSION (F("0.1.16"))
#define FRACTION_LIB_VERSION (F("0.2.0"))
class Fraction: public Printable
class Fraction
{
public:
explicit Fraction(double);
explicit Fraction(float);
Fraction(int32_t, int32_t);
Fraction(int32_t = 0, int32_t = 1); // default zero constructor
// CONSTRUCTORS
explicit Fraction(int32_t p) : n(p), d(1) {}
@ -28,8 +28,6 @@ public:
explicit Fraction(uint8_t p) : n(p), d(1) {}
Fraction(const Fraction &f) : n(f.n), d(f.d) {}
size_t printTo(Print& p) const;
// EQUALITIES
bool operator == (const Fraction&);
// bool operator == (const float&);
@ -53,10 +51,13 @@ public:
Fraction& operator *= (const Fraction&);
Fraction& operator /= (const Fraction&);
// CONVERSION
// CONVERSION and PRINTING
double toDouble();
float toFloat();
String toString();
bool isProper(); // abs(f) < 1
bool isInteger(); // d == 1
float toAngle();
int32_t nominator();

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/Fraction.git"
},
"version": "0.1.16",
"version": "0.2.0",
"frameworks": "*",
"platforms": "*",
"headers": "fraction.h"

View File

@ -1,5 +1,5 @@
name=Fraction
version=0.1.16
version=0.2.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino library to implement a Fraction datatype.

View File

@ -23,15 +23,68 @@ The code is working with a number of limitations a.o.:
- denominator is max 4 digits to keep code for multiply and divide simple
- Fractions are not exact, even floats are not exact.
- the range of numbers supported is limited.
- code is experimental still.
- code is experimental.
That said, the library is useful e.g. to display float numbers as a fraction.
From programming point of view the **fractionize(float)** function, converting a double
into a fraction is a nice programming problem, fast with a minimal error.
From programming point of view the **fractionize(float)** function,
converting a double / float into a fraction is a nice programming problem,
how to do it fast while minimizing the error.
In short, use fractions with care otherwise your sketch might get broken ;)
#### Notes on natural order
Depending on **fractionize(float)** algorithm used the natural order of numbers
might be broken.
This means that if two floats are very close
```
float f < float g does not imply Fraction(f) < Fraction(g)
float f > float g does not imply Fraction(g) > Fraction(f)
```
The minimalistic **fractionize** keeps the natural order due its simplicity.
It does have a lower accuracy as only limited number of denominators are used.
This means that if two floats are very close
```
float f < float g implies Fraction(f) <= Fraction(g)
float f > float g implies Fraction(g) >= Fraction(f)
```
#### 0.2.0 Breaking change 1
When testing with the array implementation it became evident that some
Fractions were incorrect (not just inaccurate).
An analysis lead to using reciproke values for fractions larger than 1.
By excluding the integerPart the problem looks solved in most cases.
For very small values there are still problems as the fraction cannot be determined.
A test sketch **fraction_extensive.ino** has been added to test all floats
with up to five decimals 0.00000 .. 0.99999.
Results looking good but it is no proof of correctness or guarantee there
are no issues left. In fact the well known fraction for PI = 355/113 is not
found in 0.2.0 any more. This will be investigated in the future.
#### 0.2.0 Breaking change 2
The 0.1.x version implemented the **Printable** interface to allow direct
printing of a Fraction object.
However it became clear that this costs 2 extra bytes per element, which adds up
when creating arrays of fractions.
So the **Printable** interface is removed and replaced by a **toString()** function.
```cpp
Fraction fr(PI);
Serial.print(fr.toString());
```
Fractions can also be printed by using **toFloat()** or **toDouble()**
## Interface
```cpp
@ -42,7 +95,7 @@ In short, use fractions with care otherwise your sketch might get broken ;)
- **explicit Fraction(double)**
- **explicit Fraction(float)**
- **Fraction(int32_t nominator, int32_t denominator)**
- **Fraction(int32_t nominator = 0, int32_t denominator = 1)** Default zero constructor
- **explicit Fraction(int32_t p)**
- **explicit Fraction(int16_t p)**
- **explicit Fraction(int8_t p)**
@ -52,17 +105,6 @@ In short, use fractions with care otherwise your sketch might get broken ;)
- **Fraction(const Fraction &f)**
#### Printable
The Fraction library implements the Printable interface, so one can do.
```cpp
Fraction fr(PI);
Serial.print(fr); // print 355/113
```
#### Equalities
The Fraction library implements ==, !=, >=, >, <, <=
@ -70,24 +112,31 @@ The Fraction library implements ==, !=, >=, >, <, <=
#### Basic Math
The Fraction library implements, + - * / += -= *= /= and - (negation)
The Fraction library implements:
- addition: + and +=
- subtraction: - and -+
- multiplication: \* and \*=
- division: / and /=
- negation: -
#### Conversion
- **double toDouble()** idem.
- **float toFloat()** idem.
- **double toDouble()** converts the fraction to a double.
- **float toFloat()** converts the fraction to a float.
- **String toString()** converts the fraction to a String.
The format is "(n/d)", where n has optionally the sign.
- **bool isProper()** absolute value < 1.
- **float toAngle()** returns 0..360 degrees.
- **int32_t nominator()** idem.
- **int32_t denominator()** idem.
- **int32_t nominator()** returns the nominator.
- **int32_t denominator()** returns the denominator.
#### Miscellaneous (static)
- **Fraction mediant(const Fraction&, const Fraction&)**
- **Fraction middle(const Fraction&, const Fraction&)**
- **Fraction setDenominator(const Fraction&, uint16_t)**
- **Fraction setDenominator(const Fraction&, uint16_t)** (might be simplified still)
## Use with care
@ -104,13 +153,16 @@ The library is reasonably tested. If problems arise please open an issue.
#### Should
- investigate the fraction of PI (0.2.0 does not find 355/113)
- performance testing
- investigate better **fractionize()**
- see **fraction_fast.ino** for faster fractionize (price == accuracy)
- a good start value ?
- depends on nominator / denominator size
- **float fractionize()** returns the error.
- investigate divide by zero errors
- NAN in fraction? => 0/0 ?
- INF in fraction? => 1/0 and -1/0?
- investigate better **fractionize()**
- depends on nom/denom size
- returns the error..
#### Could
@ -119,6 +171,8 @@ The library is reasonably tested. If problems arise please open an issue.
- add famous constants as Fraction e.g
- FRAC_PI = 355/113
- FRAC_E = 3985/1466
- FRAC_GOLDEN_RATIO = (2584/1597)
- add parameters to **toString()** to set () and separator?
#### Wont

View File

@ -44,16 +44,24 @@ unittest_teardown()
unittest(test_constructor)
{
Fraction pi(PI);
assertEqual(355, pi.nominator());
assertEqual(113, pi.denominator());
assertEqualFloat(PI, pi.toFloat(), 0.0001);
// what we wished it would find.
// assertEqual(355, pi.nominator());
// assertEqual(113, pi.denominator());
// (15689, 4994)
assertFalse(pi.isProper());
fprintf(stderr, "PI -> %1.8f\n", pi.toFloat());
fprintf(stderr, "PI %1.8f\n", PI);
fprintf(stderr, "pi %1.8f\n", pi.toFloat());
Fraction ee(EULER);
assertEqual(3985, ee.nominator());
assertEqual(1466, ee.denominator());
assertEqualFloat(EULER, ee.toFloat(), 0.0001);
// what we wished it would find.
// assertEqual(3985, ee.nominator());
// assertEqual(1466, ee.denominator());
// (2721, 1001)
assertFalse(ee.isProper());
fprintf(stderr, "EULER -> %1.8f\n", ee.toFloat());
fprintf(stderr, "EULER %1.8f\n", EULER);
fprintf(stderr, "euler %1.8f\n", ee.toFloat());
Fraction fr(49, 14);
assertEqual(7, fr.nominator());