0

I have problem of converting a double (say N) to p/q form (rational form), for this I have the following strategy :

  1. Multiply double N by a large number say $k = 10^{10}$
  2. then p = y*k and q = k
  3. Take gcd(p,q) and find p = p/gcd(p,q) and q = p/gcd(p,q)

when N = 8.2 , Answer is correct if we solve using pen and paper, but as 8.2 is represented as 8.19999999 in N (double), it causes problem in its rational form conversion.

I tried it doing other way as : (I used a large no. 10^k instead of 100)

if(abs(y*100 - round(y*100)) < 0.000001) y = round(y*100)/100

But this approach also doesn't give right representation all the time.

Is there any way I could carry out the equivalent conversion from double to p/q ?

mcjoshi
  • 299
  • 1
  • 4
  • 11
  • Not a answer but you can take reference of python Decimal and Fraction module and see their implementation – Hariom Singh Feb 11 '18 at 11:18
  • import fractions import decimal fractions.Fraction(decimal.Decimal('0.5')) =>Fraction(1, 2) . (This is python result ) – Hariom Singh Feb 11 '18 at 11:23
  • `double`s are just approximations of some real values. Arithmetic can't be accurate. Revise your strategy. – Jean-Baptiste Yunès Feb 11 '18 at 11:23
  • Possible duplicate of [How to convert floats to human-readable fractions?](https://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions) – Jean-Baptiste Yunès Feb 11 '18 at 11:24
  • 4
    FWIW, a `double` is exactly equal to the fraction `2^exponent * (2^53 + mantissa) / 2^53`. – Oliver Charlesworth Feb 11 '18 at 11:31
  • 1
    It's easy (but also hard), the binary floating point mantissa is a series of rational fractions (1/2, 1/4, 1/8, 1/16 etc) so all you have to do is sum the fractions and you will have an exact rational fraction for the binary floating point number. I know this is not what you want - that's the hard part. – Richard Critten Feb 11 '18 at 11:31
  • https://rosettacode.org/wiki/Convert_decimal_number_to_rational#C – Hariom Singh Feb 11 '18 at 11:37
  • For what numbers does your current code fail? – Jongware Feb 11 '18 at 17:19
  • 1
    There's no problem in converting 8.2 because this value doesn't exist in the set of machine floating point numbers. No value, no problem. Perhaps you have a problem with the non-existence of 8.2? Well it sucks. But we can't make 8.2 appear out of nowhere. So if you need exactly 8.2, don't use doubles. – n. m. could be an AI Feb 11 '18 at 17:39

2 Answers2

1

Floating point arithmetic is very difficult. As has been mentioned in the comments, part of the difficulty is that you need to represent your numbers in binary.

For example, the number 0.125 can be represented exactly in binary:

0.125 = 2^-3 = 0b0.001

But the number 0.12 cannot.

To 11 significant figures:

0.12 = 0b0.00011110101

If this is converted back to a decimal then the error becomes obvious:

0b0.00011110101 = 0.11962890625

So if you write:

double a = 0.2;

What the machine actually does is find the closest binary representation of 0.2 that it can hold within a double data type. This is an approximation since as we saw above, 0.2 cannot be exactly represented in binary.

One possible approach is to define an 'epsilon' which determines how close your number can be to the nearest representable binary floating point.

Here is a good article on floating points:

https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

laker93
  • 498
  • 4
  • 9
  • The OP already knows that one can't exactly represent 8.2. What he/she is asking is how to obtain 41/5 (presumably), despite this limitation. – Oliver Charlesworth Feb 11 '18 at 22:39
  • From the OP's comments it wasn't clear if they understood why 8.2 cannot be exactly represented in binary. In this answer I explain why and then go on to suggest the epsilon method, which is one way to obtain rational answers despite the limitation of floating point machine representation. – laker93 Feb 12 '18 at 08:26
1

have problem of converting a double (say N) to p/q form
... when N = 8.2

A typical double cannot encode 8.2 exactly. Instead the closest representable double is about

8.19999999999999928945726423989981412887573...
8.20000000000000106581410364015027880668640...  // next closest

When code does

double N = 8.2;

It will be the 8.19999999999999928945726423989981412887573... that is converted into rational form.


Converting a double to p/q form:

Multiply double N by a large number say $k = 10^{10}$

This may overflow the double. First step should be to determine if the double is large, it which case, it is a whole number.

Do not multiple by some power of 10 as double certainly uses a binary encoding. Multiplication by 10, 100, etc. may introduce round-off error.


C implementations of double overwhelmingly use a binary encoding, so that FLT_RADIX == 2.

Then every finite double x has a significand that is a fraction of some integer over some power of 2: a binary fraction of DBL_MANT_DIG digits @Richard Critten. This is often 53 binary digits.

Determine the exponent of the double. If large enough or x == 0.0, the double is a whole number.

Otherwise, scale a numerator and denominator by DBL_MANT_DIG. While the numerator is even, halve both the numerator and denominator. As the denominator is a power-of-2, no other prime values are needed for simplification consideration.

#include <float.h>
#include <math.h>
#include <stdio.h>

void form_ratio(double x) {
  double numerator = x;
  double denominator = 1.0;
  if (isfinite(numerator) && x != 0.0) {
    int expo;
    frexp(numerator, &expo);
    if (expo < DBL_MANT_DIG) {
      expo = DBL_MANT_DIG - expo;
      numerator = ldexp(numerator, expo);
      denominator = ldexp(1.0, expo);
      while (fmod(numerator, 2.0) == 0.0 && denominator > 1.0) {
        numerator /= 2.0;
        denominator /= 2.0;
      }
    }
  }
  int pre = DBL_DECIMAL_DIG;
  printf("%.*g --> %.*g/%.*g\n", pre, x, pre, numerator, pre, denominator);
}

int main(void) {
  form_ratio(123456789012.0);
  form_ratio(42.0);
  form_ratio(1.0 / 7);
  form_ratio(867.5309);
}

Output

123456789012 --> 123456789012/1
42 --> 42/1
0.14285714285714285 --> 2573485501354569/18014398509481984
867.53089999999997 --> 3815441248019913/4398046511104
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256