Index: head/usr.bin/dc/bcode.c =================================================================== --- head/usr.bin/dc/bcode.c +++ head/usr.bin/dc/bcode.c @@ -1096,13 +1096,13 @@ r = new_number(); scale = max(a->scale, b->scale); - r->scale = max(b->scale, a->scale + bmachine.scale); + r->scale = scale; if (BN_is_zero(a->number)) warnx("remainder by zero"); else { normalize(a, scale); - normalize(b, scale + bmachine.scale); + normalize(b, scale); ctx = BN_CTX_new(); bn_checkp(ctx); @@ -1117,7 +1117,7 @@ static void bdivmod(void) { - struct number *a, *b, *rdiv, *rmod; + struct number *a, *b, *frac, *quotient, *rdiv, *remainder; BN_CTX *ctx; u_int scale; @@ -1131,25 +1131,44 @@ } rdiv = new_number(); - rmod = new_number(); - rdiv->scale = bmachine.scale; - rmod->scale = max(b->scale, a->scale + bmachine.scale); + quotient = new_number(); + remainder = new_number(); scale = max(a->scale, b->scale); + rdiv->scale = 0; + remainder->scale = scale; + quotient->scale = bmachine.scale; + scale = max(a->scale, b->scale); if (BN_is_zero(a->number)) warnx("divide by zero"); else { normalize(a, scale); - normalize(b, scale + bmachine.scale); + normalize(b, scale); ctx = BN_CTX_new(); bn_checkp(ctx); - bn_check(BN_div(rdiv->number, rmod->number, + /* + * Unlike other languages' divmod operations, dc is specified + * to return the remainder and the full quotient, rather than + * the remainder and the floored quotient. bn(3) has no + * function to calculate both. So we'll use BN_div to get the + * remainder and floored quotient, then calculate the full + * quotient from those. + * + * quotient = rdiv + remainder / divisor + */ + bn_check(BN_div(rdiv->number, remainder->number, b->number, a->number, ctx)); + frac = div_number(remainder, a, bmachine.scale); + normalize(rdiv, bmachine.scale); + normalize(remainder, scale); + bn_check(BN_add(quotient->number, rdiv->number, frac->number)); + free_number(frac); BN_CTX_free(ctx); } - push_number(rdiv); - push_number(rmod); + push_number(quotient); + push_number(remainder); + free_number(rdiv); free_number(a); free_number(b); } Index: head/usr.bin/dc/dc.1 =================================================================== --- head/usr.bin/dc/dc.1 +++ head/usr.bin/dc/dc.1 @@ -35,7 +35,7 @@ .\" .\" @(#)dc.1 8.1 (Berkeley) 6/6/93 .\" -.Dd February 27, 2017 +.Dd December 5, 2017 .Dt DC 1 .Os .Sh NAME @@ -139,8 +139,8 @@ the result is pushed on the stack in their place. Any fractional part of an exponent is ignored. .Pp -For addition and subtraction, the scale of the result is the maximum -of scales of the operands. +For addition, subtraction, and remainder, the scale of the result is the +maximum of scales of the operands. For division the scale of the result is defined by the scale set by the .Ic k Index: head/usr.bin/dc/tests/Makefile =================================================================== --- head/usr.bin/dc/tests/Makefile +++ head/usr.bin/dc/tests/Makefile @@ -3,5 +3,6 @@ PACKAGE= tests ATF_TESTS_SH= inout +ATF_TESTS_SH+= bcode .include Index: head/usr.bin/dc/tests/bcode.sh =================================================================== --- head/usr.bin/dc/tests/bcode.sh +++ head/usr.bin/dc/tests/bcode.sh @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# Copyright (c) 2017 Alan Somers +# 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 bmod +bmod_head() +{ + atf_set "descr" "Tests the remainder % operator" +} +bmod_body() +{ + cat > input.dc << EOF +0 3 % p # basic usage +1 3 % p +2 3 % p +3 3 % p +4 3 % p +_1 3 % p # negative dividends work like a remainder, not a modulo +1 _3 % p # negative divisors use the divisor's absolute value +1k # fractional remainders +5 3 % p +6 5 % p +5.4 3 % p +_.1 3 % p +1.1 _3 % p +1 .3 % p +EOF + dc input.dc > output.txt + cat > expect.txt << EOF +0 +1 +2 +0 +1 +-1 +1 +2 +1 +2.4 +-.1 +1.1 +.1 +EOF + atf_check cmp expect.txt output.txt +} + +atf_test_case bmod_by_zero +bmod_by_zero_head() +{ + atf_set "descr" "remaindering by zero should print a warning" +} +bmod_by_zero_body() +{ + atf_check -e match:"remainder by zero" dc -e '1 0 %' +} + +atf_test_case bdivmod +bdivmod_head() +{ + atf_set "descr" "Tests the divide and modulo ~ operator" +} +bdivmod_body() +{ + cat > input.dc << EOF +0 3 ~ n32Pp # basic usage +1 3 ~ n32Pp +2 3 ~ n32Pp +3 3 ~ n32Pp +4 3 ~ n32Pp +_1 3 ~ n32Pp # negative dividends work like a remainder, not a modulo +_4 3 ~ n32Pp # sign of quotient and divisor must agree +1 _3 ~ n32Pp # negative divisors use the divisor's absolute value +1k # fractional remainders +5 3 ~ n32Pp +6 5 ~ n32Pp +5.4 3 ~ n32Pp +_.1 3 ~ n32Pp +1.1 _3 ~ n32Pp +1 .3 ~ n32Pp +4k +.01 .003 ~ n32Pp # divmod quotient always has scale=0 +EOF + dc input.dc > output.txt + cat > expect.txt << EOF +0 0 +1 0 +2 0 +0 1 +1 1 +-1 0 +-1 -1 +1 0 +2 1.6 +1 1.2 +2.4 1.8 +-.1 0.0 +1.1 -.3 +.1 3.3 +.001 3.3333 +EOF + atf_check cmp expect.txt output.txt +} + +atf_test_case bdivmod_by_zero +bdivmod_by_zero_head() +{ + atf_set "descr" "divmodding by zero should print a warning" +} +bdivmod_by_zero_body() +{ + atf_check -e match:"divide by zero" dc -e '1 0 ~' +} + +atf_init_test_cases() +{ + atf_add_test_case bmod + atf_add_test_case bmod_by_zero + atf_add_test_case bdivmod + atf_add_test_case bdivmod_by_zero +} Index: head/usr.bin/dc/tests/inout.sh =================================================================== --- head/usr.bin/dc/tests/inout.sh +++ head/usr.bin/dc/tests/inout.sh @@ -52,8 +52,8 @@ 0.1 p # Leading zeros are ignored 00.1 p # Leading zeros are ignored EOF -dc input.dc > output.txt -cat > expect.txt << EOF + dc input.dc > output.txt + cat > expect.txt << EOF 0 16 1