xref: /php-src/ext/bcmath/libbcmath/src/output.c (revision e56ed6e1)
1 /* output.c: bcmath library file. */
2 /*
3     Copyright (C) 1991, 1992, 1993, 1994, 1997 Free Software Foundation, Inc.
4     Copyright (C) 2000 Philip A. Nelson
5 
6     This library is free software; you can redistribute it and/or
7     modify it under the terms of the GNU Lesser General Public
8     License as published by the Free Software Foundation; either
9     version 2 of the License, or (at your option) any later version.
10 
11     This library is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14     Lesser General Public License for more details.  (LICENSE)
15 
16     You should have received a copy of the GNU Lesser General Public
17     License along with this library; if not, write to:
18 
19       The Free Software Foundation, Inc.
20       59 Temple Place, Suite 330
21       Boston, MA 02111-1307 USA.
22 
23     You may contact the author by:
24        e-mail:  philnelson@acm.org
25       us-mail:  Philip A. Nelson
26                 Computer Science Department, 9062
27                 Western Washington University
28                 Bellingham, WA 98226-9062
29 
30 *************************************************************************/
31 
32 #include "bcmath.h"
33 #include <stdbool.h>
34 #include <string.h>
35 #include "zend_alloc.h"
36 
37 
38 /* The following routines provide output for bcd numbers package
39    using the rules of POSIX bc for output. */
40 
41 /* This structure is used for saving digits in the conversion process. */
42 typedef struct stk_rec {
43 	long digit;
44 	struct stk_rec *next;
45 } stk_rec;
46 
47 /* The reference string for digits. */
48 static const char ref_str[] = "0123456789ABCDEF";
49 
50 
51 /* A special output routine for "multi-character digits."  Exactly
52    SIZE characters must be output for the value VAL.  If SPACE is
53    non-zero, we must output one space before the number.  OUT_CHAR
54    is the actual routine for writing the characters. */
55 
bc_out_long(long val,size_t size,bool space,void (* out_char)(char))56 void bc_out_long(long val, size_t size, bool space, void (*out_char)(char))
57 {
58 	char digits[40];
59 	size_t len, ix;
60 
61 	if (space) (*out_char)(' ');
62 	snprintf(digits, sizeof(digits), "%ld", val);
63 	len = strlen(digits);
64 	while (size > len) {
65 		(*out_char)('0');
66 		size--;
67 	}
68 	for (ix = 0; ix < len; ix++) {
69 		(*out_char)(digits[ix]);
70 	}
71 }
72 
73 /* Output of a bcd number.  NUM is written in base O_BASE using OUT_CHAR
74    as the routine to do the actual output of the characters. */
75 
bc_out_num(bc_num num,int o_base,void (* out_char)(char),bool leading_zero)76 void bc_out_num(bc_num num, int o_base, void (*out_char)(char), bool leading_zero)
77 {
78 	char *nptr;
79 	int index, fdigit;
80 	bool pre_space;
81 	stk_rec *digits, *temp;
82 	bc_num int_part, frac_part, base, cur_dig, t_num, max_o_digit;
83 
84 	/* The negative sign if needed. */
85 	if (num->n_sign == MINUS) (*out_char)('-');
86 
87 	/* Output the number. */
88 	if (bc_is_zero(num)) {
89 		(*out_char)('0');
90 	} else {
91 		if (o_base == 10) {
92 			/* The number is in base 10, do it the fast way. */
93 			nptr = num->n_value;
94 			if (num->n_len > 1 || *nptr != 0) {
95 				for (index = num->n_len; index > 0; index--) {
96 					(*out_char)(BCD_CHAR(*nptr++));
97 				}
98 			} else {
99 				nptr++;
100 			}
101 
102 			if (leading_zero && bc_is_zero(num)) {
103 				(*out_char)('0');
104 			}
105 
106 			/* Now the fraction. */
107 			if (num->n_scale > 0) {
108 				(*out_char)('.');
109 				for (index = 0; index < num->n_scale; index++) {
110 					(*out_char)(BCD_CHAR(*nptr++));
111 				}
112 			}
113 		} else {
114 			/* special case ... */
115 			if (leading_zero && bc_is_zero(num)) {
116 				(*out_char)('0');
117 			}
118 
119 			/* The number is some other base. */
120 			digits = NULL;
121 			bc_init_num(&int_part);
122 			bc_divide(num, BCG(_one_), &int_part, 0);
123 			bc_init_num(&frac_part);
124 			bc_init_num(&cur_dig);
125 			bc_init_num(&base);
126 			bc_sub(num, int_part, &frac_part, 0);
127 			/* Make the INT_PART and FRAC_PART positive. */
128 			int_part->n_sign = PLUS;
129 			frac_part->n_sign = PLUS;
130 			bc_int2num(&base, o_base);
131 			bc_init_num(&max_o_digit);
132 			bc_int2num(&max_o_digit, o_base - 1);
133 
134 			/* Get the digits of the integer part and push them on a stack. */
135 			while (!bc_is_zero(int_part)) {
136 				bc_modulo(int_part, base, &cur_dig, 0);
137 				/* PHP Change: malloc() -> emalloc() */
138 				temp = (stk_rec *) emalloc(sizeof(stk_rec));
139 				temp->digit = bc_num2long(cur_dig);
140 				temp->next = digits;
141 				digits = temp;
142 				bc_divide(int_part, base, &int_part, 0);
143 			}
144 
145 			/* Print the digits on the stack. */
146 			if (digits != NULL) {
147 				/* Output the digits. */
148 				while (digits != NULL) {
149 					temp = digits;
150 					digits = digits->next;
151 					if (o_base <= 16) {
152 						(*out_char)(ref_str[(int) temp->digit]);
153 					} else {
154 						bc_out_long(temp->digit, max_o_digit->n_len, 1, out_char);
155 					}
156 					efree(temp);
157 				}
158 			}
159 
160 			/* Get and print the digits of the fraction part. */
161 			if (num->n_scale > 0) {
162 				(*out_char)('.');
163 				pre_space = false;
164 				t_num = bc_copy_num(BCG(_one_));
165 				while (t_num->n_len <= num->n_scale) {
166 					bc_multiply(frac_part, base, &frac_part, num->n_scale);
167 					fdigit = bc_num2long(frac_part);
168 					bc_int2num(&int_part, fdigit);
169 					bc_sub(frac_part, int_part, &frac_part, 0);
170 					if (o_base <= 16) {
171 						(*out_char)(ref_str[fdigit]);
172 					} else {
173 						bc_out_long(fdigit, max_o_digit->n_len, pre_space, out_char);
174 						pre_space = true;
175 					}
176 					bc_multiply(t_num, base, &t_num, 0);
177 				}
178 				bc_free_num (&t_num);
179 			}
180 
181 			/* Clean up. */
182 			bc_free_num (&int_part);
183 			bc_free_num (&frac_part);
184 			bc_free_num (&base);
185 			bc_free_num (&cur_dig);
186 			bc_free_num (&max_o_digit);
187 		}
188 	}
189 }
190