say.c: Fix cents off-by-one due to floating point rounding.

Some of the money announcements can be off by one cent,
due to the use of floating point in the money calculations,
which is bad for obvious reasons.

This replaces floating point with simple string parsing
to ensure the cents value is converted accurately.

Resolves: #525
This commit is contained in:
Naveen Albert
2024-01-10 08:26:05 -05:00
parent c276ae11e0
commit b836f9c01c
2 changed files with 106 additions and 13 deletions

View File

@@ -356,6 +356,14 @@ AST_TEST_DEFINE(test_SAYFILES_function)
res = AST_TEST_FAIL; res = AST_TEST_FAIL;
} }
ast_str_set(&expr, 0, "${SAYFILES(.42,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/40&digits/2&cents") != 0) {
ast_test_status_update(test, "SAYFILES(.42,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(1.00,money)}"); ast_str_set(&expr, 0, "${SAYFILES(1.00,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr)); ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar") != 0) { if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar") != 0) {
@@ -380,6 +388,14 @@ AST_TEST_DEFINE(test_SAYFILES_function)
res = AST_TEST_FAIL; res = AST_TEST_FAIL;
} }
ast_str_set(&expr, 0, "${SAYFILES(2,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
ast_test_status_update(test, "SAYFILES(2,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(2.42,money)}"); ast_str_set(&expr, 0, "${SAYFILES(2.42,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr)); ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/40&digits/2&cents") != 0) { if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/40&digits/2&cents") != 0) {
@@ -388,6 +404,63 @@ AST_TEST_DEFINE(test_SAYFILES_function)
res = AST_TEST_FAIL; res = AST_TEST_FAIL;
} }
ast_str_set(&expr, 0, "${SAYFILES(2.05,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/5&cents") != 0) {
ast_test_status_update(test, "SAYFILES(2.05,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(2.051,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/5&cents") != 0) {
ast_test_status_update(test, "SAYFILES(2.051,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
/* Invalid amounts */
ast_str_set(&expr, 0, "${SAYFILES(blah,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/0&cents") != 0) {
ast_test_status_update(test, "SAYFILES(blah,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(2blah.05,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
ast_test_status_update(test, "SAYFILES(2blah.05,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(2.-05,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
ast_test_status_update(test, "SAYFILES(2.-05,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(2. 05,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
ast_test_status_update(test, "SAYFILES(2. 05,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_str_set(&expr, 0, "${SAYFILES(. 05,money)}");
ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
if (strcmp(ast_str_buffer(result), "digits/0&cents") != 0) {
ast_test_status_update(test, "SAYFILES(. 05,money) test failed ('%s')\n",
ast_str_buffer(result));
res = AST_TEST_FAIL;
}
ast_free(expr); ast_free(expr);
ast_free(result); ast_free(result);

View File

@@ -56,6 +56,7 @@
#include "asterisk/utils.h" #include "asterisk/utils.h"
#include "asterisk/app.h" #include "asterisk/app.h"
#include "asterisk/test.h" #include "asterisk/test.h"
#include "asterisk/cli.h" /* use ESS */
/* Forward declaration */ /* Forward declaration */
static int wait_file(struct ast_channel *chan, const char *ints, const char *file, const char *lang); static int wait_file(struct ast_channel *chan, const char *ints, const char *file, const char *lang);
@@ -353,26 +354,47 @@ static int say_digit_str_full(struct ast_channel *chan, const char *str, const c
static struct ast_str* ast_get_money_en_dollars_str(const char *str, const char *lang) static struct ast_str* ast_get_money_en_dollars_str(const char *str, const char *lang)
{ {
const char *fnr; const char *fnr;
int amt, dollars = 0, cents = 0;
double dollars = 0;
int amt, cents;
struct ast_str *fnrecurse = NULL; struct ast_str *fnrecurse = NULL;
struct ast_str *filenames;
struct ast_str *filenames = ast_str_create(20); if (ast_strlen_zero(str)) {
return NULL;
}
filenames = ast_str_create(20);
if (!filenames) { if (!filenames) {
return NULL; return NULL;
} }
ast_str_reset(filenames); ast_str_reset(filenames);
if (sscanf(str, "%30lf", &dollars) != 1) { /* Don't use %f because floating point rounding
amt = 0; * could distort the cents units. Just parse as string. */
} else { /* convert everything to cents */ if (str && *str == '.') {
amt = dollars * 100; if (sscanf(str, ".%02u", &cents) < 1) {
dollars = cents = 0;
} else {
/* If we have a space instead of numbers after '.',
* then it's not quite valid. */
const char *period = strchr(str, '.');
if (period && !isdigit(*(period + 1))) {
cents = 0;
}
}
} else {
int res = sscanf(str, "%d.%02u", &dollars, &cents);
if (res < 1) {
dollars = cents = 0;
} else if (res == 2) {
const char *period = strchr(str, '.');
if (period && !isdigit(*(period + 1))) {
cents = 0;
}
}
} }
amt = dollars * 100 + cents; /* convert everything to cents */
/* Just the cents after the dollar decimal point */ ast_debug(1, "Amount is %d (%d dollar%s, %d cent%s)\n", amt, dollars, ESS(dollars), cents, ESS(cents));
cents = amt - (((int) dollars) * 100);
ast_debug(1, "Cents is %d, amount is %d\n", cents, amt);
if (amt >= 100) { if (amt >= 100) {
fnrecurse = ast_get_number_str((amt / 100), lang); fnrecurse = ast_get_number_str((amt / 100), lang);
@@ -9758,8 +9780,6 @@ int ast_say_counted_adjective(struct ast_channel *chan, int num, const char adje
return ast_play_and_wait(chan, temp); return ast_play_and_wait(chan, temp);
} }
/*! \brief /*! \brief
* remap the 'say' functions to use those in this file * remap the 'say' functions to use those in this file
*/ */