Swift 3中的十进制转双精度浮点数转换

68

我正在将一个项目从 Swift 2.2 迁移到 Swift 3,并且尽可能地试图摆脱旧的 Cocoa 数据类型。

我的问题在于:将 NSDecimalNumber 迁移到 Decimal

Swift 2.2 中,我曾经双向桥接 NSDecimalNumberDouble

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

let doubleFromDecimal = decimalNumber.doubleValue

现在,切换到Swift 3

let double = 3.14
let decimal = Decimal(double)

let doubleFromDecimal = ???

decimal.doubleValue不存在,也没有Double(decimal),甚至没有decimal as Double...... 我想到的唯一方法是:

let doubleFromDecimal = (decimal as NSDecimalNumber).doubleValue

但是完全傻瓜式地试图摆脱 NSDecimalNumber 并不明智,因为有时候也需要使用它...

嗯,要么我错过了一些显而易见的东西,如果是这样,我请求您的原谅浪费了您的时间,否则,在我看来需要解决一个漏洞...

先感谢您的帮助。

编辑:关于Swift 4,没有更多关于此主题的信息。

编辑:关于Swift 5,没有更多关于此主题的信息。


是的,在2018年仍然很遗憾...我犹豫是否转移到Decimal的唯一原因是所有数学运算(包括字面转换,如myDecimal + 1)和比较都比NSDecimalNumber更直观。 - Leonid Usov
10个回答

39

NSDecimalNumberDecimal 是桥接的。

Foundation 框架中的 Swift 覆盖层提供了 Decimal 结构,它与 NSDecimalNumber 类桥接。Decimal 值类型提供与 NSDecimalNumber 引用类型相同的功能,两者可在与 Objective-C API 交互的 Swift 代码中互换使用。此行为类似于 Swift 将标准字符串、数字和集合类型桥接到它们对应的 Foundation 类的方式。Apple Docs

但是,与其他某些桥接类型一样,某些元素缺失。

要恢复功能,您可以编写扩展:

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

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

1
问题仍然是相同的,即使它被一个扩展隐藏了。你仍然必须使用第三种类型,而不仅仅是 DoubleDecimal... - Zaphod
2
投票支持这个在Swift 4中被接受的答案。虽然不是理想的解决方案,但Decimal类型最好能够在Swift语言中提供一个干净的double转换。 - Peter Suwara

30

在 Swift 3 中另一个可行的解决方案是将 Decimal 转换为 NSNumber,然后从中创建 Double

let someDouble = Double(someDecimal as NSNumber)

从Swift 4.2开始,您需要:

let someDouble = Double(truncating: someDecimal as NSNumber)

5
在Swift 4中,“let c = Double(truncating:a as NSNumber)”意思是将变量a转换为NSNumber类型,然后将其截断为Double类型并赋值给变量c。 - vikingosegundo
@vikingosegundo 这是Swift 4。在Swift 3中,您不需要使用“truncating”。 - rmaddy
1
是的,但你仍然需要转换为第三种类型... 你不能只使用 DoubleDecimal,你需要使用 NSDecimalNumberNSNumber... - Zaphod
4.2的解决方案仍适用于Swift 5.8.2。 - iago849

15

适用于 Swift 4 的解决方案

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

是的,但你仍然需要转换为第三种类型... 你不能只使用 DoubleDecimal,你需要使用 NSDecimalNumberNSNumber... 这就是这篇文章的重点... - Zaphod
@Zaphod 你有检查过吗? - Pranavan SP
1
确实它能工作...但你仍然使用了第三种类型,这里是String,使用description属性。整个帖子背后的想法是代码的效率和清晰度问题。关键是类型Decimal旨在替代老旧的NSDecimalNumber,但不能透明地使用而不使用黑客(通过NSNumberNSDecimalNumberString、指针或其他方式...)。这个问题更多的是修辞性的,而不是开发方面的...而且缺失的方法,在Swift 4中仍然缺失。 - Zaphod
1
永远不要依赖于任何类型的.description属性。这种做法注定会在某个时候出问题。 - alexkent
description 的使用方式的回答很糟糕。 - Isaaс Weisberg

10

Swift 5

let doubleValue = Double(truncating: decimalValue as NSNumber)

2
确实它能工作...但你仍然使用了第三种类型,这里是NSNumber。这篇文章背后的整个思想是代码的效率和清晰度问题。关键是类型Decimal旨在替代老旧的NSDecimalNumber,但不能透明地使用而不使用黑客技巧(通过NSNumberNSDecimalNumberString、指针或其他方式...)。这个问题更多的是修辞性的,而不是开发方面的...而且缺失的方法,在Swift 5中仍然缺失。 - Zaphod

3

Swift 3 中的 Decimal 不是 NSDecimalNumber。它是完全不同的类型,即 NSDecimal

您应该像以前一样继续使用 NSDecimalNumber


