Come convertire i float in frazioni leggibili dall'uomo?
-
01-07-2019 - |
Domanda
Diciamo che abbiamo 0,33, dobbiamo produrre "1/3".
Se abbiamo "0.4", dobbiamo restituire "2/5".
L'idea è di renderlo leggibile dall'uomo per far comprendere all'utente "x parti su y" come un modo migliore di comprendere i dati.
So che le percentuali sono un buon sostituto, ma mi chiedevo se esistesse un modo semplice per farlo?
Soluzione
Ho trovato quello di David Eppstein trovare un'approssimazione razionale al numero reale dato Il codice C deve essere esattamente quello che stai chiedendo.Si basa sulla teoria delle frazioni continue ed è molto veloce e abbastanza compatto.
Ho utilizzato versioni di questo personalizzate per limiti specifici di numeratore e denominatore.
/*
** find rational approximation to given real number
** David Eppstein / UC Irvine / 8 Aug 1993
**
** With corrections from Arno Formella, May 2008
**
** usage: a.out r d
** r is real number to approx
** d is the maximum denominator allowed
**
** based on the theory of continued fractions
** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...)))
** then best approximation is found by truncating this series
** (with some adjustments in the last term).
**
** Note the fraction can be recovered as the first column of the matrix
** ( a1 1 ) ( a2 1 ) ( a3 1 ) ...
** ( 1 0 ) ( 1 0 ) ( 1 0 )
** Instead of keeping the sequence of continued fraction terms,
** we just keep the last partial product of these matrices.
*/
#include <stdio.h>
main(ac, av)
int ac;
char ** av;
{
double atof();
int atoi();
void exit();
long m[2][2];
double x, startx;
long maxden;
long ai;
/* read command line arguments */
if (ac != 3) {
fprintf(stderr, "usage: %s r d\n",av[0]); // AF: argument missing
exit(1);
}
startx = x = atof(av[1]);
maxden = atoi(av[2]);
/* initialize matrix */
m[0][0] = m[1][1] = 1;
m[0][1] = m[1][0] = 0;
/* loop finding terms until denom gets too big */
while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) {
long t;
t = m[0][0] * ai + m[0][1];
m[0][1] = m[0][0];
m[0][0] = t;
t = m[1][0] * ai + m[1][1];
m[1][1] = m[1][0];
m[1][0] = t;
if(x==(double)ai) break; // AF: division by zero
x = 1/(x - (double) ai);
if(x>(double)0x7FFFFFFF) break; // AF: representation failure
}
/* now remaining x is between 0 and 1/ai */
/* approx as either 0 or 1/m where m is max that will fit in maxden */
/* first try zero */
printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
startx - ((double) m[0][0] / (double) m[1][0]));
/* now try other possibility */
ai = (maxden - m[1][1]) / m[1][0];
m[0][0] = m[0][0] * ai + m[0][1];
m[1][0] = m[1][0] * ai + m[1][1];
printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
startx - ((double) m[0][0] / (double) m[1][0]));
}
Altri suggerimenti
Da Python 2.6 in poi c'è il fractions
modulo.
(Citazione dai documenti.)
>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)
>>> from math import pi, cos
>>> Fraction.from_float(cos(pi/3))
Fraction(4503599627370497, 9007199254740992)
>>> Fraction.from_float(cos(pi/3)).limit_denominator()
Fraction(1, 2)
Se l'output deve dare a un lettore umano una rapida impressione dell'ordine del risultato, non ha senso restituire qualcosa come "113/211", quindi l'output dovrebbe limitarsi a utilizzare numeri a una cifra (e forse 1/ 10 e 9/10).Se è così, puoi osservare che ce ne sono solo 27 diverso frazioni.
Poiché la matematica sottostante per la generazione dell'output non cambierà mai, una soluzione potrebbe essere semplicemente quella di codificare semplicemente un albero di ricerca binario, in modo che la funzione esegua al massimo log(27) ~= 4 3/4 confronti.Ecco una versione C testata del codice
char *userTextForDouble(double d, char *rval)
{
if (d == 0.0)
return "0";
// TODO: negative numbers:if (d < 0.0)...
if (d >= 1.0)
sprintf(rval, "%.0f ", floor(d));
d = d-floor(d); // now only the fractional part is left
if (d == 0.0)
return rval;
if( d < 0.47 )
{
if( d < 0.25 )
{
if( d < 0.16 )
{
if( d < 0.12 ) // Note: fixed from .13
{
if( d < 0.11 )
strcat(rval, "1/10"); // .1
else
strcat(rval, "1/9"); // .1111....
}
else // d >= .12
{
if( d < 0.14 )
strcat(rval, "1/8"); // .125
else
strcat(rval, "1/7"); // .1428...
}
}
else // d >= .16
{
if( d < 0.19 )
{
strcat(rval, "1/6"); // .1666...
}
else // d > .19
{
if( d < 0.22 )
strcat(rval, "1/5"); // .2
else
strcat(rval, "2/9"); // .2222...
}
}
}
else // d >= .25
{
if( d < 0.37 ) // Note: fixed from .38
{
if( d < 0.28 ) // Note: fixed from .29
{
strcat(rval, "1/4"); // .25
}
else // d >=.28
{
if( d < 0.31 )
strcat(rval, "2/7"); // .2857...
else
strcat(rval, "1/3"); // .3333...
}
}
else // d >= .37
{
if( d < 0.42 ) // Note: fixed from .43
{
if( d < 0.40 )
strcat(rval, "3/8"); // .375
else
strcat(rval, "2/5"); // .4
}
else // d >= .42
{
if( d < 0.44 )
strcat(rval, "3/7"); // .4285...
else
strcat(rval, "4/9"); // .4444...
}
}
}
}
else
{
if( d < 0.71 )
{
if( d < 0.60 )
{
if( d < 0.55 ) // Note: fixed from .56
{
strcat(rval, "1/2"); // .5
}
else // d >= .55
{
if( d < 0.57 )
strcat(rval, "5/9"); // .5555...
else
strcat(rval, "4/7"); // .5714
}
}
else // d >= .6
{
if( d < 0.62 ) // Note: Fixed from .63
{
strcat(rval, "3/5"); // .6
}
else // d >= .62
{
if( d < 0.66 )
strcat(rval, "5/8"); // .625
else
strcat(rval, "2/3"); // .6666...
}
}
}
else
{
if( d < 0.80 )
{
if( d < 0.74 )
{
strcat(rval, "5/7"); // .7142...
}
else // d >= .74
{
if(d < 0.77 ) // Note: fixed from .78
strcat(rval, "3/4"); // .75
else
strcat(rval, "7/9"); // .7777...
}
}
else // d >= .8
{
if( d < 0.85 ) // Note: fixed from .86
{
if( d < 0.83 )
strcat(rval, "4/5"); // .8
else
strcat(rval, "5/6"); // .8333...
}
else // d >= .85
{
if( d < 0.87 ) // Note: fixed from .88
{
strcat(rval, "6/7"); // .8571
}
else // d >= .87
{
if( d < 0.88 ) // Note: fixed from .89
{
strcat(rval, "7/8"); // .875
}
else // d >= .88
{
if( d < 0.90 )
strcat(rval, "8/9"); // .8888...
else
strcat(rval, "9/10"); // .9
}
}
}
}
}
}
return rval;
}
Ecco un collegamento che spiega i calcoli dietro la conversione di un decimale in una frazione:
http://www.webmath.com/dec2fract.html
Ed ecco una funzione di esempio su come farlo effettivamente utilizzando VB (da www.freevbcode.com/ShowCode.asp?ID=582):
Public Function Dec2Frac(ByVal f As Double) As String
Dim df As Double
Dim lUpperPart As Long
Dim lLowerPart As Long
lUpperPart = 1
lLowerPart = 1
df = lUpperPart / lLowerPart
While (df <> f)
If (df < f) Then
lUpperPart = lUpperPart + 1
Else
lLowerPart = lLowerPart + 1
lUpperPart = f * lLowerPart
End If
df = lUpperPart / lLowerPart
Wend
Dec2Frac = CStr(lUpperPart) & "/" & CStr(lLowerPart)
End Function
(Dalle ricerche su Google:convertire decimale in frazione, convertire decimale in codice frazione)
Potresti voler leggere Ciò che ogni informatico dovrebbe sapere sull'aritmetica in virgola mobile.
Dovrai specificare una certa precisione moltiplicando per un numero elevato:
3.141592 * 1000000 = 3141592
quindi puoi creare una frazione:
3 + (141592 / 1000000)
e ridurre tramite GCD...
3 + (17699 / 125000)
ma non c'è modo di ottenerlo destinato frazione fuori.Potresti volere Sempre usa invece le frazioni in tutto il codice: ricorda solo di ridurre le frazioni quando puoi per evitare l'overflow!
Ecco le versioni Perl e Javascript del codice VB suggerito da devinmoore:
Perl:
sub dec2frac {
my $d = shift;
my $df = 1;
my $top = 1;
my $bot = 1;
while ($df != $d) {
if ($df < $d) {
$top += 1;
}
else {
$bot += 1;
$top = int($d * $bot);
}
$df = $top / $bot;
}
return "$top/$bot";
}
E il javascript quasi identico:
function dec2frac(d) {
var df = 1;
var top = 1;
var bot = 1;
while (df != d) {
if (df < d) {
top += 1;
}
else {
bot += 1;
top = parseInt(d * bot);
}
df = top / bot;
}
return top + '/' + bot;
}
Un'implementazione C#
/// <summary>
/// Represents a rational number
/// </summary>
public struct Fraction
{
public int Numerator;
public int Denominator;
/// <summary>
/// Constructor
/// </summary>
public Fraction(int numerator, int denominator)
{
this.Numerator = numerator;
this.Denominator = denominator;
}
/// <summary>
/// Approximates a fraction from the provided double
/// </summary>
public static Fraction Parse(double d)
{
return ApproximateFraction(d);
}
/// <summary>
/// Returns this fraction expressed as a double, rounded to the specified number of decimal places.
/// Returns double.NaN if denominator is zero
/// </summary>
public double ToDouble(int decimalPlaces)
{
if (this.Denominator == 0)
return double.NaN;
return System.Math.Round(
Numerator / (double)Denominator,
decimalPlaces
);
}
/// <summary>
/// Approximates the provided value to a fraction.
/// http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
/// </summary>
private static Fraction ApproximateFraction(double value)
{
const double EPSILON = .000001d;
int n = 1; // numerator
int d = 1; // denominator
double fraction = n / d;
while (System.Math.Abs(fraction - value) > EPSILON)
{
if (fraction < value)
{
n++;
}
else
{
d++;
n = (int)System.Math.Round(value * d);
}
fraction = n / (double)d;
}
return new Fraction(n, d);
}
}
IL Albero di Stern-Brocot induce un modo abbastanza naturale per approssimare i numeri reali mediante frazioni con denominatori semplici.
Parte del problema è che così tante frazioni non possono essere facilmente interpretate come frazioni.Per esempio.0,33 non è 1/3, è 33/100.Ma se ricordi la tua formazione scolastica elementare, allora esiste un processo di conversione dei valori decimali in frazioni, tuttavia è improbabile che ti dia ciò che desideri poiché la maggior parte delle volte i numeri decimali non sono memorizzati a 0,33, ma a 0,329999999999998 o qualcosa del genere.
Fatti un favore e non preoccuparti di questo, ma se ne hai bisogno puoi fare quanto segue:
Moltiplica il valore originale per 10 fino a rimuovere la parte frazionaria.Conserva quel numero e usalo come divisore.Quindi fai una serie di semplificazioni cercando i denominatori comuni.
Quindi 0,4 sarebbe 4/10.Dovresti quindi cercare i divisori comuni iniziando con valori bassi, probabilmente numeri primi.Partendo da 2, potrai vedere se 2 divide equamente sia il numeratore che il denominatore controllando se il fondo della divisione è lo stesso della divisione stessa.
floor(5/2) = 2
5/2 = 2.5
Quindi 5 non divide 2 equamente.Quindi controlli il numero successivo, diciamo 3.Fallo finché non raggiungi o raggiungi la radice quadrata del numero più piccolo o la superi.
Dopo averlo fatto, allora ti serve
Questo non è un "algoritmo", solo una soluzione Python:http://docs.python.org/library/fractions.html
>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)
"Diciamo che abbiamo 0,33, dobbiamo produrre "1/3"."
Quale precisione ti aspetti che abbia la "soluzione"?0,33 non è uguale a 1/3.Come si riconosce una risposta "buona" (facile da leggere)?
Qualunque cosa accada, un possibile algoritmo potrebbe essere:
Se ti aspetti di trovare la frazione più vicina in una forma X/Y dove Y è inferiore a 10, puoi eseguire il ciclo di tutti i 9 Y possibili, per ogni Y calcolare X e quindi selezionare quello più accurato.
Una soluzione integrata in R:
library(MASS)
fractions(0.666666666)
## [1] 2/3
Questo utilizza un metodo di frazione continua e ha facoltativo cycles
E max.denominator
argomenti per regolare la precisione.
Dovrai capire quale livello di errore sei disposto ad accettare.Non tutte le frazioni decimali si ridurranno a una frazione semplice.Probabilmente sceglierei un numero facilmente divisibile, come 60, e capirei quanti sessantesimi sono più vicini al valore, quindi semplificherei la frazione.
Puoi farlo in qualsiasi linguaggio di programmazione seguendo i seguenti passaggi:
- Moltiplica e dividi per 10^x dove x è la potenza di 10 necessaria per assicurarti che al numero non rimangano cifre decimali.Esempio:Moltiplica 0,33 per 10^2 = 100 per ottenere 33 e dividilo per lo stesso per ottenere 33/100
- Riduci il numeratore e il denominatore della frazione risultante mediante fattorizzazione, finché non puoi più ottenere numeri interi dal risultato.
- La frazione ridotta risultante dovrebbe essere la tua risposta.
Esempio:0,2 = 0,2 x 10^1/10^1 = 2/10 = 1/5
Quindi può essere letto come "1 parte su 5"
Una soluzione è innanzitutto memorizzare tutti i numeri come numeri razionali.Esistono librerie per l'aritmetica dei numeri razionali (es GMP).Se utilizzi un linguaggio OO potresti essere in grado di utilizzare semplicemente una libreria di classi di numeri razionali per sostituire la tua classe di numeri.
I programmi finanziari, tra gli altri, utilizzerebbero tale soluzione per essere in grado di effettuare calcoli esatti e preservare la precisione che potrebbe essere persa utilizzando un float semplice.
Ovviamente sarà molto più lento, quindi potrebbe non essere pratico per te.Dipende da quanti calcoli devi fare e quanto è importante per te la precisione.
a = rational(1);
b = rational(3);
c = a / b;
print (c.asFraction) ---> "1/3"
print (c.asFloat) ----> "0.333333"
Penso che il modo migliore per farlo sia prima convertire il valore float in una rappresentazione ASCII.In C++ potresti usare ostringstream o in C, potresti usare sprintf.Ecco come apparirebbe in C++:
ostringstream oss;
float num;
cin >> num;
oss << num;
string numStr = oss.str();
int i = numStr.length(), pow_ten = 0;
while (i > 0) {
if (numStr[i] == '.')
break;
pow_ten++;
i--;
}
for (int j = 1; j < pow_ten; j++) {
num *= 10.0;
}
cout << static_cast<int>(num) << "/" << pow(10, pow_ten - 1) << endl;
Un approccio simile potrebbe essere adottato direttamente in C.
Successivamente dovresti verificare che la frazione sia ai minimi termini.Questo algoritmo fornirà una risposta precisa, ad es.0,33 produrrebbe "33/100", non "1/3". Tuttavia, 0,4 darebbe "4/10", che quando ridotto ai termini più bassi sarebbe "2/5". Questo potrebbe non essere così potente come la soluzione di Eppstein, ma credo che sia più semplice.
Ruby ha già una soluzione integrata:
0.33.rationalize.to_s # => "33/100"
0.4.rationalize.to_s # => "2/5"
In Rails, anche gli attributi numerici di ActiveRecord possono essere convertiti:
product.size = 0.33
product.size.to_r.to_s # => "33/100"
Rispondi in C++, presupponendo che tu abbia una classe "BigInt", che può memorizzare numeri interi di dimensione illimitata.
Puoi invece utilizzare "unsigned long long", ma funzionerà solo per determinati valori.
void GetRational(double val)
{
if (val == val+1) // Inf
throw "Infinite Value";
if (val != val) // NaN
throw "Undefined Value";
bool sign = false;
BigInt enumerator = 0;
BigInt denominator = 1;
if (val < 0)
{
val = -val;
sign = true;
}
while (val > 0)
{
unsigned int intVal = (unsigned int)val;
val -= intVal;
enumerator += intVal;
val *= 2;
enumerator *= 2;
denominator *= 2;
}
BigInt gcd = GCD(enumerator,denominator);
enumerator /= gcd;
denominator /= gcd;
Print(sign? "-":"+");
Print(enumerator);
Print("/");
Print(denominator);
// Or simply return {sign,enumerator,denominator} as you wish
}
A proposito, GetRational(0.0) restituirà "+0/1", quindi potresti voler gestire questo caso separatamente.
PS:Utilizzo questo codice nella mia classe "RationalNum" da diversi anni ed è stato testato accuratamente.
Questo algoritmo di Ian Richards / Giovanni Kennedy non solo restituisce belle frazioni, ma funziona anche molto bene in termini di velocità.Questo è il codice C# tratto da questa risposta da me.
Può gestire tutto double
valori eccetto valori speciali come NaN e +/- infinito, che dovrai aggiungere se necessario.
Restituisce a new Fraction(numerator, denominator)
.Sostituisci con il tuo tipo.
Per ulteriori valori di esempio e un confronto con altri algoritmi, andare qui
public Fraction RealToFraction(double value, double accuracy)
{
if (accuracy <= 0.0 || accuracy >= 1.0)
{
throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1.");
}
int sign = Math.Sign(value);
if (sign == -1)
{
value = Math.Abs(value);
}
// Accuracy is the maximum relative error; convert to absolute maxError
double maxError = sign == 0 ? accuracy : value * accuracy;
int n = (int) Math.Floor(value);
value -= n;
if (value < maxError)
{
return new Fraction(sign * n, 1);
}
if (1 - maxError < value)
{
return new Fraction(sign * (n + 1), 1);
}
double z = value;
int previousDenominator = 0;
int denominator = 1;
int numerator;
do
{
z = 1.0 / (z - (int) z);
int temp = denominator;
denominator = denominator * (int) z + previousDenominator;
previousDenominator = temp;
numerator = Convert.ToInt32(value * denominator);
}
while (Math.Abs(value - (double) numerator / denominator) > maxError && z != (int) z);
return new Fraction((n * denominator + numerator) * sign, denominator);
}
Valori di esempio restituiti da questo algoritmo:
Accuracy: 1.0E-3 | Richards
Input | Result Error
======================| =============================
3 | 3/1 0
0.999999 | 1/1 1.0E-6
1.000001 | 1/1 -1.0E-6
0.50 (1/2) | 1/2 0
0.33... (1/3) | 1/3 0
0.67... (2/3) | 2/3 0
0.25 (1/4) | 1/4 0
0.11... (1/9) | 1/9 0
0.09... (1/11) | 1/11 0
0.62... (307/499) | 8/13 2.5E-4
0.14... (33/229) | 16/111 2.7E-4
0.05... (33/683) | 10/207 -1.5E-4
0.18... (100/541) | 17/92 -3.3E-4
0.06... (33/541) | 5/82 -3.7E-4
0.1 | 1/10 0
0.2 | 1/5 0
0.3 | 3/10 0
0.4 | 2/5 0
0.5 | 1/2 0
0.6 | 3/5 0
0.7 | 7/10 0
0.8 | 4/5 0
0.9 | 9/10 0
0.01 | 1/100 0
0.001 | 1/1000 0
0.0001 | 1/10000 0
0.33333333333 | 1/3 1.0E-11
0.333 | 333/1000 0
0.7777 | 7/9 1.0E-4
0.11 | 10/91 -1.0E-3
0.1111 | 1/9 1.0E-4
3.14 | 22/7 9.1E-4
3.14... (pi) | 22/7 4.0E-4
2.72... (e) | 87/32 1.7E-4
0.7454545454545 | 38/51 -4.8E-4
0.01024801004 | 2/195 8.2E-4
0.99011 | 100/101 -1.1E-5
0.26... (5/19) | 5/19 0
0.61... (37/61) | 17/28 9.7E-4
|
Accuracy: 1.0E-4 | Richards
Input | Result Error
======================| =============================
0.62... (307/499) | 299/486 -6.7E-6
0.05... (33/683) | 23/476 6.4E-5
0.06... (33/541) | 33/541 0
1E-05 | 1/99999 1.0E-5
0.7777 | 1109/1426 -1.8E-7
3.14... (pi) | 333/106 -2.6E-5
2.72... (e) | 193/71 1.0E-5
0.61... (37/61) | 37/61 0
Avrai due problemi fondamentali che renderanno tutto difficile:
1) La virgola mobile non è una rappresentazione esatta, il che significa che se hai una frazione di "x/y" che risulta in un valore di "z", l'algoritmo della frazione potrebbe restituire un risultato diverso da "x/y".
2) Esistono infiniti molti più numeri irrazionali che razionali.Un numero razionale è un numero che può essere rappresentato come una frazione.Gli irrazionali sono quelli che non possono.
Tuttavia, in un modo economico, poiché il punto mobile ha una precisione limitata, puoi sempre rappresentarlo come una qualche forma di fazione.(Penso...)
Completato il codice sopra e convertito in as3
public static function toFrac(f:Number) : String
{
if (f>1)
{
var parte1:int;
var parte2:Number;
var resultado:String;
var loc:int = String(f).indexOf(".");
parte2 = Number(String(f).slice(loc, String(f).length));
parte1 = int(String(f).slice(0,loc));
resultado = toFrac(parte2);
parte1 *= int(resultado.slice(resultado.indexOf("/") + 1, resultado.length)) + int(resultado.slice(0, resultado.indexOf("/")));
resultado = String(parte1) + resultado.slice(resultado.indexOf("/"), resultado.length)
return resultado;
}
if( f < 0.47 )
if( f < 0.25 )
if( f < 0.16 )
if( f < 0.13 )
if( f < 0.11 )
return "1/10";
else
return "1/9";
else
if( f < 0.14 )
return "1/8";
else
return "1/7";
else
if( f < 0.19 )
return "1/6";
else
if( f < 0.22 )
return "1/5";
else
return "2/9";
else
if( f < 0.38 )
if( f < 0.29 )
return "1/4";
else
if( f < 0.31 )
return "2/7";
else
return "1/3";
else
if( f < 0.43 )
if( f < 0.40 )
return "3/8";
else
return "2/5";
else
if( f < 0.44 )
return "3/7";
else
return "4/9";
else
if( f < 0.71 )
if( f < 0.60 )
if( f < 0.56 )
return "1/2";
else
if( f < 0.57 )
return "5/9";
else
return "4/7";
else
if( f < 0.63 )
return "3/5";
else
if( f < 0.66 )
return "5/8";
else
return "2/3";
else
if( f < 0.80 )
if( f < 0.74 )
return "5/7";
else
if(f < 0.78 )
return "3/4";
else
return "7/9";
else
if( f < 0.86 )
if( f < 0.83 )
return "4/5";
else
return "5/6";
else
if( f < 0.88 )
return "6/7";
else
if( f < 0.89 )
return "7/8";
else
if( f < 0.90 )
return "8/9";
else
return "9/10";
}
Diciamo che abbiamo 0,33, dobbiamo produrre "1/3".Se abbiamo "0.4", dobbiamo produrre "2/5".
È sbagliato nel caso comune, a causa di 1/3 = 0,3333333 = 0. (3) Inoltre, è impossibile scoprire da Soluzioni sopra suggerite che il decimale è convertito in frazione con precisione definita, poiché l'output è sempre una frazione.
MA suggerisco la mia funzione completa con molte opzioni basate sull'idea di Serie geometriche infinite, nello specifico sulla formula:
Inizialmente questa funzione tenta di trovare il periodo della frazione nella rappresentazione della stringa.Successivamente viene applicata la formula sopra descritta.
Il codice dei numeri razionali è preso in prestito Stefano M.McKamey implementazione dei numeri razionali in C#.Spero che non sia molto difficile portare il mio codice su altre lingue.
/// <summary>
/// Convert decimal to fraction
/// </summary>
/// <param name="value">decimal value to convert</param>
/// <param name="result">result fraction if conversation is succsess</param>
/// <param name="decimalPlaces">precision of considereation frac part of value</param>
/// <param name="trimZeroes">trim zeroes on the right part of the value or not</param>
/// <param name="minPeriodRepeat">minimum period repeating</param>
/// <param name="digitsForReal">precision for determination value to real if period has not been founded</param>
/// <returns></returns>
public static bool FromDecimal(decimal value, out Rational<T> result,
int decimalPlaces = 28, bool trimZeroes = false, decimal minPeriodRepeat = 2, int digitsForReal = 9)
{
var valueStr = value.ToString("0.0000000000000000000000000000", CultureInfo.InvariantCulture);
var strs = valueStr.Split('.');
long intPart = long.Parse(strs[0]);
string fracPartTrimEnd = strs[1].TrimEnd(new char[] { '0' });
string fracPart;
if (trimZeroes)
{
fracPart = fracPartTrimEnd;
decimalPlaces = Math.Min(decimalPlaces, fracPart.Length);
}
else
fracPart = strs[1];
result = new Rational<T>();
try
{
string periodPart;
bool periodFound = false;
int i;
for (i = 0; i < fracPart.Length; i++)
{
if (fracPart[i] == '0' && i != 0)
continue;
for (int j = i + 1; j < fracPart.Length; j++)
{
periodPart = fracPart.Substring(i, j - i);
periodFound = true;
decimal periodRepeat = 1;
decimal periodStep = 1.0m / periodPart.Length;
var upperBound = Math.Min(fracPart.Length, decimalPlaces);
int k;
for (k = i + periodPart.Length; k < upperBound; k += 1)
{
if (periodPart[(k - i) % periodPart.Length] != fracPart[k])
{
periodFound = false;
break;
}
periodRepeat += periodStep;
}
if (!periodFound && upperBound - k <= periodPart.Length && periodPart[(upperBound - i) % periodPart.Length] > '5')
{
var ind = (k - i) % periodPart.Length;
var regroupedPeriod = (periodPart.Substring(ind) + periodPart.Remove(ind)).Substring(0, upperBound - k);
ulong periodTailPlusOne = ulong.Parse(regroupedPeriod) + 1;
ulong fracTail = ulong.Parse(fracPart.Substring(k, regroupedPeriod.Length));
if (periodTailPlusOne == fracTail)
periodFound = true;
}
if (periodFound && periodRepeat >= minPeriodRepeat)
{
result = FromDecimal(strs[0], fracPart.Substring(0, i), periodPart);
break;
}
else
periodFound = false;
}
if (periodFound)
break;
}
if (!periodFound)
{
if (fracPartTrimEnd.Length >= digitsForReal)
return false;
else
{
result = new Rational<T>(long.Parse(strs[0]), 1, false);
if (fracPartTrimEnd.Length != 0)
result = new Rational<T>(ulong.Parse(fracPartTrimEnd), TenInPower(fracPartTrimEnd.Length));
return true;
}
}
return true;
}
catch
{
return false;
}
}
public static Rational<T> FromDecimal(string intPart, string fracPart, string periodPart)
{
Rational<T> firstFracPart;
if (fracPart != null && fracPart.Length != 0)
{
ulong denominator = TenInPower(fracPart.Length);
firstFracPart = new Rational<T>(ulong.Parse(fracPart), denominator);
}
else
firstFracPart = new Rational<T>(0, 1, false);
Rational<T> secondFracPart;
if (periodPart != null && periodPart.Length != 0)
secondFracPart =
new Rational<T>(ulong.Parse(periodPart), TenInPower(fracPart.Length)) *
new Rational<T>(1, Nines((ulong)periodPart.Length), false);
else
secondFracPart = new Rational<T>(0, 1, false);
var result = firstFracPart + secondFracPart;
if (intPart != null && intPart.Length != 0)
{
long intPartLong = long.Parse(intPart);
result = new Rational<T>(intPartLong, 1, false) + (intPartLong == 0 ? 1 : Math.Sign(intPartLong)) * result;
}
return result;
}
private static ulong TenInPower(int power)
{
ulong result = 1;
for (int l = 0; l < power; l++)
result *= 10;
return result;
}
private static decimal TenInNegPower(int power)
{
decimal result = 1;
for (int l = 0; l > power; l--)
result /= 10.0m;
return result;
}
private static ulong Nines(ulong power)
{
ulong result = 9;
if (power >= 0)
for (ulong l = 0; l < power - 1; l++)
result = result * 10 + 9;
return result;
}
Ci sono alcuni esempi di utilizzo:
Rational<long>.FromDecimal(0.33333333m, out r, 8, false);
// then r == 1 / 3;
Rational<long>.FromDecimal(0.33333333m, out r, 9, false);
// then r == 33333333 / 100000000;
Il tuo caso con il taglio della parte zero della parte destra:
Rational<long>.FromDecimal(0.33m, out r, 28, true);
// then r == 1 / 3;
Rational<long>.FromDecimal(0.33m, out r, 28, true);
// then r == 33 / 100;
Periodo minimo di dimostrazione:
Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.5m));
// then r == 1234 / 9999;
Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.6m));
// then r == 123412 / 1000000; because of minimu repeating of period is 0.1234123 in this case.
Arrotondamento alla fine:
Rational<long>.FromDecimal(0.8888888888888888888888888889m, out r));
// then r == 8 == 9;
Il caso più interessante:
Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 9);
// then r == 12345678 / 100000000;
Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 8);
// Conversation failed, because of period has not been founded and there are too many digits in fraction part of input value.
Rational<long>.FromDecimal(0.12121212121212121m, out r, 28, true, 2, 9));
// then r == 4 / 33; Despite of too many digits in input value, period has been founded. Thus it's possible to convert value to fraction.
Altri test e codici in cui tutti possono trovarli la mia libreria MathFunctions su github.
Ecco un'implementazione rapida e sporca in Javascript che utilizza un approccio di forza bruta.Per niente ottimizzato, funziona all'interno di un intervallo predefinito di frazioni: http://jsfiddle.net/PdL23/1/
/* This should convert any decimals to a simplified fraction within the range specified by the two for loops. Haven't done any thorough testing, but it seems to work fine.
I have set the bounds for numerator and denominator to 20, 20... but you can increase this if you want in the two for loops.
Disclaimer: Its not at all optimized. (Feel free to create an improved version.)
*/
decimalToSimplifiedFraction = function(n) {
for(num = 1; num < 20; num++) { // "num" is the potential numerator
for(den = 1; den < 20; den++) { // "den" is the potential denominator
var multiplyByInverse = (n * den ) / num;
var roundingError = Math.round(multiplyByInverse) - multiplyByInverse;
// Checking if we have found the inverse of the number,
if((Math.round(multiplyByInverse) == 1) && (Math.abs(roundingError) < 0.01)) {
return num + "/" + den;
}
}
}
};
//Put in your test number here.
var floatNumber = 2.56;
alert(floatNumber + " = " + decimalToSimplifiedFraction(floatNumber));
Questo si ispira all'approccio utilizzato da JPS.
Come molte persone hanno affermato, non è possibile riconvertire un punto mobile in una frazione (a meno che non sia estremamente esatto come 0,25).Ovviamente potresti creare un qualche tipo di ricerca per una vasta gamma di frazioni e utilizzare una sorta di logica fuzzy per produrre il risultato che stai cercando.Anche in questo caso questo non sarebbe esatto e dovresti definire un limite inferiore di quanto grande vuoi che sia il denominatore.
.32 < x < .34 = 1/3 o qualcosa del genere.
Ecco l'implementazione per Ruby http://github.com/valodzka/frac
Math.frac(0.2, 100) # => (1/5)
Math.frac(0.33, 10) # => (1/3)
Math.frac(0.33, 100) # => (33/100)
Mi sono imbattuto in una soluzione Haskell particolarmente elegante che utilizza un anamorfismo.Dipende da schemi di ricorsione pacchetto.
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative (liftA2)
import Control.Monad (ap)
import Data.Functor.Foldable
import Data.Ratio (Ratio, (%))
isInteger :: (RealFrac a) => a -> Bool
isInteger = ((==) <*>) (realToFrac . floor)
continuedFraction :: (RealFrac a) => a -> [Int]
continuedFraction = liftA2 (:) floor (ana coalgebra)
where coalgebra x
| isInteger x = Nil
| otherwise = Cons (floor alpha) alpha
where alpha = 1 / (x - realToFrac (floor x))
collapseFraction :: (Integral a) => [Int] -> Ratio a
collapseFraction [x] = fromIntegral x % 1
collapseFraction (x:xs) = (fromIntegral x % 1) + 1 / collapseFraction xs
-- | Use the nth convergent to approximate x
approximate :: (RealFrac a, Integral b) => a -> Int -> Ratio b
approximate x n = collapseFraction $ take n (continuedFraction x)
Se lo provi su ghci, funziona davvero!
λ:> approximate pi 2
22 % 7