如何在Swift中将小数截断为x位

21
我有一个非常长的十进制数(比如说 17.9384693864596069567),我想将小数部分截断到几位小数(输出结果应该是 17.9384)。我不想对数字四舍五入为17.9385。如何实现这个功能?

1
可能是 https://dev59.com/gGAg5IYBdhLWcg3wBXER 的重复问题。 - dokun1
以下两种解决方案都是完全有效的。 - owlswipe
当然,我只是给你展示另一个 :) - dokun1
10个回答

47

你可以通过将其作为Double的扩展来进一步整理它:

extension Double {
    func truncate(places : Int)-> Double {
        return Double(floor(pow(10.0, Double(places)) * self)/pow(10.0, Double(places)))
    }
}

你可以像这样使用它:

var num = 1.23456789
// return the number truncated to 2 places
print(num.truncate(places: 2))

// return the number truncated to 6 places
print(num.truncate(places: 6))

2
我喜欢它!如此有条理,当你使用它时,它实际上是有意义的 :)。 - owlswipe
2
很高兴你喜欢它 - 这是我编写的第一个扩展程序,所以今天我们都学到了一些东西 :-) - Russell
10
这在小于1的数字上不起作用 :( 如果你使用 0.23456789.truncate(2),你会得到0.23000000000000001 - Jeroen Bakker
这个值怎么办:“2315385.01”, 它给出了 2315385, 是错误的。 - Samarth Kejriwal
1
Jeroen Bakker - 我不知道你为什么这样想,因为它确实返回0.23。@Samarth Kejriwal - 这很奇怪。它对于魔法数字两侧的数字都不会这样做。 - Russell
@Russell 我想要将 "2315385.01" 截断后得到的结果仍为 "2315385.01",但实际结果却是 2315385。请问有什么修改建议吗? - Samarth Kejriwal

12
您可以简单地保持它如下:
String(format: "%.0f", ratio*100)

在这里,0代表您想要允许的小数位数,本例中为零。 Ratio是一种像0.5556633这样的双精度浮点数。

希望这有所帮助。

如果你想把 double 类型作为字符串使用,这就是正确的答案。 - Jonathan

9

我已经弄清楚了。

只需将数字向下取整,使用一些花哨的技巧即可。

let x = 1.23556789
let y = Double(floor(10000*x)/10000) // leaves on first four decimal places
let z = Double(floor(1000*x)/1000) // leaves on first three decimal places
print(y) // 1.2355
print(z) // 1.235

所以,将数字乘以1,0的个数为你想要的小数位数,向下取整,再除以你所乘的数字。就这样。


6
extension Double {
    /// Rounds the double to decimal places value
    func roundToPlaces(_ places:Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
    func cutOffDecimalsAfter(_ places:Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self*divisor).rounded(.towardZero) / divisor
    }
}

let a:Double = 1.228923598

print(a.roundToPlaces(2)) // 1.23
print(a.cutOffDecimalsAfter(2)) // 1.22

5
特定小数位的代码如下:
let number = 17.9384693864596069567;
let merichle = Float(String(format: "%.1f", (number * 10000)).dropLast(2))!/10000

//merichle = 17.9384

最终,你的数字将被截断而没有四舍五入...

如果你想要一个字符串输出而不是一个双精度浮点数,那看起来这个代码就没问题了。 - owlswipe
1
如果您想要一个双精度数字,可以使用以下代码: Double(String(format: "%.2f", b)) - Merichle
好的,我觉得这个方式看起来不错 :) - owlswipe
2
@RamazanKarami String(format: "%.2f", b) 会将 double 四舍五入,而不是截断它。 - George
@George 是的,你说得对。答案已经通过新的编辑进行了更正,没有四舍五入的数字。感谢你的跟进。 - Merichle
让数字等于1.99999999,你的代码将返回merichle = 2。 - Cœur

4

在Swift 5中,也可以通过为Decimal创建扩展来进行截断到小数。

