Python 3.xの切り上げ


8

Pythonでの丸めに関する質問はすでに何度も尋ねられていることは知っていますが、答えは私を助けませんでした。浮動小数点数を半分に丸めて浮動小数点数を返すメソッドを探しています。このメソッドは、小数点以下の桁数を定義するパラメーターも受け入れる必要があります。このような丸めを実装するメソッドを作成しました。ただ、見た目は上品ではないようです。

def round_half_up(number, dec_places):
    s = str(number)

    d = decimal.Decimal(s).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

フロートを文字列に変換して(浮動小数点の不正確さを回避するため)、decimalモジュールを操作する必要があるのが気に入らない。より良い解決策はありますか?

編集:以下の回答で指摘されているように、正しい丸めはそもそも数値の正しい表現を必要とするため、私の問題の解決策はそれほど明白ではありませんが、これは浮動小数点の場合とは異なります。だから私は次のコードが

def round_half_up(number, dec_places):

    d = decimal.Decimal(number).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

(上記のちょうどフロート番号を直接進数にしていない文字列に変換されるという事実により、コードから異なりが最初という)は、このように使用する場合2.18を返すために:round_half_up(2.175, 2)しかし、それはしませんので、Decimal(2.175)戻りますDecimal('2.17499999999999982236431605997495353221893310546875')フロート、道を番号はコンピュータによって表されます。驚いたことに、浮動小数点数が最初に文字列に変換されるため、最初のコードは2.18を返します。str()関数は、最初に丸めることを意図していた数に暗黙の丸めを行っているようです。したがって、2つの丸めが行われています。これは私が期待する結果ですが、技術的に間違っています。


@IcesHay数を四捨五入することで、あなたが何を意味するのかわかりません。いくつか例を挙げていただけますか?
ケニビア

@ケニビアつまり、関連する小数点以下の桁数を0〜4に切り下げ、5〜9に切り上げます。小数点以下の桁数を0に丸める場合:2.4 = 2; 2.5 = 3; 3.5 = 4など
IcesHay

回答:


7

丸めを行うのは意外に難しいです右のあなたは非常に慎重に浮動小数点演算を処理する必要があるため、。エレガントをお探しならソリューション(短く、理解しやすい)、良い出発点のようなものです。正確にdecimal.Decimal(str(number))は、数値自体から小数を作成することで置き換える必要があります。これにより、正確な表現の小数バージョンが得られます。

d = Decimal(number).quantize(...)...

Decimal(str(number)) 効果的に丸める floatを文字列表現にフォーマットすると独自の丸めが実行されるため、は 2回丸めます。これはstr(float value)、フロートの完全な10進数表現を印刷しようとしないためfloatです。コンストラクタに正確な桁を渡した場合に、同じフロートを確実に戻すために十分な桁のみを印刷します。

正しい丸めを保持したいが、大きくて複雑なdecimalモジュールに依存することを避けたい場合は、確かにそれを行うことができますが、正しい丸めに必要な正確な演算を実装するためのいくつかの方法が必要です。たとえば、分数

import fractions, math

def round_half_up(number, dec_places=0):
    sign = math.copysign(1, number)
    number_exact = abs(fractions.Fraction(number))
    shifted = number_exact * 10**dec_places
    shifted_trunc = int(shifted)
    if shifted - shifted_trunc >= fractions.Fraction(1, 2):
        result = (shifted_trunc + 1) / 10**dec_places
    else:
        result = shifted_trunc / 10**dec_places
    return sign * float(result)

assert round_half_up(1.49) == 1
assert round_half_up(1.5) == 2
assert round_half_up(1.51) == 2
assert round_half_up(2.49) == 2
assert round_half_up(2.5) == 3
assert round_half_up(2.51) == 3

上記のコードの唯一のトリッキーな部分は、浮動小数点から分数への正確な変換であり、10 as_integer_ratio()進数と分数の両方が内部で行うfloatメソッドにオフロードできることに注意してください。したがって、本当に依存関係を削除したい場合fractions場合は、小数演算を純粋な整数演算に減らすことができます。読みやすさを犠牲にして、同じ行数内にとどまります。

def round_half_up(number, dec_places=0):
    sign = math.copysign(1, number)
    exact = abs(number).as_integer_ratio()
    shifted = (exact[0] * 10**dec_places), exact[1]
    shifted_trunc = shifted[0] // shifted[1]
    difference = (shifted[0] - shifted_trunc * shifted[1]), shifted[1]
    if difference[0] * 2 >= difference[1]:  # difference >= 1/2
        shifted_trunc += 1
    return sign * (shifted_trunc / 10**dec_places)

これらの関数をテストすると、浮動小数点数を作成するときに実行される近似に注目が集まることに注意してください。たとえば、10進数は2進数で正確に表現できないため、print(round_half_up(2.175, 2))印刷さ2.17れます。そのため、10進数2.1752.175よりわずかに小さい近似値に置き換えられます。関数はその値を受け取り、小数2.175に対応する実際の小数よりも小さいことを見つけ、切り捨てることを決定ます。これは実装の癖ではありません。この動作は、浮動小数点数のプロパティから派生roundし、Python 3および2の組み込みにも存在します。


