Nsdecimalnumber(X).Intvalue Returns -2, 0, 15 and 199, Depending on the Amount of Decimals in X (X = 199.999...5)

NSDecimalNumber(x).intValue returns -2, 0, 15 and 199, depending on the amount of decimals in x (x = 199.999...5)

It's clearly a foundation bug, probably the one mentioned by Martin R in the comments.

I experimented in Playground (Swift 5) and found that the comment on that bug that int32Value works correctly is true.

import Foundation

let pQuantity = Decimal(string: "0.2857142857142857")!
let pPrice = Decimal(string: "7.00000000000000035")!

let calced = NSDecimalNumber(decimal: pQuantity * pPrice * Decimal(integerLiteral: 100)) // 200
let decimal = calced.decimalValue // 199.9999999999999999999999999999995
let integer = calced.int32Value // 200

NSDecimalNumber(decimal: Decimal(string: "199.9999999999999999999999999999995")!).uint32Value // 200
NSDecimalNumber(decimal: Decimal(string: "199.9999999999999995")!).int32Value // 200
NSDecimalNumber(decimal: Decimal(string: "199.99999999999999995")!).int32Value // 200
NSDecimalNumber(decimal: Decimal(string: "199.999999999999999995")!).int32Value // 200

Also, as you can see uint32Value also works correctly. However, none of the 64 bit variants work.

Provided you are sure that your result will fit into an Int32 you can use that as a work around until they fix it, which is probably never, given that the bug has been outstanding for a while.

How to store 1.66 in NSDecimalNumber

In

let number:NSDecimalNumber = 1.66

the right-hand side is a floating point number which cannot represent
the value "1.66" exactly. One option is to create the decimal number
from a string:

let number = NSDecimalNumber(string: "1.66")
print(number) // 1.66

Another option is to use arithmetic:

let number = NSDecimalNumber(value: 166).dividing(by: 100)
print(number) // 1.66

With Swift 3 you may consider to use the "overlay value type" Decimal instead, e.g.

let num = Decimal(166)/Decimal(100)
print(num) // 1.66

Yet another option:

let num = Decimal(sign: .plus, exponent: -2, significand: 166)
print(num) // 1.66

Addendum:

Related discussions in the Swift forum:

  • Exact NSDecimalNumber via literal
  • ExpressibleByFractionLiteral

Related bug reports:

  • SR-3317
    Literal protocol for decimal literals should support precise decimal accuracy, closed as a duplicate of
  • SR-920
    Re-design builtin compiler protocols for literal convertible types.

Examples of doing decimal math in iPhone

To init:

NSString *number = @"123.4";
NSDecimalNumber *myDecimal = [[NSDecimalNumber alloc] initWithString: number];

To do what you want to do:

NSDecimalNumber *sum = [[NSDecimalNumber alloc] initWithDecimal:[balance decimalNumberByMultiplyingBy: interest_rate];

How?

Well you make a NSDecimalNumber and alloc and then initWithDecimal. What decimal? balance multiplied (decimalNumberByMultiplyingBy) by interest_rate.

Swift 3 : Decimal to Int

This is my updated answer (thanks to Martin R and the OP for the remarks). The OP's problem was just casting the pow(x: Decimal,y: Int) -> Decimal function to an Int after subtracting 1 from the result. I have answered the question with the help of this SO post for NSDecimal and Apple's documentation on Decimal. You have to convert your result to an NSDecimalNumber, which can in turn be casted into an Int:

let size = Decimal(2)
let test = pow(size, 2) - 1
let result = NSDecimalNumber(decimal: test)
print(Int(result)) // testing the cast to Int

calculated double return scientific notation

The number itself is correct as scientific notation. If you want to present a formatted number to the user, it should be a String. Here's working code using a NumberFormatter:

extension Double {
/// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> String? {
let fmt = NumberFormatter()
fmt.numberStyle = .decimal
fmt.maximumFractionDigits = places
return fmt.string(from: self as NSNumber)
}
}

let price = tickerObj.price ?? 0
let quantity = Double(self.activeTextField.text ?? "0") ?? 0
let value = quantity / price

topValueField.text = "\(value.rounded(toPlaces: 8) ?? "Unknown")"


Related Topics



Leave a reply



Submit