extension Decimal {
  func truncation(by digit: Int) -> Decimal {
    var initialDecimal = self
    var roundedDecimal = Decimal()
    NSDecimalRound(&roundedDecimal, &initialDecimal, digit, .plain)
    return roundedDecimal
  }

使用十进制值的用例

value = Decimal(2.56430).truncation(by:2)

值为 2.560000(截断后)


我喜欢这个想法,但是有一个错误。它实际上是四舍五入,而不是截断。 .plain 会导致 NSDecimalRound 向上或向下舍入。将其替换为 .down,以始终向下舍入,这将使其具有截断行为。 - stef
@stef 这是相对的。如果数字是负数,你需要使用 .up。请查看此帖子 - Leo Dabus

3

拖放解决方案,IOS,Swift 4

将此代码复制到您的应用程序中...

import Foundation

func truncateDigitsAfterDecimal(number: Double, afterDecimalDigits: Int) -> Double {
   if afterDecimalDigits < 1 || afterDecimalDigits > 512 {return 0.0}
   return Double(String(format: "%.\(afterDecimalDigits)f", number))!
}

然后你可以像这样调用这个函数:

truncateDigitsAfterDecimal(number: 45.123456789, afterDecimalDigits: 3)

将会产生以下结果:

45.123

这将四舍五入,而不是截断。 - ninahadi

2

SwiftUI: 如果您想在视图中格式化输出,但不涉及计算,那么SwiftUI提供了一种方便的方式来使用C格式说明符作为Text()函数的一部分。

import SwiftUI

let myDouble = 17.93846938645960695
Text("\(myDouble, specifier: "%.2f")")
///Device display: 17.94

以上代码将Double的内容直接输出到视图,正确舍入到小数点后两位,并保留Double值以供进一步计算。如果作为SwiftUI用户对C语言格式说明符不熟悉,可以访问此链接获取有用信息:https://en.wikipedia.org/wiki/Printf_format_string

3
你的解决方案是将小数四舍五入...重新阅读一遍问题。 - Yeheshuah

1

方法1:如果您不想为此创建任何新函数,您可以直接按以下方式获取四舍五入后的值。

var roundedValue = (decimalValue * pow(10.0, Double(numberOfPlaces))).rounded())/pow(10.0, Double(numberOfPlaces)

例子:

var numberOfPlaces = 2
var decimalValue = 13312.2423423523523

print("\(((decimalValue * pow(10.0, Double(numberOfPlaces))).rounded())/pow(10.0, Double(numberOfPlaces)))")

结果:13312.24



方式二:如果你只想打印,可以使用以下代码:

print(String(format: "%.\(numberOfPlaces)f",decimalValue))

例子
var numberOfPlaces = 4
var decimalValue = 13312.2423423523523

print(String(format: "%.\(numberOfPlaces)f",decimalValue))

0

Swift 5.2的答案

我查看了很多答案,但在截断时总是遇到转换问题。根据我的数学知识,通过截断,如果我有3.1239并且我想要3个小数位,那么我将得到3.123而不是四舍五入(!= 3.1234)。

也许由于过程的性质,我总是成功地使用Double,但总是在Float上遇到问题。

我的方法是创建一个BinaryFloatingPoint的扩展,以便我可以重用它来处理Float、CGFLoat、Double等。

以下扩展获取BinaryFloatingPoint,并可以返回具有给定numberOfDecimals的String或BinaryFloatingPoint值,并处理不同类型的情况:

extension Numeric where Self: BinaryFloatingPoint {
    
    /// Retruns the string value of the BinaryFloatingPoint. The initiaiser
    var toString: String {
        return String(describing: self)
    }
    
    /// Returns the number of decimals. It will be always greater than 0
    var numberOfDecimals: Int {
        return toString.count - String(Int(self)).count - 1
    }
   
    /// Returns a Number with a certain number of decimals
    /// - Parameters:
    ///   - Parameter numberOfDecimals: Number of decimals to return
    /// - Returns: BinaryFloatingPoint with number of decimals especified
    func with(numberOfDecimals: Int) -> Self {
        let stringValue = string(numberOfDecimals: numberOfDecimals)
        if self is Double {
            return Double(stringValue) as! Self
        } else {
            return Float(stringValue) as! Self
        }
    }
    
    /// Returns a string representation with a number of decimals
    /// - Parameters:
    ///   - Parameter numberOfDecimals: Number of decimals to return
    /// - Returns: String with number of decimals especified
    func string(numberOfDecimals: Int) -> String {
        let selfString = toString
        let selfStringComponents = selfString.components(separatedBy: ".")
        let selfStringIntegerPart = selfStringComponents[0]
        let selfStringDecimalPart = selfStringComponents[1]
        
        if numberOfDecimals == 0 {
            return selfStringIntegerPart
        } else {
            if selfStringDecimalPart.count == numberOfDecimals {
                return [selfStringIntegerPart,
                        selfStringDecimalPart].joined(separator: ".")
            } else {
                if selfStringDecimalPart.count > numberOfDecimals {
                    return [selfStringIntegerPart,
                            String(selfStringDecimalPart.prefix(numberOfDecimals))].joined(separator: ".")
                } else {
                    let difference = numberOfDecimals - selfStringDecimalPart.count
                    let addedCharacters = [Character].init(repeating: "0", count: difference)
                    
                    return [selfStringIntegerPart,
                            selfStringDecimalPart+addedCharacters].joined(separator: ".")
                }
            }
        }
    }
        
}

它可能看起来有些老派,但我的所有测试都通过了:

func test_GivenADecimalNumber_ThenAssertNumberOfDecimalsWanted() {
   //No decimals
    XCTAssertEqual(Float(3).with(numberOfDecimals: 0), 3)
    XCTAssertEqual(Float(3.09).with(numberOfDecimals: 0), 3)
    XCTAssertEqual(Float(3.999).with(numberOfDecimals: 0), 3)
    
    XCTAssertEqual(Double(3).with(numberOfDecimals: 0), 3)
    XCTAssertEqual(Double(3.09).with(numberOfDecimals: 0), 3)
    XCTAssertEqual(Double(3.999).with(numberOfDecimals: 0), 3)
    
    
    //numberOfDecimals == totalNumberOfDecimals
    XCTAssertEqual(Float(3.00).with(numberOfDecimals: 2), 3.00)
    XCTAssertEqual(Float(3.09).with(numberOfDecimals: 2), 3.09)
    XCTAssertEqual(Float(3.01).with(numberOfDecimals: 2), 3.01)
    XCTAssertEqual(Float(3.999).with(numberOfDecimals: 3), 3.999)
    XCTAssertEqual(Float(3.991).with(numberOfDecimals: 3), 3.991)
    
    XCTAssertEqual(Double(3.00).with(numberOfDecimals: 2), 3.00)
    XCTAssertEqual(Double(3.09).with(numberOfDecimals: 2), 3.09)
    XCTAssertEqual(Double(3.01).with(numberOfDecimals: 2), 3.01)
    XCTAssertEqual(Double(3.999).with(numberOfDecimals: 3), 3.999)
    XCTAssertEqual(Double(3.991).with(numberOfDecimals: 3), 3.991)
    
    
    //numberOfDecimals < totalNumberOfDecimals
    XCTAssertEqual(Float(3.00).with(numberOfDecimals: 1), 3.0)
    XCTAssertEqual(Float(3.09).with(numberOfDecimals: 1), 3.0)
    XCTAssertEqual(Float(3.01).with(numberOfDecimals: 1), 3.0)
    XCTAssertEqual(Float(3.999).with(numberOfDecimals: 2), 3.99)
    XCTAssertEqual(Float(3.991).with(numberOfDecimals: 2), 3.99)
    
    XCTAssertEqual(Double(3.00).with(numberOfDecimals: 1), 3.0)
    XCTAssertEqual(Double(3.09).with(numberOfDecimals: 1), 3.0)
    XCTAssertEqual(Double(3.01).with(numberOfDecimals: 1), 3.0)
    XCTAssertEqual(Double(3.999).with(numberOfDecimals: 2), 3.99)
    XCTAssertEqual(Double(3.991).with(numberOfDecimals: 2), 3.99)
    
    
    //numberOfDecimals > totalNumberOfDecimals
    XCTAssertEqual(Float(3.00).with(numberOfDecimals: 3), 3.000)
    XCTAssertEqual(Float(3.09).with(numberOfDecimals: 3), 3.090)
    XCTAssertEqual(Float(3.01).with(numberOfDecimals: 3), 3.010)
    XCTAssertEqual(Float(3.999).with(numberOfDecimals: 4), 3.9990)
    XCTAssertEqual(Float(3.991).with(numberOfDecimals: 4), 3.9910)
    
    XCTAssertEqual(Double(3.00).with(numberOfDecimals: 3), 3.000)
    XCTAssertEqual(Double(3.09).with(numberOfDecimals: 3), 3.090)
    XCTAssertEqual(Double(3.01).with(numberOfDecimals: 3), 3.010)
    XCTAssertEqual(Double(3.999).with(numberOfDecimals: 4), 3.9990)
    XCTAssertEqual(Double(3.991).with(numberOfDecimals: 4), 3.9910)
}

func test_GivenADecimal_ThenAssertStringValueWithDecimalsWanted() {
    //No decimals
    XCTAssertEqual(Float(3).string(numberOfDecimals: 0), "3")
    XCTAssertEqual(Float(3.09).string(numberOfDecimals: 0), "3")
    XCTAssertEqual(Float(3.999).string(numberOfDecimals: 0), "3")
    
    XCTAssertEqual(Double(3).string(numberOfDecimals: 0), "3")
    XCTAssertEqual(Double(3.09).string(numberOfDecimals: 0), "3")
    XCTAssertEqual(Double(3.999).string(numberOfDecimals: 0), "3")
    
    
    //numberOfDecimals == totalNumberOfDecimals
    XCTAssertEqual(Float(3.00).string(numberOfDecimals: 2), "3.00")
    XCTAssertEqual(Float(3.09).string(numberOfDecimals: 2), "3.09")
    XCTAssertEqual(Float(3.01).string(numberOfDecimals: 2), "3.01")
    XCTAssertEqual(Float(3.999).string(numberOfDecimals: 3), "3.999")
    XCTAssertEqual(Float(3.991).string(numberOfDecimals: 3), "3.991")
    
    XCTAssertEqual(Double(3.00).string(numberOfDecimals: 2), "3.00")
    XCTAssertEqual(Double(3.09).string(numberOfDecimals: 2), "3.09")
    XCTAssertEqual(Double(3.01).string(numberOfDecimals: 2), "3.01")
    XCTAssertEqual(Double(3.999).string(numberOfDecimals: 3), "3.999")
    XCTAssertEqual(Double(3.991).string(numberOfDecimals: 3), "3.991")
    
    
    //numberOfDecimals < totalNumberOfDecimals
    XCTAssertEqual(Float(3.00).string(numberOfDecimals: 1), "3.0")
    XCTAssertEqual(Float(3.09).string(numberOfDecimals: 1), "3.0")
    XCTAssertEqual(Float(3.01).string(numberOfDecimals: 1), "3.0")
    XCTAssertEqual(Float(3.999).string(numberOfDecimals: 2), "3.99")
    XCTAssertEqual(Float(3.991).string(numberOfDecimals: 2), "3.99")
    
    XCTAssertEqual(Double(3.00).string(numberOfDecimals: 1), "3.0")
    XCTAssertEqual(Double(3.09).string(numberOfDecimals: 1), "3.0")
    XCTAssertEqual(Double(3.01).string(numberOfDecimals: 1), "3.0")
    XCTAssertEqual(Double(3.999).string(numberOfDecimals: 2), "3.99")
    XCTAssertEqual(Double(3.991).string(numberOfDecimals: 2), "3.99")
    
    
    //numberOfDecimals > totalNumberOfDecimals
    XCTAssertEqual(Float(3.00).string(numberOfDecimals: 3), "3.000")
    XCTAssertEqual(Float(3.09).string(numberOfDecimals: 3), "3.090")
    XCTAssertEqual(Float(3.01).string(numberOfDecimals: 3), "3.010")
    XCTAssertEqual(Float(3.999).string(numberOfDecimals: 4), "3.9990")
    XCTAssertEqual(Float(3.991).string(numberOfDecimals: 4), "3.9910")
    
    XCTAssertEqual(Double(3.00).string(numberOfDecimals: 3), "3.000")
    XCTAssertEqual(Double(3.09).string(numberOfDecimals: 3), "3.090")
    XCTAssertEqual(Double(3.01).string(numberOfDecimals: 3), "3.010")
    XCTAssertEqual(Double(3.999).string(numberOfDecimals: 4), "3.9990")
    XCTAssertEqual(Double(3.991).string(numberOfDecimals: 4), "3.9910")
}

Numeric 协议扩展并限制为 BinaryFloatingPoint 是毫无意义的。 - Leo Dabus
Double(stringValue) as! Self ??? 顺便说一下,如果它不是 Double,你怎么保证它是一个 Float。试试 Float80(123.456789).with(numberOfDecimals: 2) // BOOM - Leo Dabus
@LeoDabus,你看到了吗?逻辑是围绕协议BinaryFloatingPoint的。这个想法是,你不用在意它是Float、Double、Float80……因为!Self只是帮助编译器……另外,试着看看在边缘情况下将浮点数和双精度数转换时会发生什么。 - Reimond Hill
这是错误的。你需要使用泛型方法返回一个通用类型。如果你想要返回 Self,你应该返回调用该方法的相同类型。你并没有帮助编译器,你只是断言它是 Float,但并没有任何保证。 - Leo Dabus
顺便说一句,我不赞成将浮点数转换为字符串来进行操作,但如果你想将字符串转换为二进制浮点数,你需要将“Self”限制为“LosslessStringConvertible”。请注意,它不包括“CGFloat”,因为它不符合要求。 - Leo Dabus
它将返回一个可选的 Self? - Leo Dabus

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