Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion include/jsonata/Functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class Functions {
static std::optional<std::string> dateTimeFromMillis(
int64_t millis, const std::string& picture = "",
const std::string& timezone = "UTC");
static std::optional<std::string> formatInteger(int64_t value,
static std::optional<std::string> formatInteger(double value,
const std::string& picture);
static std::optional<int64_t> parseInteger(const std::string& value,
const std::string& picture);
Expand Down
8 changes: 4 additions & 4 deletions include/jsonata/utils/DateTimeUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ namespace utils {
class DateTimeUtils {
public:
// Number to words conversion
static std::string numberToWords(int64_t value, bool ordinal = false);
static std::string numberToWords(double value, bool ordinal = false);
static int64_t wordsToNumber(const std::string& text);
static int64_t wordsToLong(const std::string& text);

Expand All @@ -68,7 +68,7 @@ class DateTimeUtils {
static std::string decimalToLetters(int64_t value, const std::string& aChar);

// Integer formatting
static std::string formatInteger(int64_t value, const std::string& picture);
static std::string formatInteger(double value, const std::string& picture);

// Date/time formatting and parsing
static std::string formatDateTime(int64_t millis,
Expand Down Expand Up @@ -251,8 +251,8 @@ class DateTimeUtils {
defaultPresentationModifiers;

// Static helper functions
static std::string lookup(int64_t num, bool prev, bool ord);
static std::string formatInteger(int64_t value, const Format& format);
static std::string lookup(double num, bool prev, bool ord);
static std::string formatInteger(double value, const Format& format);
static Format analyseIntegerPicture(const std::string& picture);
static int64_t getRegularRepeat(
const std::vector<GroupingSeparator>& separators);
Expand Down
9 changes: 5 additions & 4 deletions src/jsonata/Functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1297,9 +1297,10 @@ Functions::getFunctionRegistry() {
if (args.size() < 2 || !isNumber(args[0]) ||
!isString(args[1]))
return std::any();
// Java: value.longValue() - handle int, long, double types
// properly
int64_t value = Utils::toLong(args[0]);
// Keep the value as a double so magnitudes beyond the
// int64 range (e.g. 1e46) are formatted correctly instead
// of overflowing on narrowing to a 64-bit integer.
double value = Utils::toDouble(args[0]);
auto picture = std::any_cast<std::string>(args[1]);
auto result = formatInteger(value, picture);
return result ? std::any(*result) : std::any();
Expand Down Expand Up @@ -4803,7 +4804,7 @@ std::optional<std::string> Functions::dateTimeFromMillis(
}

std::optional<std::string> Functions::formatInteger(
int64_t value, const std::string& picture) {
double value, const std::string& picture) {
// Match Java implementation exactly: return
// DateTimeUtils.formatInteger(value.longValue(), picture);
try {
Expand Down
46 changes: 32 additions & 14 deletions src/jsonata/utils/DateTimeUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,30 @@ DateTimeUtils::createDefaultPresentationModifiers() {
}

// Public methods implementation
std::string DateTimeUtils::numberToWords(int64_t value, bool ordinal) {
std::string DateTimeUtils::numberToWords(double value, bool ordinal) {
return lookup(value, false, ordinal);
}

std::string DateTimeUtils::lookup(int64_t num, bool prev, bool ord) {
// Operates on double (matching the JSONata JS reference) so that values beyond
// the int64 range (e.g. $formatInteger(1e46, 'w')) are handled instead of
// overflowing to a negative/garbage index into the word tables.
std::string DateTimeUtils::lookup(double num, bool prev, bool ord) {
std::string words = "";
if (num <= 19) {
words = (prev ? " and " : "") + (ord ? ordinals[num] : few[num]);
size_t idx = static_cast<size_t>(num);
words = (prev ? " and " : "") + (ord ? ordinals[idx] : few[idx]);
} else if (num < 100) {
int64_t tens = static_cast<int64_t>(num) / 10;
int64_t remainder = static_cast<int64_t>(num) % 10;
size_t tens = static_cast<size_t>(std::floor(num / 10));
int64_t remainder = static_cast<int64_t>(std::fmod(num, 10));
words = (prev ? " and " : "") + decades[tens - 2];
if (remainder > 0) {
words += "-" + lookup(remainder, false, ord);
} else if (ord) {
words = words.substr(0, words.length() - 1) + "ieth";
}
} else if (num < 1000) {
int64_t hundreds = static_cast<int64_t>(num) / 100;
int64_t remainder = static_cast<int64_t>(num) % 100;
size_t hundreds = static_cast<size_t>(std::floor(num / 100));
double remainder = std::fmod(num, 100);
words = (prev ? ", " : "") + few[hundreds] + " Hundred";
if (remainder > 0) {
words += lookup(remainder, true, ord);
Expand All @@ -230,9 +234,15 @@ std::string DateTimeUtils::lookup(int64_t num, bool prev, bool ord) {
if (mag > static_cast<int64_t>(magnitudes.size())) {
mag = static_cast<int64_t>(magnitudes.size()); // the largest word
}
int64_t factor = static_cast<int64_t>(std::pow(10, mag * 3));
int64_t mant = static_cast<int64_t>(std::floor(num / factor));
int64_t remainder = num - mant * factor;
double factor = std::pow(10, static_cast<double>(mag * 3));
double mant = std::floor(num / factor);
// Compute the product in its own statement so it is rounded to a double
// before the subtraction. This matches the JS reference's semantics and
// prevents the compiler from fusing this into a single full-precision
// multiply-add (FMA), which would leave a spurious non-zero remainder
// for large magnitudes such as 1e46.
double product = mant * factor;
double remainder = num - product;
words = (prev ? ", " : "") + lookup(mant, false, false) + " " +
magnitudes[mag - 1];
if (remainder > 0) {
Expand Down Expand Up @@ -399,7 +409,7 @@ int64_t DateTimeUtils::lettersToDecimal(const std::string& letters, char aChar)
}

// Basic formatInteger method (simplified for now)
std::string DateTimeUtils::formatInteger(int64_t value,
std::string DateTimeUtils::formatInteger(double value,
const std::string& picture) {
Format format = analyseIntegerPicture(picture);
return formatInteger(value, format);
Expand Down Expand Up @@ -549,10 +559,12 @@ DateTimeUtils::Format DateTimeUtils::analyseIntegerPicture(
return format;
}

std::string DateTimeUtils::formatInteger(int64_t value, const Format& format) {
std::string DateTimeUtils::formatInteger(double value, const Format& format) {
std::string formattedInteger = "";
// Match the JS reference: value = Math.floor(value) before formatting.
value = std::floor(value);
bool negative = value < 0;
value = std::abs(value);
value = std::fabs(value);

switch (format.primary) {
case Formats::LETTERS:
Expand Down Expand Up @@ -581,7 +593,13 @@ std::string DateTimeUtils::formatInteger(int64_t value, const Format& format) {
break;

case Formats::DECIMAL: {
formattedInteger = std::to_string(value);
// value is a non-negative, integral double here; render it as a
// plain decimal string (no scientific notation, no fractional part).
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(0) << value;
formattedInteger = oss.str();
}

// Pad with zeros if needed
int64_t padLength = format.mandatoryDigits -
Expand Down
5 changes: 0 additions & 5 deletions test/test-overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
},


{
"name": "function-formatInteger/formatInteger.json_43",
"ignoreError": true,
"reason": "Do not support number to word for numbers exceeding the 64-bit range"
},
{
"name": "function-parseInteger/parseInteger.json_11",
"ignoreError": true,
Expand Down
Loading