5

We need to convert a calculated value which might be something like 3.33333000540733337 to 3 1/3. Any of the libraries I've tried such as https://github.com/peterolson/BigRational.js will convert that to the most accurate rational number whereas I'm only concerned with the approximate rational number, to .01 significant decimals.

In ruby we currently do Rational(1.333).rationalize(Rational(0.01)) which gives us 1 as whole number, 1 as numerator and 3 as denominator.

Any thoughts on an algorithm that might help would be great.

dstarh
  • 4,976
  • 5
  • 36
  • 68
  • 1
    ["How to convert floats to human-readable fractions?"](http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions) and specifically [this javascript-oriented answer](http://stackoverflow.com/a/681534/1934901) – tehsockz Oct 09 '13 at 00:02
  • Keep in mind that converting `3.33333000540733337` to a rational number "to .01 significant decimals" will yield `3 33/100`, not `3 1/3`. – Peter Olson Oct 09 '13 at 00:15
  • Peter yep, we really need a reduced approximate fraction. – dstarh Oct 09 '13 at 00:36
  • @PeterOlson Not necessarily. The best rational approximation of 3.33333000540733337 with a denominator at most 100 is indeed 4/3, not 133/100. So that depends what algorithm is used. Using continued fractions, we'll get 4/3 and not 133/100. – Stef May 16 '22 at 19:53

5 Answers5

5

You can use a function like this using the https://github.com/peterolson/BigRational.js library:

function rationalize(rational, epsilon) {
    var denominator = 0;
    var numerator;
    var error;

    do {
        denominator++;
        numerator = Math.round((rational.numerator * denominator) / rational.denominator);
        error = Math.abs(rational.minus(numerator / denominator));
    } while (error > epsilon);
    return bigRat(numerator, denominator);
}

It will return a bigRat object. You can check your example with this:

console.log(rationalize(bigRat(3.33333000540733337),0.01));
Juan Sánchez
  • 980
  • 2
  • 10
  • 26
  • Thanks, this seems like it might do it, I'll have to write some comparison tests to see if it matches what we're getting from ruby. – dstarh Oct 09 '13 at 00:56
  • This will work only for numerators and denominators within the range of the JavaScript `Number` type, which may or may not be sufficient. If you need to keep arbitrary length numerators and denominators, you should use the BigRat methods like `numerator = BigRat(rational.numerator.times(denominator)).over(rational.denominator).round()` and so on. – Peter Olson Oct 09 '13 at 02:17
  • @PeterOlson in all likelihood we'll be dealing with amounts small enough to be in a standard kitchen recipe so I doubt we're going much over the bounds of what Number will handle, at least as far as digits to the left of the decimal and given that we're attempting to in a sense make the numbers to the right of the decimal less precise I think this should be ok. – dstarh Oct 09 '13 at 02:26
  • Wrote a quick test harness http://jsfiddle.net/cKEWS/3/ which seems that it's working quite well, other than .3 which it can't seem to get to 1/3 unless I change the epsilon to .1 rather than .01. – dstarh Oct 09 '13 at 02:28
  • Never mind, 3/10s is exactly what the ruby lib shows in that scenario so it seems we're good there as well. This seems likely to be the winner. – dstarh Oct 09 '13 at 02:36
  • @dstarh The epsilon parameter controls how big the distance between the original value and the _simpler rational_ found can be. Of course the algorithm can be optimized. For example, you can skip all the even numbers in the loop except 2 (you can skip all the non-prime numbers in fact, but I think finding prime numbers will be harder than testing the non-prime in the loop). – Juan Sánchez Oct 09 '13 at 15:12
  • @JuanSánchez, yep, for what we're using it for it looks like it might work just fine. – dstarh Oct 09 '13 at 15:44
2

Use the .toFixed() method before using your library. See http://www.w3schools.com/jsref/jsref_tofixed.asp .

StackSlave
  • 10,613
  • 2
  • 18
  • 35
1

You could use .toFixed() to get a rounded, fixed precision version, then apply BigRational to that:

var n = 3.33333000540733337;
m = n.toFixed(2);       // 3.33

Alternatively, .toPrecision() will give a number to the specificied number of significant digits.

Reference: .toFixed() .toPrecision()

-1

I will try again. Presumably you tagged the question as 'math'. So let's look at the math.

  1. Fractions are rational numbers.
  2. Rational numbers are all of the form n divided by m, (n / m), where n and m are integers and m is not zero.
  3. You want a "mixed fraction".
  4. You can't expect to "round to the nearest fraction", whole or mixed, until you decide upon the denominator (m). If you chose m = 100, then you can round to the nearest 100th. If you choose 1 then you can round to the nearest integer. 2 to the nearest half, etc.
  5. Now that you have chosen the denominator, let's call it m, multiply your value (v) by m.
  6. Round the result to the nearest integer, call it rv.
  7. Your whole part of your mixed fraction will be floor (rv/m). The fractional part will be the (rv modulo m)/m (modulo means divide first by second and take the remainder as the result)

    Example v = 3.45.

    You want to round it to the nearest 1/3, so m = 3

    rv = round to nearest integer (3.45 * 3) = round (10.35) = 10

    whole part = floor (10/3) = 3

    fractional part = (10 modulo 3) / 3 = 1/3

Fred Mitchell
  • 2,145
  • 2
  • 21
  • 29
-6

Time to dust off your math. This is really elementary school math, but that is a bit easily forgotten. Why do you want 3 1/3?

You are trying to convert a rational number (all floats are rational numbers) to another rational number (all fractions are rational numbers).

So pick your denominator. Everything follows from that! (Reduce to lowest terms - unless you want to look like you dropped out of elementary school.)

Fred Mitchell
  • 2,145
  • 2
  • 21
  • 29
  • 1
    We are displaying recipe data and converting from something like 3 tablespoons of flour to 3 1/3 cups of flour. The conversion factors are all in grams, so we know a very precise number but need a much more approximate value. Thus needing to know 3 1/3 rather than 3.33333333334 or similar – dstarh Oct 09 '13 at 00:12
  • You are missing the point! Why 3 1/3 (10/3) instead of 3.25 (13/4), 3.5 (7/2), or 3.4 (17/5)? or 3.3 (330/100 reduced to 33/10)? – Fred Mitchell Oct 09 '13 at 00:38
  • 1
    because we're printing recipes and we need results in common measurements rather than anything "overly precise" – dstarh Oct 09 '13 at 00:47
  • 1
    Even if you think he's missing the point (which, by the way, I don't), I think you're been a tad too condescending. You can explain the concept just as well without telling people that they need to brush up on their math lest they look like an elementary school dropout. – Peter Olson Oct 09 '13 at 02:10