seq(1): Consistently include 'last' for non-integers

The source of error is a rounded increment being too large and thus the loop
steps slightly past 'last'.  Perform a final comparison using the formatted
string values (truncated precision) to determine if we still need to print
the 'last' value.

PR:		217149
Submitted by:	Fernando Apesteguía <fernando.apesteguia AT gmail.com>,
		Yuri Pankov <yuripv AT icloud.com> (earlier version)
Reported by:	Martijn Dekker <mcdutchie AT hotmail.com>
Sponsored by:	Dell EMC Isilon
This commit is contained in:
Conrad Meyer 2018-02-27 22:01:40 +00:00
parent 3acf1760b7
commit 3049d4ccc0
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=330086
4 changed files with 89 additions and 19 deletions

View File

@ -1,8 +1,13 @@
# $NetBSD: Makefile,v 1.3 2009/04/14 22:15:26 lukem Exp $
# $FreeBSD$
.include <src.opts.mk>
PROG= seq
LIBADD= m
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests
.include <bsd.prog.mk>

View File

@ -76,16 +76,19 @@ static char *unescape(char *);
int
main(int argc, char *argv[])
{
int c = 0, errflg = 0;
int equalize = 0;
double first = 1.0;
double last = 0.0;
double incr = 0.0;
const char *sep, *term;
struct lconv *locale;
char *fmt = NULL;
const char *sep = "\n";
const char *term = NULL;
char pad = ZERO;
char pad, *fmt, *cur_print, *last_print;
double first, last, incr, last_shown_value, cur, step;
int c, errflg, equalize;
pad = ZERO;
fmt = NULL;
first = 1.0;
last = incr = last_shown_value = 0.0;
c = errflg = equalize = 0;
sep = "\n";
term = NULL;
/* Determine the locale's decimal point. */
locale = localeconv();
@ -169,17 +172,32 @@ main(int argc, char *argv[])
} else
fmt = generate_format(first, incr, last, equalize, pad);
if (incr > 0) {
for (; first <= last; first += incr) {
printf(fmt, first);
fputs(sep, stdout);
}
} else {
for (; first >= last; first += incr) {
printf(fmt, first);
fputs(sep, stdout);
}
for (step = 1, cur = first; incr > 0 ? cur <= last : cur >= last;
cur = first + incr * step++) {
printf(fmt, cur);
fputs(sep, stdout);
last_shown_value = cur;
}
/*
* Did we miss the last value of the range in the loop above?
*
* We might have, so check if the printable version of the last
* computed value ('cur') and desired 'last' value are equal. If they
* are equal after formatting truncation, but 'cur' and
* 'last_shown_value' are not equal, it means the exit condition of the
* loop held true due to a rounding error and we still need to print
* 'last'.
*/
asprintf(&cur_print, fmt, cur);
asprintf(&last_print, fmt, last);
if (strcmp(cur_print, last_print) == 0 && cur != last_shown_value) {
fputs(last_print, stdout);
fputs(sep, stdout);
}
free(cur_print);
free(last_print);
if (term != NULL)
fputs(term, stdout);

View File

@ -0,0 +1,7 @@
# $FreeBSD$
PACKAGE= tests
ATF_TESTS_SH= seq_test
.include <bsd.test.mk>

40
usr.bin/seq/tests/seq_test.sh Executable file
View File

@ -0,0 +1,40 @@
# Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD$
atf_test_case float_rounding
float_rounding_head()
{
atf_set "descr" "Check for correct termination in the face of floating point rounding"
}
float_rounding_body()
{
atf_check -o inline:'1\n1.1\n1.2\n' seq 1 0.1 1.2
}
atf_init_test_cases()
{
atf_add_test_case float_rounding
}