Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c20c4b1
Add initial draft of decimal32_t lldb printer
mborland Dec 9, 2025
a094f8e
Add non-finite values handling
mborland Dec 9, 2025
1431d36
Add payload handling
mborland Dec 9, 2025
91051ef
Print values without fraction as integers instead of with trailing .0
mborland Dec 9, 2025
34f6dc4
Improve sign and payload handling
mborland Dec 10, 2025
8ffda03
Add example with variety of values
mborland Dec 10, 2025
0d08900
Preserve cohort
mborland Dec 10, 2025
0b9db3a
Add debugger doc page
mborland Dec 10, 2025
d9f5231
Make decoding reusable for LLDB and GDB
mborland Dec 10, 2025
5c23ca5
Add printing of decimal64_t
mborland Dec 10, 2025
95d6e1f
Fix decimal64_t pattern
mborland Dec 10, 2025
9a4b6bc
Add decimal128_t decoding methods and printer
mborland Dec 10, 2025
c559030
Match function signature to the others
mborland Dec 10, 2025
aec22fc
Allow all types to be shown in example
mborland Dec 10, 2025
173defb
The bits of decimal128_t are synthetic
mborland Dec 10, 2025
a94d415
Avoid division and manipulate the string directly instead
mborland Dec 10, 2025
3184243
Add fast type synthetic provider
mborland Dec 10, 2025
739965e
Add generic fast type decoder
mborland Dec 10, 2025
e888d75
Reduce code duplication
mborland Dec 10, 2025
995293e
Add decimal_fast32_t to example
mborland Dec 10, 2025
85d2443
Add decimal_fast32_t pretty printer
mborland Dec 10, 2025
577fcc9
Fix import error
mborland Dec 10, 2025
243d40b
Refactor
mborland Dec 10, 2025
4235462
Fix negative nan typo
mborland Dec 10, 2025
32c8cac
Add proper handling of non-finite decimal_fast32_t values
mborland Dec 10, 2025
265ff0a
Add decimal_fast64_t pretty printer
mborland Dec 10, 2025
ff94486
Add decimal_fast128_t pretty printer
mborland Dec 10, 2025
03f5936
Improve output and explanation in documentation
mborland Dec 10, 2025
428a8b6
Fix missing return
mborland Dec 10, 2025
2c2d170
Always print a minimum of 2 digits of exponent
mborland Dec 10, 2025
e2b34c0
Update images
mborland Dec 10, 2025
a7b4eb4
Improve comments
mborland Dec 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/modules/ROOT/images/dec32_debug.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/modules/ROOT/images/dec32_fast_debug.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* xref:financial_examples.adoc[]
** xref:financial_examples.adoc#examples_money_parsing[Parsing Pricing Data from File]
** xref:financial_examples.adoc#examples_boost_math[Boost.Math Integration]
* xref:debugger.adoc[]
* xref:api_reference.adoc[]
** xref:api_reference.adoc#api_ref_types[Types]
** xref:api_reference.adoc#api_ref_structs[Structs]
Expand Down
41 changes: 41 additions & 0 deletions doc/modules/ROOT/pages/debugger.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
////
Copyright 2025 Matt Borland
Distributed under the Boost Software License, Version 1.0.
https://www.boost.org/LICENSE_1_0.txt
////

