42

I'm trying to separate the decimal and integer parts of a double in swift. I've tried a number of approaches but they all run into the same issue...

let x:Double = 1234.5678
let n1:Double = x % 1.0           // n1 = 0.567800000000034
let n2:Double = x - 1234.0        // same result
let n3:Double = modf(x, &integer) // same result

Is there a way to get 0.5678 instead of 0.567800000000034 without converting to the number to a string?

user3925713
  • 509
  • 1
  • 4
  • 10

10 Answers10

101

You can use truncatingRemainder and 1 as the divider.

Returns the remainder of this value divided by the given value using truncating division.

Apple doc

Example:

let myDouble1: Double = 12.25
let myDouble2: Double = 12.5
let myDouble3: Double = 12.75

let remainder1 = myDouble1.truncatingRemainder(dividingBy: 1)
let remainder2 = myDouble2.truncatingRemainder(dividingBy: 1)
let remainder3 = myDouble3.truncatingRemainder(dividingBy: 1)

remainder1 -> 0.25
remainder2 -> 0.5
remainder3 -> 0.75
Nico
  • 6,269
  • 9
  • 45
  • 85
32

Same approach as Alessandro Ornano implemented as an instance property of FloatingPoint protocol:

Xcode 11 • Swift 5.1

import Foundation

extension FloatingPoint {
    var whole: Self { modf(self).0 }
    var fraction: Self { modf(self).1 }
}

1.2.whole    // 1
1.2.fraction // 0.2

If you need the fraction digits and preserve its precision digits you would need to use Swift Decimal type and initialize it with a String:

extension Decimal {
    func rounded(_ roundingMode: NSDecimalNumber.RoundingMode = .plain) -> Decimal {
        var result = Decimal()
        var number = self
        NSDecimalRound(&result, &number, 0, roundingMode)
        return result
    }
    var whole: Decimal { rounded(sign == .minus ? .up : .down) }
    var fraction: Decimal { self - whole }
}

let decimal = Decimal(string: "1234.99999999")!  // 1234.99999999
let fractional = decimal.fraction                // 0.99999999
let whole = decimal.whole                        // 1234
let sum = whole + fractional                     // 1234.99999999

let negativeDecimal = Decimal(string: "-1234.99999999")!  // -1234.99999999
let negativefractional = negativeDecimal.fraction         // -0.99999999
let negativeWhole = negativeDecimal.whole                 // -1234
let negativeSum = negativeWhole + negativefractional      // -1234.99999999
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    It doesn't work on `1234.567`. I changed my answer accordingly. –  Apr 15 '20 at 00:46
  • Any way to convert it back to a double? – ScottyBlades Jan 05 '21 at 11:30
  • 1
    @ScottyBlades `(decimal as NSDecimalNumber).doubleValue` – Leo Dabus Jan 05 '21 at 13:53
  • 1
    This answer works perfectly if you need precision – Lance Samaria Apr 19 '22 at 16:57
  • @LeoDabus I found a small error when converting the fractional part back to a `Double`. The initial num is `24.745093729987275`. I follow the code from your answer and get `let decimal = Decimal(string: num.description)!; let whole = decimal.whole; let fractional = decimal.fraction` which works fine. But when I convert the fractional to a Double `let f: Double = (fractional as NSDecimalNumber).doubleValue` I get `0.7450937299872751`. There is an extra `1` at the end. Or this num `27.305615820994717`, the fractional is rounded down to `0.30561582099471696` when converting it to a Double. – Lance Samaria Apr 19 '22 at 17:37
  • You can’t represent every value with double. Make sure to use Decimal only. Why do you need to convert to Double? You can try using a string to initialize your double but I recommend not using it at all – Leo Dabus Apr 19 '22 at 22:27
15

Swift 2:

You can use:

modf(x).1

or

x % floor(abs(x))
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • This also produces the same precision issue as with truncatingRemainder. For example `print(modf(35.46).1)` outputs `0.46000000000000085` which can break the equatable and comparable methods, sometimes flip the sign, and in some edge cases divide by zero depending on your logic. – ScottyBlades Jan 05 '21 at 11:23
14

Without converting it to a string, you can round up to a number of decimal places like this:

