]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
say.c: Fix cents off-by-one due to floating point rounding.
authorNaveen Albert <asterisk@phreaknet.org>
Wed, 10 Jan 2024 13:26:05 +0000 (08:26 -0500)
committerasterisk-org-access-app[bot] <120671045+asterisk-org-access-app[bot]@users.noreply.github.com>
Tue, 30 Apr 2024 15:17:21 +0000 (15:17 +0000)
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

funcs/func_sayfiles.c
main/say.c

index 7e0ece2c966ae85e5d8b7c2161bcb5dbb5a37241..108126ea145faafc4275036753af9115dd2c203e 100644 (file)
@@ -356,6 +356,14 @@ AST_TEST_DEFINE(test_SAYFILES_function)
                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_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
        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;
        }
 
+       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_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) {
@@ -388,6 +404,63 @@ AST_TEST_DEFINE(test_SAYFILES_function)
                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(result);
 
index 300581afb763fbe10891ce2ad043e78a7b12e872..5002e8fc838d7ad364b14a3b931b5234dce35a94 100644 (file)
@@ -56,6 +56,7 @@
 #include "asterisk/utils.h"
 #include "asterisk/app.h"
 #include "asterisk/test.h"
+#include "asterisk/cli.h" /* use ESS */
 
 /* Forward declaration */
 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)
 {
        const char *fnr;
-
-       double dollars = 0;
-       int amt, cents;
+       int amt, dollars = 0, cents = 0;
        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) {
                return NULL;
        }
        ast_str_reset(filenames);
 
-       if (sscanf(str, "%30lf", &dollars) != 1) {
-               amt = 0;
-       } else { /* convert everything to cents */
-               amt = dollars * 100;
+       /* Don't use %f because floating point rounding
+        * could distort the cents units. Just parse as string. */
+       if (str && *str == '.') {
+               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 */
-       cents = amt - (((int) dollars) * 100);
-       ast_debug(1, "Cents is %d, amount is %d\n", cents, amt);
+       ast_debug(1, "Amount is %d (%d dollar%s, %d cent%s)\n", amt, dollars, ESS(dollars), cents, ESS(cents));
 
        if (amt >= 100) {
                fnrecurse = ast_get_number_str((amt / 100), lang);
@@ -9763,8 +9785,6 @@ int ast_say_counted_adjective(struct ast_channel *chan, int num, const char adje
        return ast_play_and_wait(chan, temp);
 }
 
-
-
 /*! \brief
  * remap the 'say' functions to use those in this file
  */