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 }