1

I'm using numbers divided by 10^30

I may be adding values like 1000000000000000 and 5000000000000000 stored in NSDecimalNumbers.

My concern is that I think I've seen a few times, when adding or subtracting these values, incorrect math being done.

Is that a possibility or are NSDecimalNumbers pretty sound in terms of the integrity of their math.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Zack Shapiro
  • 6,648
  • 17
  • 83
  • 151
  • 1
    Can you provide a concrete example? – Oliver Charlesworth Jan 17 '18 at 19:33
  • I'm trying to recreate now and I will – Zack Shapiro Jan 17 '18 at 19:33
  • 1
    @ZackShapiro You should use Swift native types such as `Decimal` – Leo Dabus Jan 17 '18 at 19:35
  • What benefits does it have over NSDecimalNumber other than being a native swift type? – Zack Shapiro Jan 17 '18 at 19:36
  • Benefits include (a) value vs reference semantics; (b) elimination of `NS` syntactic noise; (c) built-in `+`, `-`, etc., operators resulting in more natural and intuitive code. And, as [Apple says](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/working_with_foundation_types), “When importing the Foundation framework, the Swift overlay provides value types for many bridged reference types. ... Prefer Swift value types to bridged Objective-C reference types”. – Rob Jan 29 '19 at 18:59

2 Answers2

2

In answer to your question, the math offered by Decimal/NSDecimalNumber is sound, and the problem probably rests in either:

  1. The calculations might exceed the capacity of these decimal formats (as outlined by rob mayoff). For example, this works because we're within the 38 digit mantissa:

    let x = Decimal(sign: .plus, exponent: 60, significand: 1)
    let y = Decimal(sign: .plus, exponent: 30, significand: 1)
    let z = x + y
    

    1,000,000,000,000,000,000,000,000,000,001,000,000,000,000,000,000,000,000,000,000

    But this will not:

    let x = Decimal(sign: .plus, exponent: 60, significand: 1)
    let y = Decimal(sign: .plus, exponent: 10, significand: 1)
    let z = x + y
    

    1,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000

  2. Or, it could just be how you are instantiating these decimal values, e.g. supplying a floating point number rather than using the Decimal(sign:exponent:significand:) or NSDecimalNumber(mantissa:exponent:isNegative:) initializers:

    For example, this works fine:

    let formatter = NumberFormatter()
    formatter.numberStyle = .decimal
    
    let x = Decimal(sign: .plus, exponent: 30, significand: 1)
    print(formatter.string(for: x)!)
    

    That results in:

    1,000,000,000,000,000,000,000,000,000,000

    But these won't, because you're supplying a floating point number which suffers lower limits in precision:

    let y = Decimal(1.0e30)
    print(formatter.string(for: y)!)
    
    let z = Decimal(1_000_000_000_000_000_000_000_000_000_000.0)
    print(formatter.string(for: z)!)
    

    These both result in:

    1,000,000,000,000,000,409,600,000,000,000

For more information on floating-point arithmetic (and why certainly decimal numbers cannot be perfectly captured in floating-point types), see floating-point arithmetic.


In your other question, you ask why the following:

let foo = NSDecimalNumber(value: 334.99999).multiplying(byPowerOf10: 30)

produced:

334999990000000051200000000000000

This is the same underlying issue that I outlined above in point 2. Floating point numbers cannot accurately represent certain decimal values.

Note, your question is the same as the following Decimal rendition:

let adjustment = Decimal(sign: .plus, exponent: 30, significand: 1)
let foo = Decimal(334.99999) * adjustment

This also produces:

334999990000000051200000000000000

But you will get the desired result if you supply either a string or a exponent and mantissa/significant, because these will be accurately represented as a Decimal/NSDecimalNumber:

let bar = Decimal(string: "334.99999")! * adjustment
let baz = Decimal(sign: .plus, exponent: -5, significand: 33499999) * adjustment

Those both produce:

334999990000000000000000000000000

Bottom line, do not supply floating point numbers to Decimal or NSDecimalNumber. Use string representations or use the exponent and mantissa/significand representation and you will not see these strange deviations introduced when using floating point numbers.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

I'm using numbers divided by 1^30

Good news, then, because 1^30 = 1. Perhaps you meant 10^30?

Anyway, according to the NSDecimalNumber class reference:

An instance can represent any number that can be expressed as mantissa x 10^exponent where mantissa is a decimal integer up to 38 digits long, and exponent is an integer from –128 through 127.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848