xref: /reactos/sdk/lib/ucrt/convert/_fptostr.cpp (revision 04e0dc4a)
1 //
2 // _fptostr.cpp
3 //
4 //      Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // Defines the internal function that writes a mantissa (in a STRFLT) into a
7 // buffer.  This function puts all of the digits into the buffer, handles
8 // rounding based on the requested precision (digits), and updates the decimal
9 // point position in the STRFLT object.  Note that this function does not change
10 // the mantissa field of the STRFLT, so callers of this function may rely on it
11 // being unmodified.
12 //
13 #include <corecrt_internal.h>
14 #include <corecrt_internal_fltintrn.h>
15 #include <corecrt_internal_ptd_propagation.h>
16 #include <fenv.h>
17 #include <string.h>
18 #include <stddef.h>
19 
20 // __acrt_has_trailing_digits::no_trailing isn't indicative of how to round if we're rounding to a point before the end of the generated digits.
21 // __acrt_has_trailing_digits::no_trailing only says if there are any digits after the mantisa we got.
22 // We need to ensure the remaining generated digits are all zero before choosing rounds-to-even behavior intended for exactly representable floating point numbers.
check_trailing(char const * mantissa_it,__acrt_has_trailing_digits const trailing_digits)23 static bool check_trailing(char const * mantissa_it, __acrt_has_trailing_digits const trailing_digits)
24 {
25     if (trailing_digits == __acrt_has_trailing_digits::trailing)
26     {
27         return true;
28     }
29 
30     while (*mantissa_it == '0')
31     {
32         mantissa_it++;
33     }
34 
35     if (*mantissa_it != '\0')
36     {
37         return true;
38     }
39 
40     return false;
41 }
42 
should_round_up(char const * const mantissa_base,char const * const mantissa_it,int const sign,__acrt_has_trailing_digits const trailing_digits,__acrt_rounding_mode const rounding_mode)43 static bool should_round_up(
44     char const *               const mantissa_base,
45     char const *               const mantissa_it,
46     int                        const sign,
47     __acrt_has_trailing_digits const trailing_digits,
48     __acrt_rounding_mode       const rounding_mode
49 )
50 {
51     if (rounding_mode == __acrt_rounding_mode::legacy)
52     {
53         return *mantissa_it >= '5';
54     }
55 
56     int const round_mode = fegetround();
57 
58     if (round_mode == FE_TONEAREST)
59     {
60         if (*mantissa_it > '5')
61         {
62             return true;
63         }
64 
65         if (*mantissa_it < '5')
66         {
67             return false;
68         }
69 
70         // If there are trailing digits we are in a scenario like this .5000000001 and should round up
71         if (check_trailing(mantissa_it + 1, trailing_digits))
72         {
73             return true;
74         }
75 
76         // At this point, the number is exactly representable and we are rounding 5.
77         // In this case, IEEE 754 states to round towards the nearest even number.
78         // Therefore: if the previous digit is odd, we round up (1.5 -> 2).
79         //            if the previous digit is even, we round down (2.5 -> 2).
80 
81         // If there is no preceding digit, it is considered zero, so round down.
82         if (mantissa_it == mantissa_base)
83         {
84             return false;
85         }
86 
87         // If the previous digit is odd, we should round up to the closest even.
88         return *(mantissa_it - 1) % 2;
89     }
90 
91     if (round_mode == FE_UPWARD)
92     {
93         return check_trailing(mantissa_it, trailing_digits) && sign != '-';
94     }
95 
96     if (round_mode == FE_DOWNWARD)
97     {
98         return check_trailing(mantissa_it, trailing_digits) && sign == '-';
99     }
100 
101     return false;
102 }
103 
__acrt_fp_strflt_to_string(char * const buffer,size_t const buffer_count,int digits,STRFLT const pflt,__acrt_has_trailing_digits const trailing_digits,__acrt_rounding_mode const rounding_mode,__crt_cached_ptd_host & ptd)104 extern "C" errno_t __cdecl __acrt_fp_strflt_to_string(
105     char*                        const buffer,
106     size_t                       const buffer_count,
107     int                                digits,
108     STRFLT                       const pflt,
109     __acrt_has_trailing_digits   const trailing_digits,
110     __acrt_rounding_mode         const rounding_mode,
111     __crt_cached_ptd_host&             ptd
112 )
113 {
114     _UCRT_VALIDATE_RETURN_ERRCODE(ptd, buffer != nullptr, EINVAL);
115     _UCRT_VALIDATE_RETURN_ERRCODE(ptd, buffer_count > 0,  EINVAL);
116     buffer[0] = '\0';
117 
118     _UCRT_VALIDATE_RETURN_ERRCODE(ptd, buffer_count > static_cast<size_t>((digits > 0 ? digits : 0) + 1), ERANGE);
119     _UCRT_VALIDATE_RETURN_ERRCODE(ptd, pflt != nullptr, EINVAL);
120 
121     char* buffer_it           = buffer;
122     char* const mantissa_base = pflt->mantissa;
123     char* mantissa_it         = pflt->mantissa;
124 
125     // The buffer will contain 'digits' decimal digits plus an optional overflow
126     // digit for the rounding.
127 
128     // Initialize the first digit in the buffer to '0' (Note: not '\0') and set
129     // the pointer to the second digit of the buffer.  The first digit is used
130     // to handle overflow on rounding (e.g. 9.999... becomes 10.000...), which
131     // requires a carry into the first digit.
132     *buffer_it++ = '0';
133 
134     // Copy the digits of the value into the buffer (with '0' padding) and
135     // insert the null terminator:
136     while (digits > 0)
137     {
138         *buffer_it++ = *mantissa_it ? *mantissa_it++ : '0';
139         --digits;
140     }
141 
142     *buffer_it = '\0';
143 
144     // Do any rounding which may be needed.  Note:  if digits < 0, we don't do
145     // any rounding because in this case, the rounding occurs in a digit which
146     // will not be output because of the precision requested.
147     if (digits >= 0 && should_round_up(mantissa_base, mantissa_it, pflt->sign, trailing_digits, rounding_mode))
148     {
149         buffer_it--;
150 
151         while (*buffer_it == '9')
152         {
153             *buffer_it-- = '0';
154         }
155 
156         *buffer_it += 1;
157     }
158 
159     if (*buffer == '1')
160     {
161         // The rounding caused overflow into the leading digit (e.g. 9.999...
162         // became 10.000...), so increment the decimal point position by 1:
163         pflt->decpt++;
164     }
165     else
166     {
167         // Move the entire string to the left one digit to remove the unused
168         // overflow digit:
169         memmove(buffer, buffer + 1, strlen(buffer + 1) + 1);
170     }
171 
172     return 0;
173 }
174