Zaphod
Zaphod

Reputation: 7270

Decimal to Double conversion in Swift 3

I'm migrating a project from Swift 2.2 to Swift 3, and I'm trying to get rid of old Cocoa data types when possible.

My problem is here: migrating NSDecimalNumber to Decimal.

I used to bridge NSDecimalNumber to Double both ways in Swift 2.2:

let double = 3.14
let decimalNumber = NSDecimalNumber(value: double)

let doubleFromDecimal = decimalNumber.doubleValue

Now, switching to Swift 3:

let double = 3.14
let decimal = Decimal(double)

let doubleFromDecimal = ???

decimal.doubleValue does not exist, nor Double(decimal), not even decimal as Double... The only hack I come up with is:

let doubleFromDecimal = (decimal as NSDecimalNumber).doubleValue

But that would be completely stupid to try to get rid of NSDecimalNumber, and have to use it once in a while...

Well, either I missed something obvious, and I beg your pardon for wasting your time, or there's a loophole needed to be addressed, in my opinion...

Thanks in advance for your help.

Edit : Nothing more on the subject on Swift 4.

Edit : Nothing more on the subject on Swift 5.

Upvotes: 74

Views: 63693

Answers (9)

Eric
Eric

Reputation: 11

If you import Swift Charts, you can use .primitivePlottable

import Charts

let x: Decimal = 3.14159
let y: Double = x.primitivePlottable // 3.14159

But that's definitely not the intended way of doing it.

Upvotes: 1

Zgpeace
Zgpeace

Reputation: 4465

For swift 5, the function is

let doubleValue = Double(truncating: decimalValue as NSNumber)

the example in the below, show the number of float.

let decimalValue: Decimal = 3.14159
let doubleValue = Double(truncating: decimalValue as NSNumber)
print(String(format: "%.3f", doubleValue))  // 3.142
print(String(format: "%.4f", doubleValue)) // 3.1416
print(String(format: "%.5f", doubleValue)) // 3.14159
print(String(format: "%.6f", doubleValue)) // 3.141590
print(String(format: "%.7f", doubleValue)) // 3.1415900

Upvotes: 4

Pranavan SP
Pranavan SP

Reputation: 1865

Solution that works in Swift 4

let double = 3.14
let decimal = Decimal(double)
let doubleFromDecimal = NSDecimalNumber(decimal: decimal).doubleValue
print(doubleFromDecimal)

Upvotes: 18

Eugene Dudnyk
Eugene Dudnyk

Reputation: 6030

In Swift open source, the implementation is actually done in Decimal.swift, but it is private. You can re-use the code from there.

extension Double {
    @inlinable init(_ other: Decimal) {
        if other._length == 0 {
            self.init(other._isNegative == 1 ? Double.nan : 0)
            return
        }

        var d: Double = 0.0
        for idx in (0..<min(other._length, 8)).reversed() {
            var m: Double
            switch idx {
            case 0: m = Double(other._mantissa.0)
                break
            case 1: m = Double(other._mantissa.1)
                break
            case 2: m = Double(other._mantissa.2)
                break
            case 3: m = Double(other._mantissa.3)
                break
            case 4: m = Double(other._mantissa.4)
                break
            case 5: m = Double(other._mantissa.5)
                break
            case 6: m = Double(other._mantissa.6)
                break
            case 7: m = Double(other._mantissa.7)
                break
            default: break
            }
            d = d * 65536 + m
        }

        if other._exponent < 0 {
            for _ in other._exponent..<0 {
                d /= 10.0
            }
        } else {
            for _ in 0..<other._exponent {
                d *= 10.0
            }
        }
        self.init(other._isNegative != 0 ? -d : d)
    }
}

Upvotes: 2

rmaddy
rmaddy

Reputation: 318814

Another solution that works in Swift 3 is to cast the Decimal to NSNumber and create the Double from that.

let someDouble = Double(someDecimal as NSNumber)

As of Swift 4.2 you need:

let someDouble = Double(truncating: someDecimal as NSNumber)

Upvotes: 34

Rafał Sroka
Rafał Sroka

Reputation: 40030

Swift 5

let doubleValue = Double(truncating: decimalValue as NSNumber)

Upvotes: 13

eonil
eonil

Reputation: 85985

You are supposed to use as operator to cast a Swift type to its bridged underlying Objective-C type. So just use as like this.

let p = Decimal(1)
let q = (p as NSDecimalNumber).doubleValue

In Swift 4, Decimal is NSDecimalNumber. Here's citation from Apple's official documentation in Xcode 10.

Important

The Swift overlay to the Foundation framework provides the Decimal structure, which bridges to the NSDecimalNumber class. For more information about value types, see Working with Cocoa Frameworks in Using Swift with Cocoa and Objective-C (Swift 4.1).

There's no NSDecimal anymore. There was confusing NSDecimal type in Swift 3, but it seems to be a bug. No more confusion.

Note

I see the OP is not interested in Swift 4, but I added this answer because mentioning only about (outdated) Swift 3 made me confused.

Upvotes: 4

sketchyTech
sketchyTech

Reputation: 5906

NSDecimalNumber and Decimal are bridged

The Swift overlay to the Foundation framework provides the Decimal structure, which bridges to the NSDecimalNumber class. The Decimal value type offers the same functionality as the NSDecimalNumber reference type, and the two can be used interchangeably in Swift code that interacts with Objective-C APIs. This behavior is similar to how Swift bridges standard string, numeric, and collection types to their corresponding Foundation classes. Apple Docs

but as with some other bridged types certain elements are missing.

To regain the functionality you could write an extension:

extension Decimal {
    var doubleValue:Double {
        return NSDecimalNumber(decimal:self).doubleValue
    }
}

// implementation
let d = Decimal(floatLiteral: 10.65)
d.doubleValue

Upvotes: 44

user28434&#39;mstep
user28434&#39;mstep

Reputation: 6600

Decimal in Swift 3 is not NSDecimalNumber. It's NSDecimal, completely different type.

You should just keep using NSDecimalNumber as you did before.

Upvotes: 3

Related Questions