From 2d1e14b78ea22881697f1c37db5be341157040fa Mon Sep 17 00:00:00 2001 From: Judith Silverman Date: Mon, 20 Jul 2026 17:34:43 -0700 Subject: [PATCH] IRSA-7801 (Bug fix): Respect irsa_format when displaying floating-point values Ipac_Table_Writer: Respect irsa_format and support exponential notation in column-width calculation. --- src/Ascii_Writer/Ascii_Writer.cxx | 40 ++----- src/Column.hxx | 5 +- src/Decimal_String_Trimmer.hxx | 8 +- .../Decimal_String_Trimmer.cxx | 48 ++++---- src/Format_Packet.hxx | 56 +++++++++ src/Ipac_Table_Writer/Ipac_Table_Writer.cxx | 110 +++++++++--------- 6 files changed, 153 insertions(+), 114 deletions(-) diff --git a/src/Ascii_Writer/Ascii_Writer.cxx b/src/Ascii_Writer/Ascii_Writer.cxx index a1844a67..392c1800 100644 --- a/src/Ascii_Writer/Ascii_Writer.cxx +++ b/src/Ascii_Writer/Ascii_Writer.cxx @@ -14,29 +14,6 @@ #include "../Format_Packet.hxx" #include "../data_size.hxx" -namespace { - - -void apply_format_packet(std::ostream &stream, const tablator::Format_Packet &fmt) { - switch (fmt.flag_) { - case 'f': - stream << std::fixed << std::setprecision(fmt.precision_); - break; - case 'e': - stream << std::scientific << std::setprecision(fmt.precision_); - break; - case 'g': - case '\0': - default: - stream << std::defaultfloat << std::setprecision(fmt.precision_); - break; - } -} - - -} // namespace - - namespace tablator { // Declare static constexpr class members. constexpr const char Ascii_Writer::DEFAULT_SEPARATOR; @@ -62,8 +39,7 @@ void Ascii_Writer::write_type_as_ascii(std::ostream &os, if (type != Data_Type::CHAR && array_size != 1) { for (size_t n = 0; n < array_size; ++n) { write_array_unit_as_ascii(os, format_packet, 1, - data + n * get_data_size(type), - options); + data + n * get_data_size(type), options); if (n != array_size - 1) { os << separator; @@ -85,8 +61,7 @@ void Ascii_Writer::write_type_as_ascii_expand_array( for (size_t n = 0; n < array_size; ++n) { os << std::setw(col_width); write_array_unit_as_ascii(os, format_packet, 1, - data + n * get_data_size(type), - options); + data + n * get_data_size(type), options); if (n != array_size - 1) { os << IPAC_COLUMN_SEPARATOR; @@ -138,17 +113,18 @@ void Ascii_Writer::write_array_unit_as_ascii(std::ostream &os, os << *reinterpret_cast(data); } break; case Data_Type::FLOAT32_LE: { - apply_format_packet(os, format_packet); - os << *reinterpret_cast(data); + format_packet.apply_to_stream(os); + os << *reinterpret_cast(data); } break; case Data_Type::FLOAT64_LE: { + double value_doub = *reinterpret_cast(data); if (options.is_trim_decimal_runs()) { os << Decimal_String_Trimmer::get_decimal_string( - *reinterpret_cast(data), + value_doub, format_packet.get_formatted_value(value_doub), options.min_run_length_for_trim_); } else { - apply_format_packet(os, format_packet); - os << *reinterpret_cast(data); + format_packet.apply_to_stream(os); + os << value_doub; } } break; case Data_Type::CHAR: diff --git a/src/Column.hxx b/src/Column.hxx index 8cc6fa58..ecb855c3 100644 --- a/src/Column.hxx +++ b/src/Column.hxx @@ -98,13 +98,12 @@ public: return tablator::get_data_size(type_) * array_size_; } - inline Format_Packet get_format_packet() const { return format_packet_; } + inline const Format_Packet &get_format_packet() const { return format_packet_; } private: static std::string extract_format_str(const Field_Properties &field_properties) { const auto &field_prop_attributes = field_properties.get_attributes(); - const auto format_iter = - field_prop_attributes.find(ATTR_IRSA_FORMAT); + const auto format_iter = field_prop_attributes.find(ATTR_IRSA_FORMAT); if (format_iter != field_prop_attributes.end()) { return format_iter->second; } diff --git a/src/Decimal_String_Trimmer.hxx b/src/Decimal_String_Trimmer.hxx index f9e70f46..5152979d 100644 --- a/src/Decimal_String_Trimmer.hxx +++ b/src/Decimal_String_Trimmer.hxx @@ -2,7 +2,6 @@ #include - namespace tablator { namespace Decimal_String_Trimmer { @@ -12,11 +11,14 @@ namespace Decimal_String_Trimmer { // decimal point in the decimal representation of doub_value. If it // finds such a run, it rounds up or down accordingly and truncates // the string. -const std::string get_decimal_string(double doub_value, ushort min_run_length_for_trim); +const std::string get_decimal_string(double doub_value, + const std::string &orig_doub_str, + ushort min_run_length_for_trim); // This function returns the length of the string returned by get_decimal_string(). -size_t get_decimal_string_length(double doub_value, ushort min_run_length_for_trim); +size_t get_decimal_string_length(double doub_value, const std::string &orig_doub_str, + ushort min_run_length_for_trim); } // namespace Decimal_String_Trimmer diff --git a/src/Decimal_String_Trimmer/Decimal_String_Trimmer.cxx b/src/Decimal_String_Trimmer/Decimal_String_Trimmer.cxx index 75618af1..24b3f5a4 100644 --- a/src/Decimal_String_Trimmer/Decimal_String_Trimmer.cxx +++ b/src/Decimal_String_Trimmer/Decimal_String_Trimmer.cxx @@ -1,12 +1,11 @@ +#include "../Decimal_String_Trimmer.hxx" + #include #include #include #include -#include "../Decimal_String_Trimmer.hxx" - - namespace { //============== @@ -15,22 +14,25 @@ namespace { class Trimmability_Packet { public: - Trimmability_Packet(double value_doub, ushort min_run_length_for_trim) + Trimmability_Packet(double value_doub, const std::string &orig_value_str, + ushort min_run_length_for_trim) : min_run_length_for_trim_(min_run_length_for_trim) { // Check for negative value. value_doub_ = value_doub; is_neg_ = (value_doub < 0); - value_str_ = boost::lexical_cast(value_doub); + value_str_ = orig_value_str; abs_value_str_ = is_neg_ ? value_str_.substr(1) : value_str_; // Check for scientific notation ("a.bbbbe-cc"). e_pos_ = abs_value_str_.find("e"); got_exp_ = (e_pos_ != std::string::npos); - abs_str_len_ = abs_value_str_.size(); abs_coeff_str_len_ = got_exp_ ? e_pos_ : abs_str_len_; + exp_str_ = got_exp_ ? abs_value_str_.substr(e_pos_) : ""; + adjusted_exp_str_.assign(exp_str_); // Adjust later as needed. + // Prepare to check for runs of 0s or of 9s starting to the right // of the decimal point and on or to the right of the first // non-zero digit. @@ -38,6 +40,7 @@ class Trimmability_Packet { bool keep_looking = is_candidate_for_adjustment(anchor_pos_, abs_coeff_str_len_); + if (keep_looking) { // Set default values of these variables which will then capture // the return value of get_rounding_info_for_double(). @@ -62,7 +65,7 @@ class Trimmability_Packet { //================================================ - const std::string get_possibly_trimmed_string() const { + const std::string get_possibly_trimmed_string() { if (!is_trimmable_) { return value_str_; } @@ -81,8 +84,6 @@ class Trimmability_Packet { std::string abs_ret_coeff_str = abs_coeff_str_.substr(0, adjusted_abs_coeff_str_len_); - std::string exp_str_ = got_exp_ ? abs_value_str_.substr(e_pos_) : ""; - if (!got_run_of_9s_) { // The run is of 0s; all that's needed is to truncate and, if @@ -122,9 +123,14 @@ class Trimmability_Packet { // coeff_str was 9.9999912, adjusted_abs_coeff_str would be // 10.0). This next call produces a string in proper // scientific notation format, replacing e.g. 10.0e-5 with - // 1.0e-4. - return sign_str + - standardize_scientific_notation(adjusted_abs_coeff_str, exp_str_); + // 1.00e-4. + const auto standardized_str = + standardize_scientific_notation(adjusted_abs_coeff_str, exp_str_); + + size_t adjusted_e_pos = standardized_str.find("e"); + adjusted_exp_str_ = standardized_str.substr(adjusted_e_pos); + + return sign_str + standardized_str; } return sign_str + adjusted_abs_coeff_str; } @@ -133,12 +139,8 @@ class Trimmability_Packet { size_t get_possibly_trimmed_length() const { if (is_trimmable_) { - static const short MAX_EXP_LEN = 4; // "e-cd" - // Don't bother estimating length of possibly adjusted exp_str. - size_t max_exp_len = got_exp_ ? MAX_EXP_LEN : 0; short sign_len = is_neg_ ? 1 : 0; - - return sign_len + adjusted_abs_coeff_str_len_ + max_exp_len; + return sign_len + adjusted_abs_coeff_str_len_ + adjusted_exp_str_.size(); } return value_str_.size(); } @@ -278,6 +280,8 @@ class Trimmability_Packet { std::string value_str_; std::string abs_value_str_; + std::string exp_str_; + std::string adjusted_exp_str_; size_t e_pos_; bool got_exp_; @@ -308,16 +312,18 @@ namespace tablator { // the string. const std::string Decimal_String_Trimmer::get_decimal_string( - double doub_value, ushort min_run_length_for_trim) { - Trimmability_Packet trim_packet(doub_value, min_run_length_for_trim); + double doub_value, const std::string &orig_doub_str, + ushort min_run_length_for_trim) { + Trimmability_Packet trim_packet(doub_value, orig_doub_str, min_run_length_for_trim); return trim_packet.get_possibly_trimmed_string(); } // This function returns the length of the string returned by get_decimal_string(). size_t tablator::Decimal_String_Trimmer::get_decimal_string_length( - double doub_value, ushort min_run_length_for_trim) { - Trimmability_Packet trim_packet(doub_value, min_run_length_for_trim); + double doub_value, const std::string &orig_doub_str, + ushort min_run_length_for_trim) { + Trimmability_Packet trim_packet(doub_value, orig_doub_str, min_run_length_for_trim); return trim_packet.get_possibly_trimmed_length(); } diff --git a/src/Format_Packet.hxx b/src/Format_Packet.hxx index 25e97c39..462a1304 100644 --- a/src/Format_Packet.hxx +++ b/src/Format_Packet.hxx @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "data_size.hxx" namespace tablator { @@ -63,6 +66,59 @@ struct Format_Packet { Format_Packet() : Format_Packet("", Data_Type::CHAR) {} + //============================================= + + void apply_to_stream(std::ostream &os) const { + switch (flag_) { + case 'f': + os << std::fixed << std::setprecision(precision_); + break; + case 'e': + os << std::scientific << std::setprecision(precision_); + break; + default: + os << std::defaultfloat << std::setprecision(precision_); + break; + } + } + + //============================================= + + template >> + std::string get_formatted_value(T value) const { + std::ostringstream oss; + apply_to_stream(oss); + oss << value; + return oss.str(); + } + + //============================================= + + size_t get_max_strlen() const { + // https://stackoverflow.com/questions/2151302/counting-digits-in-a-float + static size_t MAX_FLOAT32_STRLEN = 15; + static size_t MAX_FLOAT64_STRLEN = 24; + + static size_t MAX_FLOAT32_EXPONENT_STRLEN = 4; + static size_t MAX_FLOAT64_EXPONENT_STRLEN = 5; + + size_t max_strlen; + if (data_type_ == Data_Type::FLOAT32_LE) { + max_strlen = MAX_FLOAT32_STRLEN; + if (flag_ == 'e') { + max_strlen += MAX_FLOAT32_EXPONENT_STRLEN; + } + } else { + max_strlen = MAX_FLOAT64_STRLEN; + if (flag_ == 'e') { + max_strlen += MAX_FLOAT64_EXPONENT_STRLEN; + } + } + return max_strlen; + } + + //============================================= + Data_Type data_type_; uint precision_; char flag_; diff --git a/src/Ipac_Table_Writer/Ipac_Table_Writer.cxx b/src/Ipac_Table_Writer/Ipac_Table_Writer.cxx index 9ac27be6..c1c26dca 100644 --- a/src/Ipac_Table_Writer/Ipac_Table_Writer.cxx +++ b/src/Ipac_Table_Writer/Ipac_Table_Writer.cxx @@ -11,7 +11,6 @@ #include "../Decimal_String_Trimmer.hxx" #include "../Table.hxx" - // This file contains high-level implementations of public functions of the // Ipac_Table_Writer class. @@ -31,16 +30,11 @@ const std::vector get_all_row_ids(const tablator::Table &table) { //==================================================== -// This templatized function is called for types other than CHAR and -// FLOAT64_LE, each of which has its own dedicated function (below). - -template -size_t compute_max_column_width_for_type(const tablator::Table &table, - const std::vector &requested_row_ids, - size_t col_idx, - uint8_t const *col_data_start_ptr, - size_t col_width_from_headers, - uint max_width_for_type, uint array_size) { +template >> +size_t compute_max_column_width_for_integral_type( + const tablator::Table &table, const std::vector &requested_row_ids, + size_t col_idx, uint8_t const *col_data_start_ptr, + size_t col_width_from_headers, uint max_width_for_type, uint array_size) { if (col_width_from_headers >= max_width_for_type) { return col_width_from_headers; } @@ -62,6 +56,7 @@ size_t compute_max_column_width_for_type(const tablator::Table &table, if (curr_array_elt == tablator::get_null()) { continue; } + std::string string_val = boost::lexical_cast(curr_array_elt); max_width_sofar = std::max(max_width_sofar, string_val.size()); @@ -76,16 +71,20 @@ size_t compute_max_column_width_for_type(const tablator::Table &table, //==================================================== -size_t compute_max_column_width_for_double(const tablator::Table &table, - const std::vector &requested_row_ids, - size_t col_idx, - uint8_t const *col_data_start_ptr, - size_t col_width_from_headers, - uint min_run_length_for_trimming, - uint max_width_for_double, uint array_size) { - if (col_width_from_headers >= max_width_for_double) { +template >> +size_t compute_max_column_width_for_floating_point_type( + const tablator::Table &table, const std::vector &requested_row_ids, + size_t col_idx, uint8_t const *col_data_start_ptr, + size_t col_width_from_headers, uint min_run_length_for_trim, + bool trim_decimal_runs_f, uint array_size) { + const auto &column = table.get_columns().at(col_idx); + const tablator::Format_Packet &format_packet = column.get_format_packet(); + auto max_width_for_type = format_packet.get_max_strlen(); + + if (col_width_from_headers >= max_width_for_type) { return col_width_from_headers; } + size_t max_width_sofar = col_width_from_headers; for (size_t requested_row_id : requested_row_ids) { @@ -96,21 +95,30 @@ size_t compute_max_column_width_for_double(const tablator::Table &table, // We've already accounted for the width of the null value. continue; } - const double *curr_array_elt_data_ptr = - reinterpret_cast(curr_col_data_ptr); + const T *curr_array_elt_data_ptr = + reinterpret_cast(curr_col_data_ptr); - for (uint j = 0; j < array_size && max_width_sofar < max_width_for_double; - ++j) { - double curr_array_elt = *curr_array_elt_data_ptr; + for (uint j = 0; j < array_size && max_width_sofar < max_width_for_type; ++j) { + T curr_array_elt = *curr_array_elt_data_ptr; if (curr_array_elt == tablator::get_null()) { continue; } - size_t curr_width = - tablator::Decimal_String_Trimmer::get_decimal_string_length( - curr_array_elt, min_run_length_for_trimming); - max_width_sofar = std::max(max_width_sofar, curr_width); - if (max_width_sofar >= max_width_for_double) { + const std::string &orig_formatted_string = + format_packet.get_formatted_value(curr_array_elt); + + if (trim_decimal_runs_f) { + size_t curr_width = + tablator::Decimal_String_Trimmer::get_decimal_string_length( + curr_array_elt, orig_formatted_string, + min_run_length_for_trim); + max_width_sofar = std::max(max_width_sofar, curr_width); + } else { + max_width_sofar = + std::max(max_width_sofar, orig_formatted_string.size()); + } + + if (max_width_sofar >= max_width_for_type) { return max_width_sofar; } ++curr_array_elt_data_ptr; @@ -119,6 +127,7 @@ size_t compute_max_column_width_for_double(const tablator::Table &table, return max_width_sofar; } + //==================================================== size_t compute_max_column_width_for_char(const tablator::Table &table, @@ -203,16 +212,13 @@ size_t tablator::Ipac_Table_Writer::get_single_column_width( static size_t MAX_INT64_STRLEN = ceil(log10(INT64_MAX)) + 1; static size_t MAX_UINT64_STRLEN = ceil(log10(UINT64_MAX)); - // https://stackoverflow.com/questions/2151302/counting-digits-in-a-float - static size_t MAX_FLOAT32_STRLEN = 15; - static size_t MAX_FLOAT64_STRLEN = 24; - - auto column = table.get_columns().at(col_idx); + auto &column = table.get_columns().at(col_idx); // Build lower bound for column width based on the 4 lines of // the header: name, unit, type, and null. auto type = column.get_type(); + uint array_size = column.get_array_size(); bool got_array = (array_size > 1); size_t max_width_sofar = to_ipac_string(type).size(); @@ -239,7 +245,6 @@ size_t tablator::Ipac_Table_Writer::get_single_column_width( (null_value.empty()) ? tablator::Table::DEFAULT_NULL_VALUE : null_value; max_width_sofar = std::max(max_width_sofar, null_str.size()); - const std::vector &table_data = table.get_data(); size_t col_offset = table.get_offsets().at(col_idx); uint8_t const *data_start_ptr = table_data.data(); @@ -251,68 +256,63 @@ size_t tablator::Ipac_Table_Writer::get_single_column_width( switch (type) { case Data_Type::INT8_LE: { // std::cout << "INT8" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_INT8_STRLEN, array_size); } break; case Data_Type::UINT8_LE: { // std::cout << "UINT8" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_UINT8_STRLEN, array_size); } break; case Data_Type::INT16_LE: { // std::cout << "INT16" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_INT16_STRLEN, array_size); } break; case Data_Type::UINT16_LE: { // std::cout << "UINT16" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_UINT16_STRLEN, array_size); } break; case Data_Type::INT32_LE: { // std::cout << "INT32" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_INT32_STRLEN, array_size); } break; case Data_Type::UINT32_LE: { // std::cout << "UINT32" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_UINT32_STRLEN, array_size); } break; case Data_Type::INT64_LE: { - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_INT64_STRLEN, array_size); } break; case Data_Type::UINT64_LE: { - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_integral_type( table, requested_row_ids, col_idx, col_data_start_ptr, max_width_sofar, MAX_UINT64_STRLEN, array_size); } break; case Data_Type::FLOAT32_LE: { // std::cout << "FLOAT32" << std::endl; - max_width_sofar = compute_max_column_width_for_type( + max_width_sofar = compute_max_column_width_for_floating_point_type( table, requested_row_ids, col_idx, col_data_start_ptr, - max_width_sofar, MAX_FLOAT32_STRLEN, array_size); + max_width_sofar, min_run_length_for_trim, + false /* trim_decimal_runs_f */, array_size); } break; case Data_Type::FLOAT64_LE: { // std::cout << "FLOAT64" << std::endl; - if (trim_decimal_runs_f) { - max_width_sofar = compute_max_column_width_for_double( - table, requested_row_ids, col_idx, col_data_start_ptr, - max_width_sofar, min_run_length_for_trim, MAX_FLOAT64_STRLEN, - array_size); - } else { - max_width_sofar = compute_max_column_width_for_type( - table, requested_row_ids, col_idx, col_data_start_ptr, - max_width_sofar, MAX_FLOAT64_STRLEN, array_size); - } + max_width_sofar = compute_max_column_width_for_floating_point_type( + table, requested_row_ids, col_idx, col_data_start_ptr, + max_width_sofar, min_run_length_for_trim, trim_decimal_runs_f, + array_size); } break; case Data_Type::CHAR: { max_width_sofar = compute_max_column_width_for_char(