[#debug]
= Debugger Support
:idprefix: debug_

Currently, Boost.Decimal supports pretty printing with LLDB.
To load the pretty printer, add the following line to your `.lldbinit`:

[source]
----
command script import /path/to/decimal/extra/decimal_printer.py
----

Once you have loaded `decimal_printer.py` you can run the following example to see how different values are represented with the pretty printer.

.The following example can be run with the debugger to show a variety of values
====

[source,c++]
----
include::example$debugger.cpp[]
----

*_Expected Output for T = decimal32_t:_*

image::dec32_debug.png[]

*_Expected Output for T = decimal_fast32_t:_*

image::dec32_fast_debug.png[]
====

The reason for the difference in how `decimal32_t` and `decimal_fast32_t` is displayed is due to xref:cohorts.adoc[Cohorts and Normalization]

. `decimal32_t`, `decimal64_t` and `decimal128_t` debuggers are cohort preserving
. `decimal_fast32_t`, `decimal_fast64_t` and `decimal_fast128_t` debuggers are always normalized
59 changes: 59 additions & 0 deletions examples/debugger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example, when run with the pretty printers, shows how various values are represented

#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/decimal128_t.hpp> // For type decimal128_t
#include <boost/decimal/decimal_fast32_t.hpp> // For type decimal_fast32_t
#include <boost/decimal/cmath.hpp> // For nan function to write payload to nans
#include <limits>

template <typename T>
void debug_values()
{
// Displays the maximum and minimum values that the type can hold
// from numeric_limits
const T max {std::numeric_limits<T>::max()};
const T min {std::numeric_limits<T>::min()};

// A number whose representation will change based on IEEE vs fast type
// In the IEEE case 3.140e+00 will be displayed as the pretty printer is cohort preserving
const T short_num {"3.140"};

// Shows how infinities will be displayed
const T pos_inf {std::numeric_limits<T>::infinity()};
const T neg_inf {-std::numeric_limits<T>::infinity()};

// Shows how the different kinds of NANs will be displayed
const T qnan {std::numeric_limits<T>::quiet_NaN()};
const T snan {std::numeric_limits<T>::signaling_NaN()};

// Shows how a payload added to a QNAN will be displayed
const T payload_nan {boost::decimal::nan<T>("7")};

// Break Here:
static_cast<void>(max);
static_cast<void>(min);
static_cast<void>(short_num);
static_cast<void>(pos_inf);
static_cast<void>(neg_inf);
static_cast<void>(qnan);
static_cast<void>(snan);
static_cast<void>(payload_nan);
}

int main()
{
debug_values<boost::decimal::decimal32_t>();
debug_values<boost::decimal::decimal64_t>();
debug_values<boost::decimal::decimal128_t>();

debug_values<boost::decimal::decimal_fast32_t>();
debug_values<boost::decimal::decimal_fast64_t>();
debug_values<boost::decimal::decimal_fast128_t>();

return 0;
}
230 changes: 230 additions & 0 deletions extra/decimal_printer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# Copyright 2025 Matt Borland
# Distributed under the Boost Software License, Version 1.0.
# https://www.boost.org/LICENSE_1_0.txt

from detail.decode_ieee_type import decode_decimal32
from detail.decode_ieee_type import decode_decimal64
from detail.decode_ieee_type import decode_decimal128
from detail.decode_fast_type import decode_decimal_fast32
from detail.decode_fast_type import decode_decimal_fast64
from detail.decode_fast_type import decode_decimal_fast128

import lldb

def decimal32_summary(valobj, internal_dict):
"""
Custom summary for decimal32_t type
Displays in scientific notation with cohort preservation
"""

try:
val = valobj.GetNonSyntheticValue()
bits = val.GetChildMemberWithName("bits_").GetValueAsUnsigned()
return decode_decimal32(bits)

except Exception as e:
return f"<invalid decimal32_t: {e}>"

def decimal64_summary(valobj, internal_dict):
"""
Custom summary for decimal64_t type
Displays in scientific notation with cohort preservation
"""

try:
val = valobj.GetNonSyntheticValue()
bits = val.GetChildMemberWithName("bits_").GetValueAsUnsigned()
return decode_decimal64(bits)

except Exception as e:
return f"<invalid decimal64_t: {e}>"

def decimal128_summary(valobj, internal_dict):
"""
Custom summary for decimal128_t type
Displays in scientific notation with cohort preservation
"""

try:
val = valobj.GetNonSyntheticValue()
bits = val.GetChildMemberWithName("bits_")
bits_high = bits.GetChildMemberWithName("high").GetValueAsUnsigned()
bits_low = bits.GetChildMemberWithName("low").GetValueAsUnsigned()
combined_bits = (bits_high << 64) | bits_low
return decode_decimal128(combined_bits)

except Exception as e:
return f"<invalid decimal128_t: {e}>"

def decimal_fast32_summary(valobj, internal_dict):
"""
Custom summary for decimal_fast32_t type
Displays in scientific notation
"""

try:
val = valobj.GetNonSyntheticValue()
significand = val.GetChildMemberWithName("significand_").GetValueAsUnsigned()
exp = val.GetChildMemberWithName("exponent_").GetValueAsUnsigned()
sign = val.GetChildMemberWithName("sign_").GetValueAsUnsigned()
return decode_decimal_fast32(significand, exp, sign)

except Exception as e:
return f"<invalid decimal_fast32_t: {e}>"

def decimal_fast64_summary(valobj, internal_dict):
"""
Custom summary for decimal_fast64_t type
Displays in scientific notation
"""

try:
val = valobj.GetNonSyntheticValue()
significand = val.GetChildMemberWithName("significand_").GetValueAsUnsigned()
exp = val.GetChildMemberWithName("exponent_").GetValueAsUnsigned()
sign = val.GetChildMemberWithName("sign_").GetValueAsUnsigned()
return decode_decimal_fast64(significand, exp, sign)

except Exception as e:
return f"<invalid decimal_fast64_t: {e}>"
def decimal_fast128_summary(valobj, internal_dict):
"""
Custom summary for decimal_fast128_t type
Displays in scientific notation
"""

try:
val = valobj.GetNonSyntheticValue()

significand = val.GetChildMemberWithName("significand_")
bits_high = significand.GetChildMemberWithName("high").GetValueAsUnsigned()
bits_low = significand.GetChildMemberWithName("low").GetValueAsUnsigned()
combined_bits = (bits_high << 64) | bits_low

exp = val.GetChildMemberWithName("exponent_").GetValueAsUnsigned()
sign = val.GetChildMemberWithName("sign_").GetValueAsUnsigned()

return decode_decimal_fast128(combined_bits, exp, sign)

except Exception as e:
return f"<invalid decimal_fast128_t: {e}>"

def __lldb_init_module(debugger, internal_dict):
decimal32_pattern = r"^(const )?(boost::decimal::decimal32_t|(\w+::)*decimal32_t)( &| \*)?$"
decimal64_pattern = r"^(const )?(boost::decimal::decimal64_t|(\w+::)*decimal64_t)( &| \*)?$"
decimal128_pattern = r"^(const )?(boost::decimal::decimal128_t|(\w+::)*decimal128_t)( &| \*)?$"

decimal_fast32_pattern = r"^(const )?(boost::decimal::decimal_fast32_t|(\w+::)*decimal_fast32_t)( &| \*)?$"
decimal_fast64_pattern = r"^(const )?(boost::decimal::decimal_fast64_t|(\w+::)*decimal_fast64_t)( &| \*)?$"
decimal_fast128_pattern = r"^(const )?(boost::decimal::decimal_fast128_t|(\w+::)*decimal_fast128_t)( &| \*)?$"

debugger.HandleCommand(
f'type summary add -x "{decimal32_pattern}" -e -F decimal_printer.decimal32_summary'
)
debugger.HandleCommand(
f'type synthetic add -x "{decimal32_pattern}" -l decimal_printer.DecimalSyntheticProvider'
)

print("decimal32_t printer loaded successfully")

debugger.HandleCommand(
f'type summary add -x "{decimal64_pattern}" -e -F decimal_printer.decimal64_summary'
)
debugger.HandleCommand(
f'type synthetic add -x "{decimal64_pattern}" -l decimal_printer.DecimalSyntheticProvider'
)

print("decimal64_t printer loaded successfully")

debugger.HandleCommand(
f'type summary add -x "{decimal128_pattern}" -e -F decimal_printer.decimal128_summary'
)
debugger.HandleCommand(
f'type synthetic add -x "{decimal128_pattern}" -l decimal_printer.DecimalSyntheticProvider'
)

print("decimal128_t printer loaded successfully")

debugger.HandleCommand(
f'type summary add -x "{decimal_fast32_pattern}" -e -F decimal_printer.decimal_fast32_summary'
)
debugger.HandleCommand(
f'type synthetic add -x "{decimal_fast32_pattern}" -l decimal_printer.DecimalFastSyntheticProvider'
)

print("decimal_fast32_t printer loaded successfully")

debugger.HandleCommand(
f'type summary add -x "{decimal_fast64_pattern}" -e -F decimal_printer.decimal_fast64_summary'
)
debugger.HandleCommand(
f'type synthetic add -x "{decimal_fast64_pattern}" -l decimal_printer.DecimalFastSyntheticProvider'
)

print("decimal_fast64_t printer loaded successfully")

debugger.HandleCommand(
f'type summary add -x "{decimal_fast128_pattern}" -e -F decimal_printer.decimal_fast128_summary'
)
debugger.HandleCommand(
f'type synthetic add -x "{decimal_fast128_pattern}" -l decimal_printer.DecimalFastSyntheticProvider'
)

print("decimal_fast128_t printer loaded successfully")

class DecimalSyntheticProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj

def num_children(self):
return 1

def get_child_index(self, name):
if name == "bits_":
return 0
return -1

def get_child_at_index(self, index):
if index == 0:
return self.valobj.GetChildMemberWithName("bits_")
return None

def update(self):
pass

def has_children(self):
return True

class DecimalFastSyntheticProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj

def num_children(self):
return 3

def get_child_index(self, name):
if name == "significand_":
return 0
elif name == "exponent_":
return 1
elif name == "sign_":
return 2
else:
return -1

def get_child_at_index(self, index):
if index == 0:
return self.valobj.GetChildMemberWithName("significand_")
elif index == 1:
return self.valobj.GetChildMemberWithName("exponent_")
elif index == 2:
return self.valobj.GetChildMemberWithName("sign_")
else:
return None

def update(self):
pass

def has_children(self):
return True

Loading