有很多很多方法可以將浮點數值舍入到整數。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 單精度應該可以很好地處理。