3

I'm trying to make a function to create the fraction version of a floating point number in my app with Swift. It's working perfectly right now, except when it has to build a mixed number (whole and fraction part).

As an example below, when I call the function with 0.4 it works fine, but not with 1.4 due to it has a whole part (1). It seems when I substract the whole part (integerPart) to the original quantity it loses precision. You can check that directly in a playground.

Examples:

0.4 -> 2/5  
1.4 -> 1 2/5  
2.4 -> 2 2/5  
0.5 -> 1/2  
0.7 -> 7/10  
etc...  



func fractionize(quantity: Float) -> String {

let integerPart: Float = floor(quantity);
var numerator: Float = quantity - integerPart;
var firstNumerator: Float = numerator;
var denominator: Float = 1;

if (isInteger(quantity)) {

    return "\(Int(integerPart))";

} else {

    do {
        denominator++;
        numerator = firstNumerator * denominator;
        println(numerator);
    } while (!isInteger(numerator) && denominator <= 10);

    if (integerPart > 0) {

        if (isInteger(numerator)) {
            return "\(integerPart) \(Int(numerator))/\(Int(denominator))";
        } else {
            return "\(quantity) couldn't be fractionized. Result = \(integerPart) \(numerator) / \(denominator)";
        }

    } else {

        if (isInteger(numerator)) {
            return "\(Int(numerator))/\(Int(denominator))";
        } else {
            return "\(quantity) couldn't be fractionized. Result = \(numerator) / \(denominator)";
        }

    }

}

}

fractionize(1.4);

As an extra example, it's working perfectly with 1.5 but not with 1.4, 2.4, 3.4, etc... due to exactly the same. I don't know how to make a good substraction so that the isInteger method works fine. This is my isInteger function. I already tested it and it's working fine:

func isInteger(quantity: Float) -> Bool {
    return floor(quantity) == quantity;
}

Check in a playground and you'll see what happens when you try to fractionize 1.3 or 1.4 for example.

  • 3
    Most probably a duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken). – Martin R Jul 21 '15 at 14:32
  • 2
    Note that 1.5 can be represented exactly as a binary floating point number, but 1.4, 2.4, 3.4, ... can *not*. – Martin R Jul 21 '15 at 14:34
  • 1
    @MartinR what do you mean with "binary floating point", and how can I solve my issue to make the function work fine? I also tried before with double with no success. – Sergio Daniel L. García Jul 21 '15 at 14:38
  • 2
    `Float` and `Double` use a *binary* representation and cannot store the value 1.4 exactly. It is all explained in much detail in the referenced thread. I don't want to sound rude, but you have to read and understood that before it makes sense to suggest a solution. – Martin R Jul 21 '15 at 14:40
  • 1
    Another standard reference is ["What Every Computer Scientist Should Know About Floating-Point Arithmetic"](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). – Martin R Jul 21 '15 at 14:41
  • I'm reading right now and somehow I understand the problem. But what I don't know yet is how to make it work for my case. Is there any way to avoid that behavior? – Sergio Daniel L. García Jul 21 '15 at 14:43
  • 1
    Ah, the weekly "my floating point operation doesn't work" question… Martin is right, you really, _really_ need to read up about how floating point numbers are represented in computers and how they work. This is one of the few low-level things a developer _must_ understand, even when only working in high-level languages. – DarkDust Jul 21 '15 at 14:43
  • Where does the `quantity` come from? Once it is stored in a Double or Float, the precision is lost and – for example – 1.4 is *indistinguishable* from 1.39999995. One possible solution is to use a *decimal* representation as suggested below. If you have to work with Float or Double then you can do the conversion only *approximately*. A well-known algorithm is to use "continued fractions", and a Swift implementation can be found in http://stackoverflow.com/a/28352004/1187415. – Martin R Jul 21 '15 at 14:57

2 Answers2

2

If you need to rely on exact number representations, you might want to look into NSDecimalNumber as "normal" floating point numbers cannot express some decimal numbers exactly. See also this nice tutorial.

DarkDust
  • 90,870
  • 19
  • 190
  • 224
2

You should calculate by an integer to avoid the floating point precision issue. Therefore, convert the float to an integer at first.

Is what you want the following code?

func gcd(var m: Int, var n: Int) -> Int {
    if m < n {
        (m, n) = (n, m)
    }
    if n == 0 {
        return m
    } else if m % n == 0 {
        return n
    } else {
        return gcd(n, m % n)
    }
}

func fractionize(var quantity: Float) -> String {
    var i = 0
    while quantity % 1 != 0 {
        quantity = quantity * 10
        i += 1
    }

    var numerator = Int(quantity)
    var denominator = Int(pow(Double(10), Double(i)))

    let divisor = gcd(numerator, denominator)

    numerator /= divisor
    denominator /= divisor

    var wholeNumber = 0
    if numerator > denominator {
        wholeNumber = numerator / denominator
        numerator -= denominator * wholeNumber
    }

    if wholeNumber > 0 {
        return "\(wholeNumber) \(numerator)/\(denominator)"
    } else {
        return "\(numerator)/\(denominator)"
    }
}

println(fractionize(0.4)) // 2/5
println(fractionize(1.4)) // 1 2/5
println(fractionize(2.4)) // 2 2/5
println(fractionize(0.5)) // 1/2
println(fractionize(0.7)) // 7/10
kishikawa katsumi
  • 10,418
  • 1
  • 41
  • 53