問題d = Decimal(number).quantize(...)は、浮動小数点数の正確な表現がないことです。したがって、Decimal(2.175)Decimal( '2.175')である間、Decimal( '2.17499999 ...')が得られます(したがって、丸めは正しく行われませんDecimal('2.175'))。それが最初に文字列に変換する理由です。
IcesHay

@IcesHay分母が2の累乗ではないため、0.1のような数値(つまり、1/10の小数)がバイナリで正確に表現されていないことは事実ですが、実際のP​​ython浮動小数点数を取得すると、近似はすでに行われています、そしてあなたがメモリに持っている数は正確な表現を持ってます、それはによって提供されますas_integer_ratio()(0.1の場合、それは3602879701896397/36028797018963968になります)。decimal.Decimal(float)コンストラクタは、その表現を使用し、その後の丸めが正確であると正確に行われます。
user4815162342

よろしいですか?関数をテストしてround_half_up(2.175, 2)2.17を返しましたが、これは正しくありません。多分私が間違っている何かがあるのでしょうか?
IcesHay

@IcesHayまさにこれです。2.175は、浮動小数点数として読み取られると、2.175よりわずかに小さい分数2448832297382707/1125899906842624として正確に表現できる数に近似されます。(これは'%.20f' % 2.175で評価されるでテストでき'2.17499999999999982236'ます。)たとえば、ハーフアップ丸めを使用するPython 2.7では、round(2.175)は2.17を返します。この種の結果は、浮動小数点の制限として文書化されています。
user4815162342

1
@IcesHayそれは浮動小数点の動作方法ではありません。文字列が浮動小数点に読み込まれると、元の数値(「2.175」を「2.174999999999 ...」から区別します)が回復不能に失われます。あなたの質問は、この回答が提供する数値の四捨五入の実装に関するものです。あなたの実際の問題は完全に別の問題のようです。なぜなら、今はround(丸めを実装する)Python 2でも十分ではないことが判明しているためです。質問を編集して、実際の使用例を指定してください-おそらく、浮動小数点数を避けて小数を使用することで十分でしょう。
user4815162342

1

(フロートポイントの不正確さを回避するために)フロートを文字列に変換してから、decimalモジュールを使用する必要があるのが好きではありません。より良い解決策はありますか?

はい; Decimal2.675などの数値を正確に表し、2.67ではなく2.68に丸める必要がある場合は、プログラム全体で数値を表すために使用します。

他に方法はありません。画面に2.675と表示される浮動小数点数、実際の数2.675ではありません。実際、これは2.675よりもわずかに小さいため、2.67に切り捨てられます。

>>> 2.675 - 2
0.6749999999999998

それは'2.675'たまたまそのような最短の文字列であるため、文字列形式でのみ表示されますfloat(s) == 2.6749999999999998。この長い表現(9が多い)も正確ではないことに注意してください。

あなたは丸め関数を記述しかし、それは可能ではありませんmy_round(2.675, 2)ラウンドまでに2.68もためにmy_round(2 + 0.6749999999999998, 2)にラウンドダウンします2.67。入力は実際には同じ浮動小数点数であるためです。

そのため、数値2.675が浮動小数点数に変換されて再び元に戻る場合、四捨五入する必要があるかどうかの情報はすでに失われています。解決策は、そもそもフロートを浮かせないことです。


-2

エレガントな1行の関数を作成するために非常に長い時間を費やした後、私は辞書のサイズに匹敵するものを得ました。

これを行う最も簡単な方法は、

def round_half_up(inp,dec_places):
    return round(inp+0.0000001,dec_places)

これはすべてのケースで正確ではないことを認めますが、単純な迅速な回避策が必要な場合は機能するはずです。


あなたの努力に感謝。あなたのソリューションはシンプルかもしれませんが、それは簡単な方法であり、あまり実用的ではありません。もう一度明確にするために、私は解決策を探しています。それは、浮動小数点値を小数点以下の桁数に正しく「半分」に丸めることです。他の誰かが同様の問題を抱えていて、エレガントで効果的な解決策があるかどうか知りたいです。
IcesHay

2
このソリューションは正しくありません。たとえば、round_half_up(1.499999999, 0)1.0ではなく2.0を返します。
user4815162342

@ user4815162342通常は1.5に切り捨てられないため、opが最初にこの質問をしたのはそのためです
ケニビア

@ user4815162342しかし、そうです、それらのケースでは正確ではありません。しかし、1.5を切り捨てるだけの場合は、これはすばやい回避策だと思います。ところであなたのパートからの素晴らしい答え:)
ケニビア

1
ありがとう。昨日私に聞いたとしたら、1.499999999を2.0に丸めることは、一般的に、特にOPの両方で正しくないと言っていたでしょう。しかし、私の回答へのコメントでOPとの最新の議論の後で、私はもはや確信が持てず、質問は現在の形では答えられないと考え始めています。その観点から、私は自分の反対票を取り消しています。
user4815162342
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.