xref: /src/lib/libc/string/strverscmp.c (revision 05c9a0158f6837bb3a3781e4ed75f66115f6415a)
105c9a015SAymeric Wibo /*-
205c9a015SAymeric Wibo * SPDX-License-Identifier: BSD-2-Clause
305c9a015SAymeric Wibo * Copyright (c) 2022 Aymeric Wibo <obiwac@gmail.com>
405c9a015SAymeric Wibo */
505c9a015SAymeric Wibo 
605c9a015SAymeric Wibo #include <ctype.h>
705c9a015SAymeric Wibo #include <stddef.h>
805c9a015SAymeric Wibo 
905c9a015SAymeric Wibo int
strverscmp(const char * s1,const char * s2)1005c9a015SAymeric Wibo strverscmp(const char *s1, const char *s2)
1105c9a015SAymeric Wibo {
1205c9a015SAymeric Wibo 	size_t digit_count_1, digit_count_2;
1305c9a015SAymeric Wibo 	size_t zeros_count_1, zeros_count_2;
1405c9a015SAymeric Wibo 	const unsigned char *num_1, *num_2;
1505c9a015SAymeric Wibo 	const unsigned char *u1 = __DECONST(const unsigned char *, s1);
1605c9a015SAymeric Wibo 	const unsigned char *u2 = __DECONST(const unsigned char *, s2);
1705c9a015SAymeric Wibo 
1805c9a015SAymeric Wibo 	/*
1905c9a015SAymeric Wibo 	 * If pointers are the same, no need to go through to process of
2005c9a015SAymeric Wibo 	 * comparing them.
2105c9a015SAymeric Wibo 	 */
2205c9a015SAymeric Wibo 	if (s1 == s2)
2305c9a015SAymeric Wibo 		return (0);
2405c9a015SAymeric Wibo 
2505c9a015SAymeric Wibo 	while (*u1 != '\0' && *u2 != '\0') {
2605c9a015SAymeric Wibo 		/* If either character is not a digit, act like strcmp(3). */
2705c9a015SAymeric Wibo 
2805c9a015SAymeric Wibo 		if (!isdigit(*u1) || !isdigit(*u2)) {
2905c9a015SAymeric Wibo 			if (*u1 != *u2)
3005c9a015SAymeric Wibo 				return (*u1 - *u2);
3105c9a015SAymeric Wibo 			u1++;
3205c9a015SAymeric Wibo 			u2++;
3305c9a015SAymeric Wibo 			continue;
3405c9a015SAymeric Wibo 		}
3505c9a015SAymeric Wibo 		if (*u1 == '0' || *u2 == '0') {
3605c9a015SAymeric Wibo 			/*
3705c9a015SAymeric Wibo 			 * Treat leading zeros as if they were the fractional
3805c9a015SAymeric Wibo 			 * part of a number, i.e. as if they had a decimal point
3905c9a015SAymeric Wibo 			 * in front. First, count the leading zeros (more zeros
4005c9a015SAymeric Wibo 			 * == smaller number).
4105c9a015SAymeric Wibo 			 */
4205c9a015SAymeric Wibo 			zeros_count_1 = 0;
4305c9a015SAymeric Wibo 			zeros_count_2 = 0;
4405c9a015SAymeric Wibo 			for (; *u1 == '0'; u1++)
4505c9a015SAymeric Wibo 				zeros_count_1++;
4605c9a015SAymeric Wibo 			for (; *u2 == '0'; u2++)
4705c9a015SAymeric Wibo 				zeros_count_2++;
4805c9a015SAymeric Wibo 			if (zeros_count_1 != zeros_count_2)
4905c9a015SAymeric Wibo 				return (zeros_count_2 - zeros_count_1);
5005c9a015SAymeric Wibo 
5105c9a015SAymeric Wibo 			/* Handle the case where 0 < 09. */
5205c9a015SAymeric Wibo 			if (!isdigit(*u1) && isdigit(*u2))
5305c9a015SAymeric Wibo 				return (1);
5405c9a015SAymeric Wibo 			if (!isdigit(*u2) && isdigit(*u1))
5505c9a015SAymeric Wibo 				return (-1);
5605c9a015SAymeric Wibo 		} else {
5705c9a015SAymeric Wibo 			/*
5805c9a015SAymeric Wibo 			 * No leading zeros; we're simply comparing two numbers.
5905c9a015SAymeric Wibo 			 * It is necessary to first count how many digits there
6005c9a015SAymeric Wibo 			 * are before going back to compare each digit, so that
6105c9a015SAymeric Wibo 			 * e.g. 7 is not considered larger than 60.
6205c9a015SAymeric Wibo 			 */
6305c9a015SAymeric Wibo 			num_1 = u1;
6405c9a015SAymeric Wibo 			num_2 = u2;
6505c9a015SAymeric Wibo 
6605c9a015SAymeric Wibo 			/* Count digits (more digits == larger number). */
6705c9a015SAymeric Wibo 			for (; isdigit(*u1); u1++)
6805c9a015SAymeric Wibo 				;
6905c9a015SAymeric Wibo 			for (; isdigit(*u2); u2++)
7005c9a015SAymeric Wibo 				;
7105c9a015SAymeric Wibo 			digit_count_1 = u1 - num_1;
7205c9a015SAymeric Wibo 			digit_count_2 = u2 - num_2;
7305c9a015SAymeric Wibo 			if (digit_count_1 != digit_count_2)
7405c9a015SAymeric Wibo 				return (digit_count_1 - digit_count_2);
7505c9a015SAymeric Wibo 
7605c9a015SAymeric Wibo 			/*
7705c9a015SAymeric Wibo 			 * If there are the same number of digits, go back to
7805c9a015SAymeric Wibo 			 * the start of the number.
7905c9a015SAymeric Wibo 			 */
8005c9a015SAymeric Wibo 			u1 = num_1;
8105c9a015SAymeric Wibo 			u2 = num_2;
8205c9a015SAymeric Wibo 		}
8305c9a015SAymeric Wibo 
8405c9a015SAymeric Wibo 		/* Compare each digit until there are none left. */
8505c9a015SAymeric Wibo 		for (; isdigit(*u1) && isdigit(*u2); u1++, u2++) {
8605c9a015SAymeric Wibo 			if (*u1 != *u2)
8705c9a015SAymeric Wibo 				return (*u1 - *u2);
8805c9a015SAymeric Wibo 		}
8905c9a015SAymeric Wibo 	}
9005c9a015SAymeric Wibo 	return (*u1 - *u2);
9105c9a015SAymeric Wibo }
92