2016-06-13 2 views
0

У меня есть эта информация.Round Issue in swift

let params2: [String: AnyObject] = [ 
    "app_token": myapptoken, 
    "member_access_token": accessToken!, 
    "pay_process": 0, 
    "payamount_credit": 9.87 //hardcode 
] 

При печати params2

В результате

["app_token": myapptoken, "member_access_token": accessToken, "payamount_credit": 9.869999999999999, "pay_process": 0]

"payamount_credit": 9.87 теперь "payamount_credit": 9.869999999999999

Я перепробовал все способы которые существуют круглые, но ведут себя одинаково.

NSString(format: "%.\2f", 9.87)

Double(round(1000*9.87)/1000)

Самое странное, что все это происходит только с этим конкретным номером (9,87), что-то мистическое.

Игровая площадка.

enter image description here

+1

Это значения с плавающей запятой для вас. :) http://programmers.stackexchange.com/questions/101163/what-causes-floating-point-rounding-errors –

+0

Вам нужно сгенерировать новые токены, если они являются частными. Вы включили их в выходной образец. – Brett

ответ

4

Не использовать поплавки или двойников представлять валюту, именно по (каламбур) по этой причине. Вместо этого используйте NSDecimalNumber (см. Which Swift datatype do I use for currency).

NSDecimalNumber прилагает определенные усилия для использования, но выполняет арифметику базы 10. Чтобы использовать его правильно, вам нужно немного почитать API. В частности, для округления вам нужно предоставить NSDecimalNumberHandler (вы можете установить значение по умолчанию для использования).

Хороший материал для чтения:

Я думаю что площадка показывает неожиданный результат потому что он принуждает внутреннее значение NSDecimalNumber к Double bef (я бы приветствовал альтернативные или авторитетные объяснения, потому что я нахожу это любопытным). Однако внутреннее представление должно быть правильным.

Попробуйте это в Playground:

let myRoundingBehavior = NSDecimalNumberHandler(roundingMode: .RoundBankers, 
scale: 2, raiseOnExactness: true, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true) 

NSDecimalNumber.setDefaultBehavior(myRoundingBehavior) 

let integerPrice = NSDecimalNumber(mantissa: 987, exponent: -2, isNegative: false) 

integerPrice    // 9.870000000000001 
Float(integerPrice)  // 9.87 
Double(integerPrice)  // 9.870000000000001 
integerPrice.description // "9.87" 

Не думайте, что Float является более точным представлением числа, это просто происходит работать лучше. Если вы держите свои расчеты (например, налоги, скидка, доставка и т. Д.) В области NSDecimalNumber, они будут точными.


Swift 3/Xcode бета 1 бонус:

поведение площадка остается прежним, но меняется Великий API ереименование Rodeo изменяет приведенный выше код:

let myRoundingBehavior = NSDecimalNumberHandler(roundingMode: .roundBankers, 
               scale: 2, 
               raiseOnExactness: true, 
               raiseOnOverflow: true, 
               raiseOnUnderflow: true, 
               raiseOnDivideByZero: true) 

Изменение здесь что глобальное перечисление NSRoundingMode исчезает и заменяется перечислением RoundingMode, которое является членом NSDecimalNumber.

+0

Не работает, проверьте мой ответ Редактировать – jose920405

+0

Вы инициализируете его «Двойным», поэтому вы уже потеряли точность. Инициализируйте его строкой. – Jim

3

Проблема заключается в numeric precission, просто положить, некоторые цифры не могут быть определены как Double без потери точности.

Вместо этого вы должны использовать NSDecimalNumber:

let num = NSDecimalNumber(string: "9.87") 
1

Попробуйте использовать эту функцию:

let price: Double = 9.87 

func roundDouble(num: Double) -> Float { 

    //Create NSDecimalNumberHandler to specify rounding behavior 
    let numHandler = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundDown, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) 

    let roundedNum = NSDecimalNumber(double: num).decimalNumberByRoundingAccordingToBehavior(numHandler) 

    //Convert to float so you don't get the endless repeating decimal. 
    return Float(roundedNum) 

} 

roundDouble(price) //9.87 

Я думаю, что лучше всего сделать здесь явно использовать поплавок вместо двойной, потому что точность двойника не требуется, если вы хотите только два десятичных знака. Эта функция просто помогает округлить ее немного точнее.

+0

см. Мое редактирование. @ jose920405 – Ike10