2

During a debug session with XCode 5, how would I display the actual value of an NSDecimal var? I found this question but that doesn't work for me. Entering a summary description like {(int)[$VAR intValue]} just results in a message "Summary Unavailable". I should add that my NSDecimals are in an array (NSDecimal dataPoint[2];).

Using the debug console to either print the var description via the context menu or by using p dataPoint[0] just gives me the raw NSDecimal view:

Printing description of dataPoint[0]:
(NSDecimal) [0] = {
  _exponent = -1
  _length = 1
  _isNegative = 0
  _isCompact = 1
  _reserved = 0
  _mantissa = {
    [0] = 85
    [1] = 0
    [2] = 42703
    [3] = 65236
    [4] = 65534
    [5] = 65535
    [6] = 23752
    [7] = 29855
  }
}
Community
  • 1
  • 1
Mike Lischke
  • 48,925
  • 16
  • 119
  • 181

2 Answers2

5

UPDATE

In Xcode 10.0, there was an lldb bug that made this answer print the wrong value if the Decimal is a value in a Dictionary<String: Decimal> (and probably in other cases). See this question and answer and Swift bug report SR-8989. The bug was fixed by Xcode 11 (possibly earlier).

ORIGINAL

You can add lldb support for formatting NSDecimal (and, in Swift, Foundation.Decimal) by installing Python code that converts the raw bits of the NSDecimal to a human-readable string. This is called a type summary script and is documented under “PYTHON SCRIPTING” on this page of the lldb documentation.

One advantage of using a type summary script is that it doesn't involve running code in the target process, which can be important for certain targets.

Another advantage is that the Xcode debugger's variable view seems to work more reliably with a type summary script than with a summary format as seen in hypercrypt's answer. I had trouble with the summary format, but the type summary script works reliably.

Without a type summary script (or other customization), Xcode shows an NSDecimal (or Swift Decimal) like this:

before type summary script

With a type summary script, Xcode shows it like this:

after type summary script

Setting up the type summary script involves two steps:

  1. Save the script (shown below) in a file somewhere. I saved it in ~/.../lldb/Decimal.py.

  2. Add a command to ~/.lldbinit to load the script. The command should look like this:

    command script import ~/.../lldb/Decimal.py
    

    Change the path to wherever you stored the script.

Here's the script. I have also saved it in this gist.

# Decimal / NSDecimal support for lldb
#
# Put this file somewhere, e.g. ~/.../lldb/Decimal.py
# Then add this line to ~/.lldbinit:
#     command script import ~/.../lldb/Decimal.py

import lldb

def stringForDecimal(sbValue, internal_dict):
    from decimal import Decimal, getcontext

    sbData = sbValue.GetData()
    if not sbData.IsValid():
        raise Exception('unable to get data: ' + sbError.GetCString())
    if sbData.GetByteSize() != 20:
        raise Exception('expected data to be 20 bytes but found ' + repr(sbData.GetByteSize()))

    sbError = lldb.SBError()
    exponent = sbData.GetSignedInt8(sbError, 0)
    if sbError.Fail():
        raise Exception('unable to read exponent byte: ' + sbError.GetCString())

    flags = sbData.GetUnsignedInt8(sbError, 1)
    if sbError.Fail():
        raise Exception('unable to read flags byte: ' + sbError.GetCString())
    length = flags & 0xf
    isNegative = (flags & 0x10) != 0

    if length == 0 and isNegative:
        return 'NaN'

    if length == 0:
        return '0'

    getcontext().prec = 200
    value = Decimal(0)
    scale = Decimal(1)
    for i in range(length):
        digit = sbData.GetUnsignedInt16(sbError, 4 + 2 * i)
        if sbError.Fail():
            raise Exception('unable to read memory: ' + sbError.GetCString())
        value += scale * Decimal(digit)
        scale *= 65536

    value = value.scaleb(exponent)
    if isNegative:
        value = -value

    return str(value)

def __lldb_init_module(debugger, internal_dict):
    print('registering Decimal type summaries')
    debugger.HandleCommand('type summary add Foundation.Decimal -F "' + __name__ + '.stringForDecimal"')
    debugger.HandleCommand('type summary add NSDecimal -F "' + __name__ + '.stringForDecimal"')
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
3

The easiest way is to turn it into an NSDecimalNumber in the debugger, i.e.

po [NSDecimalNumber decimalNumberWithDecimal:dataPoint[0]]

This will create a new NSDecimalNumber which prints a nice description. The NSDecimal in your questions is 8.5.

(lldb) po [NSDecimalNumber decimalNumberWithDecimal:dataPoint[0]]
8.5

If you want to have the number displayed in the Variable View, the Summary Format for it would be:

{[NSDecimalNumber decimalNumberWithDecimal:$VAR]}:s
hypercrypt
  • 15,389
  • 6
  • 48
  • 59