有很多很多方法可以将浮点数值舍入到整数。C 和 C++ 在 <math.h> 或 <cmath> 中提供了一些基本的方法。
舍入算法主要有两个类别:对称于零的算法和有某种偏差的算法。
有偏舍入 偏差是统计学中用于表示样本不能完全代表其真实值的一种数学概念;它们以某种方式倾斜。
例如,<cmath> 中的 floor() 函数偏向于负无穷大,因为它总是选择较小的整数——也就是说,它总是选择更接近负无穷大的数字。
floor( 7.5 ) --> 7
floor( -7.5 ) --> -8
假设您所在的城市想知道人们在某条特定高速公路上行驶的速度。第一步是收集每个驾驶员的精确速度,第二步是将所有个体值转换为一个值来表示正常车速。为了简单起见,我们只使用平均值。
假设用于采样驾驶员速度的设备比计算机存储它的能力精确得多。(这实际上并不少见。)同样,为了简单起见,我们将说计算机将速度存储为整数。
通过对十二名驾驶员进行采样,我们得到以下速度(以英里/小时为单位)
49.087 57.901 28.500 46.738 51.270 53.096
44.795 47.218 46.347 45.989 47.582 50.563
快速计算表明,平均速度为 47.424 英里/小时。
如果该城市只是简单地使用 floor() 函数将其转换为整数,它将得到
49 57 28 46 51 53
44 47 46 45 47 50
平均值为 46.916 --> 46 英里/小时(请记住,整数运算!)
无论哪种方式,采样都相差约一英里/小时。我不认为该城市会关心一英里/小时的差异,但这确实说明了 floor() 函数的*偏差*或倾向,即使数字更接近负无穷大,从而使数据偏向一个不准确的数字。
这只是我突然想到的一个简单例子,但在许多科学和统计调查中,这种差异可能意义重大。假设阿波罗计划与月球的距离差了 1%?假设一家制药公司在日常维生素片中多放了 1% 的铁?假设一家建筑公司错误计算了桥梁的承受能力 1%?在所有这些情况下,结果都可能是致命的。百分之一是一个*很大的*比例。
对称舍入 偏差的一个特例是围绕零。让我们将 floor() 函数修改为趋向于零。
1 2 3 4 5 6 7
|
double floor0( double value )
{
if (value < 0.0)
return ceil( value );
else
return floor( value );
}
|
现在,结果的绝对值将始终相同
floor0( -7.7 ) --> -7 floor0( 7.7 ) --> 7
floor0( -7.5 ) --> -7 floor0( 7.5 ) --> 7
floor0( -7.3 ) --> -7 floor0( 7.3 ) --> 7
关于这个就到此为止。
无偏舍入 那么,我们如何处理这些偏差呢?通过添加一些考虑进去的规则。
让我们应用我们在小学学到的知识:在算术舍入中,如果下一位是 5 或更大,则向上舍入;如果小于 5,则向下舍入。我们自己写一个小函数来实现这一点。
1 2 3 4
|
double round( double value )
{
return floor( value + 0.5 );
}
|
问题是这*仍然有偏*. 我们实际上将 floor() 的偏差从负无穷大反转到了正无穷大,因为当正好在两个值中间时,我们总是选择向上舍入。
round( 10.3 ) --> 10
round( 10.5 ) --> 11
round( 10.7 ) --> 11
您可以在上面的表格中看到这种偏差:结果倾向于 11 而远离 10。
这就引出了诀窍:当正好在两个值中间时,我们应该怎么舍入?
一种非常流行的方法有多种称呼,例如“银行家舍入”、“舍入到偶数”、“收敛舍入”,甚至“无偏舍入”等等。它通过偏斜偏差本身来实现。
给定一个正好在两个值中间的数字,舍入到*偶数*值(零在此被视为偶数)。
round( 1.7 ) --> 2 round( 2.7 ) --> 3
round( 1.5 ) --> 2 round( 2.5 ) --> 2
round( 1.3 ) --> 1 round( 2.3 ) --> 2
对于随机数据,这非常方便。银行家喜欢它,因为存入和取出的资金是随机的。(当然,*存在*趋势,但您无法*准确*预测将存入和取出多少。)重要的是,银行家舍入*如果数据有偏,它仍然有偏*. 它只对随机数据是无偏的。
一种解决方案称为“交替舍入”。它通过每隔一次向上或向下偏斜来实现。
round( 1.5 ) --> 2
round( 1.5 ) --> 1
round( 1.5 ) --> 2
round( 1.5 ) --> 1
等等
但这并不总是很有用。
消除所有偏差的唯一方法是使用*随机*偏差……当然,这在普通的 PC 上是不可能生成的,但它仍然可以很好地解决问题。
如果样本正好在两个整数中间,则*随机*选择其中一个。
当然,这种方法的致命弱点是您使用的随机数生成器。C 和 C++ 的默认伪随机生成器不是很好。梅森旋转器是迄今为止最受欢迎的高质量伪随机数生成器,但它实现起来并不简单,所以我下面不包含它。
总之,下面是一个方便、简单的库,您可以随意使用。我甚至允许您随意拆解它(因为算法非常明显……)
如果我能找到绕过默认 epsilon 问题的方法,我可能会在将来更新它。欢迎提出改进建议。
:-)
rounding-algorithms.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
// rounding-algorithms.hpp
//
// General Rounding Algorithms
// Copyright (c) 2008 Michael Thomas Greer
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
//----------------------------------------------------------------------------
// Reference
// <<a href="http://www.pldesignline.com/howto/showArticle.jhtml;?articleID=175801189>">http://www.pldesignline.com/howto/showArticle.jhtml;?articleID=175801189></a>
//
//----------------------------------------------------------------------------
// In this library, symmetric functions are indicated by a zero at the end
// of the function name.
//
// If you want a different default epsilon make sure to change
//
// #define ROUNDING_EPSILON 0.001
//
// to whatever you want it to be. (I wanted to make it so that you could
// define a different default epsilon each time you #included the file, but
// I haven't figured out how to get around the template restrictions yet.)
//
#ifndef ROUNDING_ALGORITHMS_HPP
#define ROUNDING_ALGORITHMS_HPP
#ifndef ROUNDING_EPSILON
#define ROUNDING_EPSILON 0.0000001
#endif
#include <cmath>
#include <cstdlib>
#include <ciso646>
namespace rounding
{
//--------------------------------------------------------------------------
// round down
// Bias: -Infinity
using std::floor;
//--------------------------------------------------------------------------
// round up
// Bias: +Infinity
using std::ceil;
//--------------------------------------------------------------------------
// symmetric round down
// Bias: towards zero
template <typename FloatType>
FloatType floor0( const FloatType& value )
{
FloatType result = std::floor( std::fabs( value ) );
return (value < 0.0) ? -result : result;
}
//--------------------------------------------------------------------------
// A common alias for floor0()
// (notwithstanding hardware quirks)
template <typename FloatType>
inline
FloatType trunc( const FloatType& value )
{
return floor0( value );
}
//--------------------------------------------------------------------------
// symmetric round up
// Bias: away from zero
template <typename FloatType>
FloatType ceil0( const FloatType& value )
{
FloatType result = std::ceil( std::fabs( value ) );
return (value < 0.0) ? -result : result;
}
//--------------------------------------------------------------------------
// Common rounding: round half up
// Bias: +Infinity
template <typename FloatType>
FloatType roundhalfup( const FloatType& value )
{
return std::floor( value +0.5 );
}
//--------------------------------------------------------------------------
// Round half down
// Bias: -Infinity
template <typename FloatType>
FloatType roundhalfdown( const FloatType& value )
{
return std::ceil( value -0.5 );
}
//--------------------------------------------------------------------------
// symmetric round half down
// Bias: towards zero
template <typename FloatType>
FloatType roundhalfdown0( const FloatType& value )
{
FloatType result = roundhalfdown( std::fabs( value ) );
return (value < 0.0) ? -result : result;
}
//--------------------------------------------------------------------------
// symmetric round half up
// Bias: away from zero
template <typename FloatType>
FloatType roundhalfup0( const FloatType& value )
{
FloatType result = roundhalfup( std::fabs( value ) );
return (value < 0.0) ? -result : result;
}
//--------------------------------------------------------------------------
// round half even (banker's rounding)
// Bias: none
template <typename FloatType>
FloatType roundhalfeven(
const FloatType& value,
const FloatType& epsilon = ROUNDING_EPSILON
) {
if (value < 0.0) return -roundhalfeven <FloatType> ( -value, epsilon );
FloatType ipart;
std::modf( value, &ipart );
// If 'value' is exctly halfway between two integers
if ((value -(ipart +0.5)) < epsilon)
{
// If 'ipart' is even then return 'ipart'
if (std::fmod( ipart, 2.0 ) < epsilon)
return ipart;
// Else return the nearest even integer
return ceil0( ipart +0.5 );
}
// Otherwise use the usual round to closest
// (Either symmetric half-up or half-down will do0
return roundhalfup0( value );
}
//--------------------------------------------------------------------------
// round alternate
// Bias: none for sequential calls
bool _is_up = false;
template <typename FloatType>
FloatType roundalternate( const FloatType& value, int& is_up = _is_up )
{
if ((is_up != is_up))
return roundhalfup( value );
return roundhalfdown( value );
}
//--------------------------------------------------------------------------
// symmetric round alternate
// Bias: none for sequential calls
template <typename FloatType>
FloatType roundalternate0( const FloatType& value, int& is_up = _is_up )
{
if ((is_up != is_up))
return roundhalfup0( value );
return roundhalfdown0( value );
}
//--------------------------------------------------------------------------
// round random
// Bias: generator's bias
template <typename FloatType, typename RandValue, typename RandomGenerator>
FloatType roundrandom(
const FloatType& value,
const RandValue& mid,
RandomGenerator& g
) {
if (g() < mid)
return roundhalfup0( value );
return roundhalfdown0( value );
}
//--------------------------------------------------------------------------
// default round random
// Bias: rand()
template <typename FloatType>
FloatType roundrandom( const FloatType& value )
{
return roundrandom <FloatType, int, int(*)()> ( value, RAND_MAX /2, &rand );
}
}
#endif
|
如果您发现错误,请告诉我!
round half down 完全按照它应该做的。给定一个正好在两个整数中间的数字 (12.5),它会向下舍入到 12。
作为一件无关紧要的趣事,如果您将类型化参数传递给模板函数,您实际上不必提供模板参数
1 2 3 4 5
|
double num = 12.5;
cout << roundhalfdown( num ) << endl;
num = 12.500001;
cout << roundhalfdown( num ) << endl;
|
关于浮点数的一点需要记住的是,它们不是精确的。事实上,它们可能出错的地方比人们意识到的要多得多。因此,根据您的硬件和 C 库的脾气,第二个可能被视为与第一个相同,也可能不相同。IEEE 单精度应该可以很好地处理。