Pythonのround()が正しく丸められていないようです
-
09-06-2019 - |
質問
のドキュメント ラウンド() 関数は、それに数値を渡し、小数点以下の位置を四捨五入することを示しています。したがって、それは すべき これを行う:
n = 5.59
round(n, 1) # 5.6
しかし、実際には、古き良き浮動小数点の奇妙な点が忍び込み、次のような結果になります。
5.5999999999999996
UIの目的上、表示する必要があります 5.6
. 。インターネットを徘徊していくつか見つけました ドキュメンテーション これは Python の実装に依存します。残念ながら、これは私の Windows 開発マシンと私が試した各 Linux サーバーの両方で発生します。 こちらもご覧ください.
独自の丸いライブラリを作成する以外に、これを回避する方法はありますか?
解決
保存方法についてはどうすることもできませんが、少なくともフォーマットは正しく機能します。
'%.1f' % round(n, 1) # Gives you '5.6'
他のヒント
四捨五入しなくても書式設定は正しく機能します。
"%.1f" % n
Decimal モジュールを使用すると、「round」関数を使用せずに近似できます。特に通貨アプリケーションを作成する際に、四捨五入に私が使用しているものは次のとおりです。
Decimal(str(16.2)).quantize(Decimal('.01'), rounding=ROUND_UP)
これにより、16.20 という 10 進数が返されます。
round(5.59, 1)
正常に動作しています。問題は、5.6 をバイナリ浮動小数点で正確に表現できないことです。
>>> 5.6
5.5999999999999996
>>>
Vinko が言うように、文字列の書式設定を使用して表示の丸めを行うことができます。
Pythonには 10進数演算用モジュール それが必要な場合。
そうすれば「5.6」が得られます str(round(n, 1))
ただの代わりに round(n, 1)
.
データ型を整数に切り替えることができます。
>>> n = 5.59
>>> int(n * 10) / 10.0
5.5
>>> int(n * 10 + 0.5)
56
次に、ロケールの小数点区切り文字を挿入して数値を表示します。
しかし、 ジミーの答え 優れている。
浮動小数点演算は、わずかではあるが厄介な精度の不正確さに対して脆弱です。整数または固定小数点を操作できる場合は、精度が保証されます。
を見てください。 10進数モジュール
小数は、「人々を念頭に置いて設計されたフローティングポイントモデルに基づいており、必然的に最重要指導の原則があります。コンピューターは、人々が学校で学ぶ算術と同じように機能する算術を提供する必要があります。」 - 小数点以降の算術仕様からの抜粋。
そして
10進数は正確に表すことができます。対照的に、1.1や2.2などの数値には、バイナリ浮動点に正確な表現がありません。エンドユーザーは通常、バイナリフローティングポイントと同様に、1.1 + 2.2が3.3000000000000003を表示するとは予想されません。
Decimal は、浮動小数点演算を必要とするアプリを簡単に作成できる種類の演算を提供します。 また 会計など、人間が読める形式で結果を提示する必要があります。
それは本当に大きな問題です。このコードを試してください:
print "%.2f" % (round((2*4.4+3*5.6+3*4.4)/8,2),)
4.85と表示されます。次に、次のようにします。
print "Media = %.1f" % (round((2*4.4+3*5.6+3*4.4)/8,1),)
4.8と表示されます。手で計算すると、正確な答えは 4.85 になりますが、試してみると次のようになります。
print "Media = %.20f" % (round((2*4.4+3*5.6+3*4.4)/8,20),)
あなたは真実を見ることができます:浮動小数点は、分母が 2 のべき乗である分数の最も近い有限和として格納されます。
文字列フォーマット演算子を使用できます %
, 、sprintfに似ています。
mystring = "%.2f" % 5.5999
プリントフ 吸盤。
print '%.1f' % 5.59 # returns 5.6
完璧に機能します
format(5.59, '.1f') # to display
float(format(5.59, '.1f')) #to round
私がやっている:
int(round( x , 0))
この場合、最初に単位レベルで適切に丸め、次に浮動小数点数の出力を避けるために整数に変換します。
それで
>>> int(round(5.59,0))
6
この答えは文字列をフォーマットするよりもうまく機能すると思います。また、round 関数を使用する方が理にかなっています。
コード:
x1 = 5.63
x2 = 5.65
print(float('%.2f' % round(x1,1))) # gives you '5.6'
print(float('%.2f' % round(x2,1))) # gives you '5.7'
出力:
5.6
5.7
ここでラウンドの失敗が見られます。これら 2 つの数値を小数点第 1 位に四捨五入したい場合はどうすればよいでしょうか?23.45 23.55私の教育は、これらを丸くすることからあなたが得るべきだったことでした:23.4 23.6「ルール」は、前の数値が奇妙な場合は、前の数値が偶数であった場合はまとめられていないことです。Pythonのround関数は単純に5を切り捨てます。
問題は最後の桁が 5 の場合のみです。例えば。0.045 は内部的に 0.044999999999999... として保存されます。単純に最後の桁を 6 に増やして四捨五入することもできます。これにより、望ましい結果が得られます。
import re
def custom_round(num, precision=0):
# Get the type of given number
type_num = type(num)
# If the given type is not a valid number type, raise TypeError
if type_num not in [int, float, Decimal]:
raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
# If passed number is int, there is no rounding off.
if type_num == int:
return num
# Convert number to string.
str_num = str(num).lower()
# We will remove negative context from the number and add it back in the end
negative_number = False
if num < 0:
negative_number = True
str_num = str_num[1:]
# If number is in format 1e-12 or 2e+13, we have to convert it to
# to a string in standard decimal notation.
if 'e-' in str_num:
# For 1.23e-7, e_power = 7
e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
# For 1.23e-7, number = 123
number = ''.join(str_num.split('e-')[0].split('.'))
zeros = ''
# Number of zeros = e_power - 1 = 6
for i in range(e_power - 1):
zeros = zeros + '0'
# Scientific notation 1.23e-7 in regular decimal = 0.000000123
str_num = '0.' + zeros + number
if 'e+' in str_num:
# For 1.23e+7, e_power = 7
e_power = int(re.findall('e\+[0-9]+', str_num)[0][2:])
# For 1.23e+7, number_characteristic = 1
# characteristic is number left of decimal point.
number_characteristic = str_num.split('e+')[0].split('.')[0]
# For 1.23e+7, number_mantissa = 23
# mantissa is number right of decimal point.
number_mantissa = str_num.split('e+')[0].split('.')[1]
# For 1.23e+7, number = 123
number = number_characteristic + number_mantissa
zeros = ''
# Eg: for this condition = 1.23e+7
if e_power >= len(number_mantissa):
# Number of zeros = e_power - mantissa length = 5
for i in range(e_power - len(number_mantissa)):
zeros = zeros + '0'
# Scientific notation 1.23e+7 in regular decimal = 12300000.0
str_num = number + zeros + '.0'
# Eg: for this condition = 1.23e+1
if e_power < len(number_mantissa):
# In this case, we only need to shift the decimal e_power digits to the right
# So we just copy the digits from mantissa to characteristic and then remove
# them from mantissa.
for i in range(e_power):
number_characteristic = number_characteristic + number_mantissa[i]
number_mantissa = number_mantissa[i:]
# Scientific notation 1.23e+1 in regular decimal = 12.3
str_num = number_characteristic + '.' + number_mantissa
# characteristic is number left of decimal point.
characteristic_part = str_num.split('.')[0]
# mantissa is number right of decimal point.
mantissa_part = str_num.split('.')[1]
# If number is supposed to be rounded to whole number,
# check first decimal digit. If more than 5, return
# characteristic + 1 else return characteristic
if precision == 0:
if mantissa_part and int(mantissa_part[0]) >= 5:
return type_num(int(characteristic_part) + 1)
return type_num(characteristic_part)
# Get the precision of the given number.
num_precision = len(mantissa_part)
# Rounding off is done only if number precision is
# greater than requested precision
if num_precision <= precision:
return num
# Replace the last '5' with 6 so that rounding off returns desired results
if str_num[-1] == '5':
str_num = re.sub('5$', '6', str_num)
result = round(type_num(str_num), precision)
# If the number was negative, add negative context back
if negative_number:
result = result * -1
return result
どうでしょうか:
round(n,1)+epsilon