From: Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org>
To: linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org
Cc: Andrew Morton <akpm@linux-foundation.org>,
Jonathan Cameron <jic23@kernel.org>,
David Lechner <dlechner@baylibre.com>,
Andy Shevchenko <andy@kernel.org>,
Rodrigo Alencar <rodrigo.alencar@analog.com>
Subject: [PATCH v5 3/7] lib: kstrtox: allow a negative scale in kstrto[u]dec64()
Date: Mon, 02 Nov 2026 07:41:01 -0300 [thread overview]
Message-ID: <20261102-e-notation-v5-3-d399e544a982@analog.com> (raw)
In-Reply-To: <20261102-e-notation-v5-0-d399e544a982@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
With E notation support, the scale is added to the exponent, and a
negative effective exponent already drops integer digits with rounding
to nearest, ties to even. A negative scale could be handled the same
way, but the scale is declared unsigned int.
Change the scale parameter of kstrtoudec64() and kstrtodec64() to int
and describe a negative scale in the kernel-doc. For example, a scale of
-2 with input "12355" results in 124. This lets callers parse a value
into a coarser unit than the input, such as Hz into kHz, without a
separate division and rounding step.
Behavior doesn't change for any existing caller, since they all pass a
non-negative scale.
Add dedicated test macros for kstrto[u]dec64() with a signed scale
field, as the generic ones carry an unsigned base, and add test cases
for negative scales: dropped integer digits with ties to even, results
at the type limits, rounding past them, and exponents that overflow when
added to the scale.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
include/linux/kstrtox.h | 4 +-
lib/kstrtox.c | 20 +++++-----
lib/test-kstrtox.c | 101 ++++++++++++++++++++++++++++++++++++++++++++----
3 files changed, 106 insertions(+), 19 deletions(-)
diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h
index b41b9736886f..e2d5870c979b 100644
--- a/include/linux/kstrtox.h
+++ b/include/linux/kstrtox.h
@@ -97,8 +97,8 @@ int __must_check kstrtou8(const char *s, unsigned int base, u8 *res);
int __must_check kstrtos8(const char *s, unsigned int base, s8 *res);
int __must_check kstrtobool(const char *s, bool *res);
-int __must_check kstrtoudec64(const char *s, unsigned int scale, u64 *res);
-int __must_check kstrtodec64(const char *s, unsigned int scale, s64 *res);
+int __must_check kstrtoudec64(const char *s, int scale, u64 *res);
+int __must_check kstrtodec64(const char *s, int scale, s64 *res);
int __must_check kstrtoull_from_user(const char __user *s, size_t count, unsigned int base, unsigned long long *res);
int __must_check kstrtoll_from_user(const char __user *s, size_t count, unsigned int base, long long *res);
diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index 192f2067cad1..ab4822c24627 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -424,7 +424,7 @@ static u64 round_half_even(const char *p, const char *end, u64 res)
return res & 1;
}
-static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+static int _kstrtoudec64(const char *s, int scale, u64 *res)
{
const char *s_int, *s_frac, *s_end, *s_round;
int len_int, len_frac;
@@ -500,18 +500,19 @@ static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
* @s: The start of the string. The string must be null-terminated, and may also
* include a single newline before its terminating null. The first character
* may also be a plus sign, but not a minus sign.
- * @scale: The number of digits to the right of the decimal point.
+ * @scale: The number of digits to the right of the decimal point. A negative
+ * scale drops digits to the left of the decimal point instead.
* @res: Where to write the result of the conversion on success.
*
- * For example, a scale of 3 with input "123.45" results in 123450. Note that
- * trailing zeros in the fractional part input to match the scale are not
- * required. Also, digits beyond the specified scale are rounded to nearest,
- * ties to even.
+ * For example, a scale of 3 with input "123.45" results in 123450, and a
+ * scale of -2 with input "12355" results in 124. Note that trailing zeros in
+ * the fractional part input to match the scale are not required. Also, digits
+ * beyond the specified scale are rounded to nearest, ties to even.
*
* Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
*/
noinline
-int kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+int kstrtoudec64(const char *s, int scale, u64 *res)
{
if (s[0] == '+')
s++;
@@ -524,7 +525,8 @@ EXPORT_SYMBOL(kstrtoudec64);
* @s: The start of the string. The string must be null-terminated, and may also
* include a single newline before its terminating null. The first character
* may also be a plus sign or a minus sign.
- * @scale: The number of digits to the right of the decimal point.
+ * @scale: The number of digits to the right of the decimal point. A negative
+ * scale drops digits to the left of the decimal point instead.
* @res: Where to write the result of the conversion on success.
*
* For example, a scale of 4 with input "-3.141592" results in -31416. Note
@@ -535,7 +537,7 @@ EXPORT_SYMBOL(kstrtoudec64);
* Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
*/
noinline
-int kstrtodec64(const char *s, unsigned int scale, s64 *res)
+int kstrtodec64(const char *s, int scale, s64 *res)
{
u64 tmp;
int rv;
diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c
index 1f176b232085..8ba0f23bb8ef 100644
--- a/lib/test-kstrtox.c
+++ b/lib/test-kstrtox.c
@@ -709,9 +709,66 @@ static void __init test_kstrtos8_fail(void)
TEST_FAIL(kstrtos8, s8, "%hhd", test_s8_fail);
}
+struct test_dec_fail {
+ const char *str;
+ int scale;
+};
+
+#define DEFINE_TEST_DEC_FAIL(test) \
+ const struct test_dec_fail test[] __initconst
+
+#define DECLARE_TEST_DEC_OK(type, test_type) \
+ struct test_type { \
+ const char *str; \
+ int scale; \
+ type expected_res; \
+ }
+
+#define TEST_DEC_FAIL(fn, type, fmt, test) \
+{ \
+ unsigned int i; \
+ \
+ for_each_test(i, test) { \
+ const struct test_dec_fail *t = &test[i]; \
+ type tmp; \
+ int rv; \
+ \
+ tmp = 0; \
+ rv = fn(t->str, t->scale, &tmp); \
+ if (rv >= 0) { \
+ WARN(1, "str '%s', scale %d, expected -E, got %d/" fmt "\n", \
+ t->str, t->scale, rv, tmp); \
+ continue; \
+ } \
+ } \
+}
+
+#define TEST_DEC_OK(fn, type, fmt, test) \
+{ \
+ unsigned int i; \
+ \
+ for_each_test(i, test) { \
+ const typeof(test[0]) *t = &test[i]; \
+ type res; \
+ int rv; \
+ \
+ rv = fn(t->str, t->scale, &res); \
+ if (rv != 0) { \
+ WARN(1, "str '%s', scale %d, expected 0/" fmt ", got %d\n", \
+ t->str, t->scale, t->expected_res, rv); \
+ continue; \
+ } \
+ if (res != t->expected_res) { \
+ WARN(1, "str '%s', scale %d, expected " fmt ", got " fmt "\n", \
+ t->str, t->scale, t->expected_res, res); \
+ continue; \
+ } \
+ } \
+}
+
static void __init test_kstrtoudec64_ok(void)
{
- DECLARE_TEST_OK(u64, struct test_udec64);
+ DECLARE_TEST_DEC_OK(u64, test_udec64);
static DEFINE_TEST_OK(struct test_udec64, test_udec64_ok) = {
/* basic: integer.fraction, exact digits */
{"0.0", 1, 0},
@@ -781,13 +838,25 @@ static void __init test_kstrtoudec64_ok(void)
/* rounding with many excess digits */
{"0.00000000000000000000000000000000423", 34, 42},
{"1.99999999999999999999999999999999999", 3, 2000},
+ /* negative scale: drops integer digits */
+ {"12345", -2, 123ULL},
+ {"12355", -2, 124ULL},
+ {"1250", -2, 12ULL},
+ {"1350", -2, 14ULL},
+ {"123.45", -3, 0ULL},
+ {"500", -3, 0ULL},
+ {"500.1", -3, 1ULL},
+ {"999", -3, 1ULL},
+ {"999", -4, 0ULL},
+ {"2.7e10", -9, 27ULL},
+ {"18446744073709551615000", -3, ULLONG_MAX},
};
- TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok);
+ TEST_DEC_OK(kstrtoudec64, u64, "%llu", test_udec64_ok);
}
static void __init test_kstrtoudec64_fail(void)
{
- static DEFINE_TEST_FAIL(test_udec64_fail) = {
+ static DEFINE_TEST_DEC_FAIL(test_udec64_fail) = {
/* empty / whitespace */
{"", 3},
{"\n", 3},
@@ -814,13 +883,17 @@ static void __init test_kstrtoudec64_fail(void)
{"0.2", 20},
{"0.18446744073709551616", 20},
{"1", 20},
+ /* negative scale: rounds up past the limit */
+ {"18446744073709551615500", -3},
+ /* negative scale: exponent overflows when added to it */
+ {"1e-2147483648", -1},
};
- TEST_FAIL(kstrtoudec64, u64, "%llu", test_udec64_fail);
+ TEST_DEC_FAIL(kstrtoudec64, u64, "%llu", test_udec64_fail);
}
static void __init test_kstrtodec64_ok(void)
{
- DECLARE_TEST_OK(s64, struct test_dec64);
+ DECLARE_TEST_DEC_OK(s64, test_dec64);
static DEFINE_TEST_OK(struct test_dec64, test_dec64_ok) = {
/* basic positive */
{"0.0", 1, 0},
@@ -873,13 +946,20 @@ static void __init test_kstrtodec64_ok(void)
{"-0.00000000000000000001", 20, -1},
{"0.009223372036854775807", 21, LLONG_MAX},
{"-0.009223372036854775808", 21, LLONG_MIN},
+ /* negative scale: drops integer digits */
+ {"-12345", -2, -123LL},
+ {"-12355", -2, -124LL},
+ {"-1250", -2, -12LL},
+ {"-1350", -2, -14LL},
+ {"-999", -4, 0LL},
+ {"-9223372036854775808000", -3, LLONG_MIN},
};
- TEST_OK(kstrtodec64, s64, "%lld", test_dec64_ok);
+ TEST_DEC_OK(kstrtodec64, s64, "%lld", test_dec64_ok);
}
static void __init test_kstrtodec64_fail(void)
{
- static DEFINE_TEST_FAIL(test_dec64_fail) = {
+ static DEFINE_TEST_DEC_FAIL(test_dec64_fail) = {
/* empty / whitespace */
{"", 3},
{"\n", 3},
@@ -903,8 +983,13 @@ static void __init test_kstrtodec64_fail(void)
{"-0.1", 20},
{"0.09223372036854775808", 20},
{"-0.09223372036854775809", 20},
+ /* negative scale: rounds up past the limit */
+ {"9223372036854775807500", -3},
+ {"-9223372036854775808501", -3},
+ /* negative scale: exponent overflows when added to it */
+ {"-1e-2147483648", -1},
};
- TEST_FAIL(kstrtodec64, s64, "%lld", test_dec64_fail);
+ TEST_DEC_FAIL(kstrtodec64, s64, "%lld", test_dec64_fail);
}
static int __init test_kstrtox_init(void)
--
2.43.0
next prev parent reply other threads:[~2026-11-02 10:41 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 10:40 [PATCH v5 0/7] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
2026-11-02 10:40 ` [PATCH v5 1/7] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
2026-11-02 10:41 ` [PATCH v5 2/7] lib: kstrtox: round to nearest, ties to even in kstrto[u]dec64() Rodrigo Alencar via B4 Relay
2026-11-02 10:41 ` Rodrigo Alencar via B4 Relay [this message]
2026-11-02 10:41 ` [PATCH v5 4/7] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() Rodrigo Alencar via B4 Relay
2026-11-02 10:41 ` [PATCH v5 5/7] iio: test: rescale: compare scales against the precision of the output Rodrigo Alencar via B4 Relay
2026-11-02 10:41 ` [PATCH v5 6/7] iio: core: use kstrtodec64() to parse fixed-point values Rodrigo Alencar via B4 Relay
2026-11-02 10:41 ` [PATCH v5 7/7] iio: test: add kunit tests for channel info writes Rodrigo Alencar via B4 Relay
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261102-e-notation-v5-3-d399e544a982@analog.com \
--to=devnull+rodrigo.alencar.analog.com@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=andy@kernel.org \
--cc=dlechner@baylibre.com \
--cc=jic23@kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rodrigo.alencar@analog.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®