在Swift中获取double类型的小数部分

43

我想在Swift中将一个双精度浮点数的小数部分和整数部分分开。我尝试了许多方法,但它们都遇到了同样的问题...

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

有没有一种方法可以获得0.5678而不是将数字转换为字符串再进行操作?


1
请查看此链接:https://dev59.com/G3RB5IYBdhLWcg3wj36c - vacawama
10个回答

103

你可以使用truncatingRemainder1作为除数。

使用截断除法,返回此值除以给定值的余数。

苹果文档

示例:

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

simple and precise - DazChong
1
let d: Double = 12.2 let rem = d.truncatingRemainder(dividingBy: 1) //0.1999999999999993 - protspace
@protspace 0.19999999999999930.20 是相同的。您只需要在向用户显示时使用 NumberFormatter 即可。如果您需要更高的精度,可以使用 Decimal 并使用字符串初始化器。https://dev59.com/G3RB5IYBdhLWcg3wj36c - Leo Dabus
@LeoDabus 谢谢,但是如果我在逻辑中稍后需要这个0.2怎么办?你如何处理? - protspace
@protspace 抱歉回复晚了。如果需要更高的精度,您可以使用“Decimal”。 - Leo Dabus
我不建议这样做,因为存在精度问题。 - ScottyBlades

32

与Alessandro Ornano实现的浮点数协议示例属性相同:

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
如果您需要小数位并保留其精度,则需要使用Swift Decimal类型,并用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

1
它在1234.567上不起作用。我相应地改变了我的答案。 - user652038
有什么办法将它转换回双精度类型吗? - ScottyBlades
1
@ScottyBlades (decimal as NSDecimalNumber).doubleValue @(将十进制数转换为NSDecimalNumber类型后)取其双精度值。 - Leo Dabus
1
如果您需要精度,此答案完美适用。 - Lance Samaria
@LeoDabus 我在将小数部分转换回 Double 时发现了一个小错误。初始数字是 24.745093729987275。我按照你的答案中的代码进行操作,得到了 let decimal = Decimal(string: num.description)!; let whole = decimal.whole; let fractional = decimal.fraction,这个方法运行良好。但是当我将小数部分转换为 Double 时,let f: Double = (fractional as NSDecimalNumber).doubleValue,我得到了 0.7450937299872751。末尾多了一个 1。或者对于这个数字 27.305615820994717,当将其转换为 Double 时,小数部分被舍入为 0.30561582099471696 - Lance Samaria
你不能用double来表示每一个值。请确保只使用Decimal。为什么需要转换成Double?你可以尝试使用字符串来初始化你的double,但我建议根本不要使用它。 - Leo Dabus

16

Swift 2

您可以使用:

modf(x).1
x % floor(abs(x))

这也会产生与截断余数相同的精度问题。例如,print(modf(35.46).1)输出0.46000000000000085,这可能会破坏可比性和相等性方法,有时会翻转符号,在某些边缘情况下根据逻辑导致除以零。 - ScottyBlades

14

不将其转换为字符串,您可以像这样四舍五入到小数点后几位:

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

你的输出将会是

0.5678


5
谢谢回答。这很好,但不幸的是对我没用,因为我事先不知道代码中适当的位数。猜测我得通过字符串转换:-/ - user3925713

7

Swift 5.1

let x:Double = 1234.5678

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

这两种方法都返回 Double 类型的值。

如果你想要一个整数作为整数部分,你可以直接使用:

Int(x)

5
使用Float,因为它比Double具有更少的精度数字。
let x:Double = 1234.5678
let n1:Float = Float(x % 1)           // n1 = 0.5678

给那位点踩的人,您能否指出您认为这是一个不好的回答的原因? - Ahmad
1
通过转换为Float,您只是在没有任何好处的情况下丢弃了更多信息:这将给出一个不太准确的答案(0.567799985408782958984375而不是0.567800000000033833202905952930450439453125)。 - Mark Dickinson
@MarkDickinson 这不是原帖作者想要实现的吗? - Ahmad
3
“什么?一个比0.5678更远的值?我怀疑这不可能。” - Mark Dickinson

3

C语言的数学库中有一个函数,许多编程语言也包含了这个函数,Swift也不例外。它被称为modf,在Swift中使用方法如下:

// modf返回一个两个元素的元组

// 第一个元素是整数部分,第二个元素是小数部分

let splitPi = modf(3.141592)

splitPi.0 // 3.0

splitPi.1 // 0.141592

你可以创建以下扩展来使用它:

extension Double {

    func getWholeNumber() -> Double {

        return modf(self).0

    }

    func getFractionNumber() -> Double {

        return modf(self).1

    }

}

这个问题已经在6年前被@AlessandroOrnano发布过了。顺便说一句,我已经发布了一个通用的扩展,也适用于Double。 - Leo Dabus

0
您可以这样获取整数部分:
let d: Double = 1.23456e12

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

trunc()函数截取小数点后的部分,Int()函数则将其四舍五入至下一个较低的值。对于正数而言,这两个函数的作用相同,但对于负数则有所不同。如果你从d中减去被截取的部分,那么你就会得到小数部分。

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

您可以通过以下方式获取Double值的尾数和指数:
let d: Double = 1.23456e78

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

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

你的结果将会是指数为78,尾数为1.23456。

希望这能对你有所帮助。


0

不可能创建一个适用于所有Double的解决方案。如果其他答案曾经有效过,我也认为这是不可能的,但它们现在已经不再有效。

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)
    }
}

这似乎解决了双精度问题。

用法:

print(34.46979988898988.decimal) // outputs 0.46979988898988
print(34.46.decimal) // outputs 0.46

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