let x:Double = 1234.5678
let numberOfPlaces:Double = 4.0
let powerOfTen:Double = pow(10.0, numberOfPlaces)
let targetedDecimalPlaces:Double = round((x % 1.0) * powerOfTen) / powerOfTen

Your output would be

0.5678

  • 5
    Thanks for the answer. This is good, but unfortunately it doesn't work for me because I don't know the appropriate number of places in code beforehand. Guess I'll have to convert through strings :-/ – user3925713 Jul 14 '15 at 15:06
6

Swift 5.1

let x:Double = 1234.5678

let decimalPart:Double = x.truncatingRemainder(dividingBy: 1)    //0.5678
let integerPart:Double = x.rounded(.towardZero)                   //1234

Both of these methods return Double value.

if you want an integer number as integer part, you can just use

Int(x)
fantom
  • 486
  • 1
  • 6
  • 15
YodagamaHeshan
  • 4,996
  • 2
  • 26
  • 36
5

Use Float since it has less precision digits than Double

let x:Double = 1234.5678
let n1:Float = Float(x % 1)           // n1 = 0.5678
Ahmad
  • 12,336
  • 6
  • 48
  • 88
  • For the person who downvoted, could you please indicate any reason why you think this is bad answer? – Ahmad Jul 14 '15 at 02:11
  • 1
    By converting to `Float` you're simply throwing out more information for no benefit: that's going to give a *less* accurate answer (`0.567799985408782958984375` instead of `0.567800000000033833202905952930450439453125`). – Mark Dickinson Jul 14 '15 at 07:29
  • @MarkDickinson isn't that what the OP wanted to accomplish? – Ahmad Jul 14 '15 at 12:00
  • 3
    What, a value that's even *further* away from `0.5678`? I doubt it. – Mark Dickinson Jul 14 '15 at 14:44
3

There’s a function in C’s math library, and many programming languages, Swift included, give you access to it. It’s called modf, and in Swift, it works like this

// modf returns a 2-element tuple,

// with the whole number part in the first element,

// and the fraction part in the second element

let splitPi = modf(3.141592)

splitPi.0 // 3.0

splitPi.1 // 0.141592

You can create an extension like below,

extension Double {

    func getWholeNumber() -> Double {

        return modf(self).0

    }

    func getFractionNumber() -> Double {

        return modf(self).1

    }

}
Super Xtreem
  • 187
  • 1
  • 10
  • This was already posted by @AlessandroOrnano 6 years ago. Btw I have already posted a generic extension which works for Double as well – Leo Dabus Mar 09 '22 at 19:24
0

You can get the Integer part like this:

let d: Double = 1.23456e12

let intparttruncated = trunc(d)
let intpartroundlower = Int(d)

The trunc() function truncates the part after the decimal point and the Int() function rounds to the next lower value. This is the same for positive numbers but a difference for negative numbers. If you subtract the truncated part from d, then you will get the fractional part.

func frac (_ v: Double) -> Double
{
    return (v - trunc(v))
}

You can get Mantissa and Exponent of a Double value like this:

let d: Double = 1.23456e78

let exponent = trunc(log(d) / log(10.0))

let mantissa = d / pow(10, trunc(log(d) / log(10.0)))

Your result will be 78 for the exponent and 1.23456 for the Mantissa.

Hope this helps you.

j.s.com
  • 1,422
  • 14
  • 24
0

It's impossible to create a solution that will work for all Doubles. And if the other answers ever worked, which I also believe is impossible, they don't anymore.

let _5678 = 1234.5678.description.drop { $0 != "." } .description // ".5678"
Double(_5678)  // 0.5678

let _567 = 1234.567.description.drop { $0 != "." } .description // ".567"
Double(_567) // 0.5669999999999999
0
extension Double {

    /// Gets the decimal value from a double.
    var decimal: Double {
        Double("0." + string.split(separator: ".").last.string) ?? 0.0
    }
    
    var string: String {
        String(self)
    }
}

This appears to solve the Double precision issues.

Usage:

print(34.46979988898988.decimal) // outputs 0.46979988898988
print(34.46.decimal) // outputs 0.46
ScottyBlades
  • 12,189
  • 5
  • 77
  • 85