4
实际上,Decimal 对应于 NSDecimalNumberSwift 对 Foundation 框架提供了 Decimal 结构体,可以桥接到 NSDecimalNumber 类。Decimal 值类型提供与 NSDecimalNumber 引用类型相同的功能,两者可以在与 Objective-C API 交互的 Swift 代码中互换使用。 https://developer.apple.com/reference/foundation/nsdecimalnumber - Alexander
@AlexanderMomchliov,除非没有NSDecimalNumber的方法可用,否则您仍需要将Decimal转换为NSDecimalNumber才能使用它们。来源:我刚在playground中检查了一下。 - user28434'mstep
2
是的,我并没有这样说。我只是说你假设 Decimal 对应于 NSDecimal 是错误的。 - Alexander
1
@AlexanderMomchliov,但是它是NSDecimal,只需将Swift 3生成的标头中的Decimal与Foundation C代码中的NSDecimal进行比较即可。仅因为Swift在导入时将旧Foundation代码的Decimal变量桥接到NSDecimalNumber并不意味着Decimal变成了NSDecimalNumber - user28434'mstep
2
尽管文档中说Decimal可以转换为NSDecimalNumber,但根据我的经验,现在最安全的做法是坚持使用NSDecimalNumber,并等待库的稳定(我试图将其转换为Decimal,但结果比我预期的更混乱,而且导致代码崩溃)。 - Jeff Lewis

3
您需要使用as运算符将Swift类型转换为其底层的Objective-C类型。只需像这样使用as即可。
let p = Decimal(1)
let q = (p as NSDecimalNumber).doubleValue

在Swift 4中,Decimal 就是 NSDecimalNumber。以下是来自Xcode 10的苹果官方文档的引用。

重要提示

Foundation框架的Swift覆盖层提供了Decimal结构,可桥接到NSDecimalNumber类。有关值类型的更多信息,请参见"在使用Swift与Cocoa和Objective-C一起使用时处理Cocoa框架"(Swift 4.1)。

不再有NSDecimal。 在Swift 3中存在令人困惑的NSDecimal类型,但这似乎是一个错误。 不再有混淆。

注意

我看到OP对Swift 4不感兴趣,但我添加了这个答案,因为只提到(过时的)Swift 3让我感到困惑。

这里指的不是底层结构,而是语法问题。我知道你必须使用“as”运算符来转换为桥接类型。我的问题只是,“如果您仍然需要偶尔引用它,那么使用Decimal而不是NSDecimalNumber有什么意义?”,例如获取双精度值...没有办法透明地从NSDecimalNumber更改代码到Decimal...这就是我想说的...并且在Swift 4中仍然是如此(正如编辑中所示)。 - Zaphod
@Zaphod 重点在于“使用适当的值类型”。你应该知道,许多 NS- 类实际上是设计为值类型的,但由于 OBJC 缺乏语言结构而实现为类。在我看来,苹果希望为 Swift 用户提供更多“Swift-y”库,但他们对 Swift 的过渡还没有完成。在苹果库中有许多这样的“不完整”部分,它们很可能会在以后更新。 - eonil
在我看来,一旦苹果成功地转向Swift,他们会逐步删除对OBJC部分的访问。 - eonil
那正是我的观点...使用Decimal时,转换并不完全结束... - Zaphod
桥接到类型并不意味着成为该类型。String 桥接到 NSStringDictionary 桥接到 NSMutableDictionary,但它们是完全独立的类型。更多阅读:https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/working_with_foundation_types - Ky -

3

对于 Swift 5,该函数为

let doubleValue = Double(truncating: decimalValue as NSNumber)

下面的示例显示了浮点数的数量。
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

1
确实它可以工作...但你仍然使用了第三种类型,这里是NSNumber。这篇文章背后的整个思想是代码的效率和清晰度问题。关键是类型Decimal旨在替代老旧的NSDecimalNumber,但不能透明地使用而不使用黑客技巧(通过NSNumberNSDecimalNumberString、指针或其他方式...)。这个问题更多的是修辞性的,而不是开发方面的...而且缺失的方法,在Swift 5中仍然缺失。 - Zaphod

2
在 Swift 开源中,实现 实际上是在 Decimal.swift 中完成的,但它是私有的。您可以从那里重新使用代码。
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)
    }
}

确实如此,但真正的问题仍然存在,为什么doubleValue被声明为internal而不是明显应该是public...也许需要进行拉取请求...但我不知道如何提出... ‍♂️ - Zaphod
您可以在Swift论坛上创建进化建议,您将获得有关当前实现的反馈和推理以及有关该建议的考虑事项。 - Eugene Dudnyk
好的,我会把它加入我的待办事项列表中... - Zaphod

0
如果你导入了Swift Charts,你可以使用`.primitivePlottable`。
import Charts

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

但这绝对不是预期的做法。

哈哈!太棒了!确实这不是预期的做法,但我喜欢它! - Zaphod

-1

let decimalNumber = Decimal(10.5) let doubleNumber = Double(decimalNumber)

print(doubleNumber) // 输出:10.5


这段代码无法编译。请你花点心思来正确格式化你的回答。要么将代码缩进4个空格,要么用三个反引号(```)包裹代码行。 - HangarRash
请在您的回答中添加一些解释,以便其他人可以从中学习。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接