Skip to content

Commit

Permalink
Fix #9 #10, support for arrays and fix errors in conversion (#11)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
RobTillaart authored Apr 24, 2024
1 parent 03d422e commit dcefc56
Show file tree
Hide file tree
Showing 20 changed files with 847 additions and 179 deletions.
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
102 changes: 78 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)**
Expand All @@ -52,42 +105,38 @@ 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 ==, !=, >=, >, <, <=


#### 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
Expand All @@ -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

Expand All @@ -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

Expand Down
16 changes: 8 additions & 8 deletions examples/FractionFindSum/FractionFindSum.ino
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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++)
{
Expand All @@ -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))
Expand All @@ -57,7 +58,7 @@ void findSum(Fraction f)
}
}
Serial.print("\t => ");
Serial.println(z);
Serial.println(z.toString());
Serial.println();
}

Expand All @@ -67,5 +68,4 @@ void loop()
}


// -- END OF FILE --

// -- END OF FILE --
43 changes: 20 additions & 23 deletions examples/FractionMediant/FractionMediant.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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();

Expand All @@ -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();

Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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 --
Loading

0 comments on commit dcefc56

Please sign in to comment.