发布者:
2008年8月18日

舍入算法

评分:4.3/5 (71 票)
*****
有很多很多方法可以将浮点数值舍入到整数。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 单精度应该可以很好地处理。