1 /*
2  * ratio.c
3  * Exact rational numbers.
4  *
5  * Copyright (C) 2003-2017 Cosmin Truta.
6  *
7  * This software is distributed under the zlib license.
8  * Please see the accompanying LICENSE file.
9  */
10 
11 #include "ratio.h"
12 
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 
19 #ifdef OPNG_LLONG_T_DEFINED
20 typedef opng_llong_t opng_xlong_impl_t;
21 typedef opng_ullong_t opng_uxlong_impl_t;
22 #define OPNG_XLONG_IMPL_FORMAT_PREFIX OPNG_LLONG_FORMAT_PREFIX
23 #else
24 typedef long opng_xlong_impl_t;
25 typedef unsigned long opng_uxlong_impl_t;
26 #define OPNG_XLONG_IMPL_FORMAT_PREFIX "l"
27 #endif
28 
29 
30 /*
31  * Writes formatted output to a memory buffer.
32  * This is a wrapper to [v]snprintf which avoids well-known defects
33  * occurring in some of the underlying snprintf implementations.
34  * The function returns the number of characters written, excluding the
35  * null-termination character, if the buffer size is large enough, or -1
36  * otherwise. (Unlike the proper snprintf, this function does not return
37  * a number larger than zero if the buffer size is too small.)
38  */
39 static int
opng_snprintf_impl(char * buffer,size_t buffer_size,const char * format,...)40 opng_snprintf_impl(char *buffer, size_t buffer_size, const char *format, ...)
41 {
42 
43 #if defined _WIN32 || defined __WIN32__ || defined _WIN64 || defined __WIN64__
44 #define OPNG_VSNPRINTF _vsnprintf
45 #else
46 #define OPNG_VSNPRINTF vsnprintf
47 #endif
48 
49     va_list arg_ptr;
50     int result;
51 
52     va_start(arg_ptr, format);
53     result = OPNG_VSNPRINTF(buffer, buffer_size, format, arg_ptr);
54     va_end(arg_ptr);
55 
56     if (result < 0 || (size_t)result >= buffer_size)
57     {
58         /* Guard against broken [v]snprintf implementations. */
59         if (buffer_size > 0)
60             buffer[buffer_size - 1] = '\0';
61         return -1;
62     }
63     return result;
64 
65 #undef OPNG_VSNPRINTF
66 
67 }
68 
69 /*
70  * Writes a decomposed rational value to a memory buffer.
71  * This is the base implementation used internally by the the other
72  * ratio-to-string conversion functions.
73  */
74 static int
opng_sprint_uratio_impl(char * buffer,size_t buffer_size,opng_uxlong_impl_t num,opng_uxlong_impl_t denom,int always_percent)75 opng_sprint_uratio_impl(char *buffer, size_t buffer_size,
76                         opng_uxlong_impl_t num, opng_uxlong_impl_t denom,
77                         int always_percent)
78 {
79     /* (1) num/denom == 0/0                 ==> print "??%"
80      * (2) num/denom == INFINITY            ==> print "INFINITY%"
81      * (3) 0 <= num/denom < 99.995%         ==> use the percent format "99.99%"
82      *     if always_percent:
83      * (4)    0.995 <= num/denom < INFINITY ==> use the percent format "999%"
84      *     else:
85      * (5)    0.995 <= num/denom < 99.995   ==> use the factor format "9.99x"
86      * (6)    99.5 <= num/denom < INFINITY  ==> use the factor format "999x"
87      *     end if
88      */
89 
90     opng_uxlong_impl_t integer_part, remainder;
91     unsigned int fractional_part, scale;
92     double scaled_ratio;
93 
94     /* (1,2): num/denom == 0/0 or num/denom == INFINITY */
95     if (denom == 0)
96         return opng_snprintf_impl(buffer, buffer_size,
97                                   num == 0 ? "??%%" : "INFINITY%%");
98 
99     /* (3): 0 <= num/denom < 99.995% */
100     /* num/denom < 99.995% <==> denom/(denom-num) < 20000 */
101     if (num < denom && denom / (denom - num) < 20000)
102     {
103         scale = 10000;
104         scaled_ratio = ((double)num * (double)scale) / (double)denom;
105         fractional_part = (unsigned int)(scaled_ratio + 0.5);
106         /* Adjust the scaled result in the event of a roundoff error. */
107         /* Such error may occur only if the numerator is extremely large. */
108         if (fractional_part >= scale)
109             fractional_part = scale - 1;
110         return opng_snprintf_impl(buffer, buffer_size,
111                                   "%u.%02u%%",
112                                   fractional_part / 100,
113                                   fractional_part % 100);
114     }
115 
116     /* Extract the integer part out of the fraction for the remaining cases. */
117     integer_part = num / denom;
118     remainder = num % denom;
119     scale = 100;
120     scaled_ratio = ((double)remainder * (double)scale) / (double)denom;
121     fractional_part = (unsigned int)(scaled_ratio + 0.5);
122     if (fractional_part >= scale)
123     {
124         fractional_part = 0;
125         ++integer_part;
126     }
127 
128     /* (4): 0.995 <= num/denom < INFINITY */
129     if (always_percent)
130         return opng_snprintf_impl(buffer, buffer_size,
131                                   "%" OPNG_XLONG_IMPL_FORMAT_PREFIX "u%02u%%",
132                                   integer_part, fractional_part);
133 
134     /* (5): 0.995 <= num/denom < 99.995 */
135     if (integer_part < 100)
136         return opng_snprintf_impl(buffer, buffer_size,
137                                   "%" OPNG_XLONG_IMPL_FORMAT_PREFIX "u.%02ux",
138                                   integer_part, fractional_part);
139 
140     /* (6): 99.5 <= num/denom < INFINITY */
141     /* Round to the nearest integer. */
142     /* Recalculate the integer part, for corner cases like 123.999. */
143     integer_part = num / denom;
144     if (remainder > (denom - 1) / 2)
145         ++integer_part;
146     return opng_snprintf_impl(buffer, buffer_size,
147                               "%" OPNG_XLONG_IMPL_FORMAT_PREFIX "ux",
148                               integer_part);
149 }
150 
151 /*
152  * Converts a rational value to a compact factor string representation.
153  */
154 int
opng_ulratio_to_factor_string(char * buffer,size_t buffer_size,const struct opng_ulratio * ratio)155 opng_ulratio_to_factor_string(char *buffer, size_t buffer_size,
156                               const struct opng_ulratio *ratio)
157 {
158     opng_uxlong_impl_t num = ratio->num;
159     opng_uxlong_impl_t denom = ratio->denom;
160     return opng_sprint_uratio_impl(buffer, buffer_size, num, denom, 0);
161 }
162 
163 /*
164  * Converts a rational value to a compact percent string representation.
165  */
166 int
opng_ulratio_to_percent_string(char * buffer,size_t buffer_size,const struct opng_ulratio * ratio)167 opng_ulratio_to_percent_string(char *buffer, size_t buffer_size,
168                                const struct opng_ulratio *ratio)
169 {
170     opng_uxlong_impl_t num = ratio->num;
171     opng_uxlong_impl_t denom = ratio->denom;
172     return opng_sprint_uratio_impl(buffer, buffer_size, num, denom, 1);
173 }
174 
175 #ifdef OPNG_LLONG_T_DEFINED
176 
177 /*
178  * Converts a rational value to a compact factor string representation.
179  */
180 int
opng_ullratio_to_factor_string(char * buffer,size_t buffer_size,const struct opng_ullratio * ratio)181 opng_ullratio_to_factor_string(char *buffer, size_t buffer_size,
182                                const struct opng_ullratio *ratio)
183 {
184     opng_uxlong_impl_t num = ratio->num;
185     opng_uxlong_impl_t denom = ratio->denom;
186     return opng_sprint_uratio_impl(buffer, buffer_size, num, denom, 0);
187 }
188 
189 /*
190  * Converts a rational value to a compact percent string representation.
191  */
192 int
opng_ullratio_to_percent_string(char * buffer,size_t buffer_size,const struct opng_ullratio * ratio)193 opng_ullratio_to_percent_string(char *buffer, size_t buffer_size,
194                                 const struct opng_ullratio *ratio)
195 {
196     opng_uxlong_impl_t num = ratio->num;
197     opng_uxlong_impl_t denom = ratio->denom;
198     return opng_sprint_uratio_impl(buffer, buffer_size, num, denom, 1);
199 }
200 
201 #endif  /* OPNG_LLONG_T_DEFINED */
202