1 /*******************************************************************************
2 
3     Module contains utility functions to convert floats to integer types.
4     Old conversion functions eg. ocean.math.Math.rndint or
5     ocean.math.Math.rndlong currently round x.5 to the nearest even integer. So
6     `rndint(4.5) == 4` and `rndint(5.5) == 6`. This is undesired behaviour for some
7     situations, so the functions in this module round to the nearest integer. So
8     `floatToInt(4.5, output)` sets `output == 5` and `floatToInt(5.5, output)` sets
9     output == 6 (this is round to nearest integer away from zero rounding see
10     `http://man7.org/linux/man-pages/man3/lround.3.html` for details on the
11     stdc lround, lroundf, llround, and llroundf functions).
12 
13     To check for errors the functions feclearexcept and fetestexcept are used.
14     The feclearexcept(FE_ALL_EXCEPT) method is called before calling the
15     mathematical function and after the mathematical function has been called
16     the fetestexcept method is called to check for errors that occured in the
17     mathematical function $(LPAREN)for more details in these functions see
18     http://man7.org/linux/man-pages/man7/math_error.7.html$(RPAREN).
19 
20     Copyright:
21         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
22         All rights reserved.
23 
24     License:
25         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
26         Alternatively, this file may be distributed under the terms of the Tango
27         3-Clause BSD License (see LICENSE_BSD.txt for details).
28 
29 *******************************************************************************/
30 
31 module ocean.math.Convert;
32 
33 
34 import core.stdc.fenv;
35 
36 import core.stdc.math;
37 
38 version (unittest) import ocean.core.Test;
39 
40 /*******************************************************************************
41 
42     Rounds a float, double, or real value to the nearest (away from zero) int
43     value using lroundf.
44 
45     Params:
46         input = the value to round - can be either a float, double, or real
47         output = the converted value
48 
49     Returns:
50         true if the conversion has succeeded (no errors)
51 
52 *******************************************************************************/
53 
54 public bool roundToInt ( T )( T input, out int output )
55 {
56     feclearexcept(FE_ALL_EXCEPT);
57     static if ( is(T == float) )
58     {
59         auto tmp = lroundf(input);
60     }
61     else static if ( is(T == double) )
62     {
63         auto tmp = lround(input);
64     }
65     else
66     {
67         static assert (is(T == real), "roundToInt(): Input argument expected to"
68           ~ " be of type float, double or real, not \"" ~ T.stringof ~ "\"");
69         auto tmp = lroundl(input);
70     }
71 
72     if (tmp > int.max || tmp < int.min)
73         return false;
74     output = cast(int) tmp;
75     return !fetestexcept(FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW);
76 }
77 
78 
79 /*******************************************************************************
80 
81     Rounds a float, double, or real value to the nearest (away from zero) long
82     value using llroundf.
83 
84     Params:
85         input = the value to round - can be either a float, double, or real
86         output = the converted value
87 
88     Returns:
89         true if the conversion has succeeded (no errors)
90 
91 *******************************************************************************/
92 
93 public bool roundToLong ( T )( T input, out long output )
94 {
95     feclearexcept(FE_ALL_EXCEPT);
96     static if ( is(T == float) )
97     {
98         output = llroundf(input);
99     }
100     else static if ( is(T == double) )
101     {
102         output = llround(input);
103     }
104     else
105     {
106         static assert (is(T == real), "roundToLong(): Input argument expected to"
107           ~ " be of type float, double or real, not \"" ~ T.stringof ~ "\"");
108         output = lroundl(input);
109     }
110 
111     return !fetestexcept(FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW);
112 }
113 
114 
115 /*******************************************************************************
116 
117     Method to test the conversions for the float, double, or real types.
118 
119     Params:
120         T = the type of input value to test the conversions for
121 
122 *******************************************************************************/
123 
124 private void testConversions ( T ) ( )
125 {
126     static assert ( is(T == float) || is(T == double) || is(T == real),
127         "Type " ~ T.stringof ~ " unsupported for testConversions");
128 
129     int int_result;
130     long long_result;
131 
132     // Check that converting a NaN always fails
133     test(!roundToInt(T.nan, int_result), "Error converting NaN");
134     test(!roundToLong(T.nan, long_result), "Error converting NaN");
135 
136     // Check conversion of a negative number (should fail for the unsigneds)
137     test(roundToInt(cast(T)-4.2, int_result), "Error converting " ~ T.stringof);
138     test!("==")(int_result, -4, "Incorrect " ~ T.stringof ~ " conversion");
139 
140     test(roundToLong(cast(T)-4.2, long_result), "Error converting " ~ T.stringof);
141     test!("==")(int_result, -4, "Incorrect " ~ T.stringof ~ " conversion");
142 
143     // Check conversion of x.5, should round up
144     test(roundToInt(cast(T)6.5, int_result), "Error converting " ~ T.stringof);
145     test!("==")(int_result, 7, "Incorrect " ~ T.stringof ~ " conversion");
146 
147     test(roundToLong(cast(T)6.5, long_result), "Error converting " ~ T.stringof);
148     test!("==")(long_result, 7, "Incorrect " ~ T.stringof ~ " conversion");
149 
150     // Check conversion of x.4 should round down
151     test(roundToInt(cast(T)9.49, int_result), "Error converting " ~ T.stringof);
152     test!("==")(int_result, 9, "Incorrect " ~ T.stringof ~ " conversion");
153 
154     test(roundToLong(cast(T)9.49, long_result), "Error converting " ~ T.stringof);
155     test!("==")(long_result, 9, "Incorrect " ~ T.stringof ~ " conversion");
156 }
157 
158 
159 /*******************************************************************************
160 
161     Unittest; tests the float, double, and real conversions
162 
163 *******************************************************************************/
164 
165 unittest
166 {
167     testConversions!(float)();
168     testConversions!(double)();
169     testConversions!(real)();
170 }