تمثل بلطف رقمًا عائمًا في بيثون
-
27-09-2019 - |
سؤال
أريد أن أمثل رقم النقطة العائمة كسلسلة مدورة إلى عدد من الأرقام المهمة ، وعدم استخدام التنسيق الأسي. في الأساس ، أريد أن أعرض أي رقم فاصلة وتأكد من أنه "يبدو لطيفًا".
هناك عدة أجزاء لهذه المشكلة:
- أحتاج إلى أن أكون قادرًا على تحديد عدد الأرقام المهمة.
- يجب أن يكون عدد الأرقام المهمة متغيرة ، والتي لا يمكن القيام بها مع مشغل تنسيق السلسلة. [تحرير] لقد تم تصحيحها ؛ يمكن لمشغل تنسيق السلسلة القيام بذلك.
- أحتاج إلى أن يتم تقريبها بالطريقة التي يتوقعها الشخص ، وليس شيئًا مثل 1.99999999999
لقد اكتشفت طريقة واحدة للقيام بذلك ، على الرغم من أنه يبدو وكأنه جولة عمل وليست مثالية تمامًا. (الحد الأقصى للدقة هو 15 رقمًا مهمًا.)
>>> def f(number, sigfig):
return ("%.15f" % (round(number, int(-1 * floor(log10(number)) + (sigfig - 1))))).rstrip("0").rstrip(".")
>>> print f(0.1, 1)
0.1
>>> print f(0.0000000000368568, 2)
0.000000000037
>>> print f(756867, 3)
757000
هل هناك طريقة أفضل للقيام بذلك؟ لماذا لا يكون للبيثون وظيفة مدمجة لهذا؟
المحلول
يبدو أنه لا توجد خدعة تنسيق سلسلة مدمجة تتيح لك (1) طباعة عوامات تظهر أول رقم مهم بعد المكان العشري الخامس عشر و (2) ليس بالترميز العلمي. بحيث يترك معالجة السلسلة اليدوية.
أدناه أنا استخدم decimal
وحدة لاستخراج الأرقام العشرية من العائمة. ال float_to_decimal
يتم استخدام الوظيفة لتحويل الطفو إلى أ Decimal
هدف. الطريقة الواضحة decimal.Decimal(str(f))
خطأ بسبب str(f)
يمكن أن تفقد أرقام كبيرة.
float_to_decimal
تم رفعه من وثائق الوحدة العشرية.
بمجرد الحصول على الأرقام العشرية على أنها مجموعة من INTs ، فإن الكود أدناه يقوم بالشيء الواضح: قم بقطع العدد المطلوب من الأرقام الرائعة ، وجولة إذا لزم الأمر ، انضم إلى الأرقام معًا في سلسلة ، وصياغة على علامة ، وضع علامة عشرية نقطة وأصفار إلى اليسار أو اليمين حسب الاقتضاء.
في الأسفل ، ستجد بعض الحالات التي اعتدت على اختبارها f
وظيفة.
import decimal
def float_to_decimal(f):
# http://docs.python.org/library/decimal.html#decimal-faq
"Convert a floating point number to a Decimal with no loss of information"
n, d = f.as_integer_ratio()
numerator, denominator = decimal.Decimal(n), decimal.Decimal(d)
ctx = decimal.Context(prec=60)
result = ctx.divide(numerator, denominator)
while ctx.flags[decimal.Inexact]:
ctx.flags[decimal.Inexact] = False
ctx.prec *= 2
result = ctx.divide(numerator, denominator)
return result
def f(number, sigfig):
# http://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
assert(sigfig>0)
try:
d=decimal.Decimal(number)
except TypeError:
d=float_to_decimal(float(number))
sign,digits,exponent=d.as_tuple()
if len(digits) < sigfig:
digits = list(digits)
digits.extend([0] * (sigfig - len(digits)))
shift=d.adjusted()
result=int(''.join(map(str,digits[:sigfig])))
# Round the result
if len(digits)>sigfig and digits[sigfig]>=5: result+=1
result=list(str(result))
# Rounding can change the length of result
# If so, adjust shift
shift+=len(result)-sigfig
# reset len of result to sigfig
result=result[:sigfig]
if shift >= sigfig-1:
# Tack more zeros on the end
result+=['0']*(shift-sigfig+1)
elif 0<=shift:
# Place the decimal point in between digits
result.insert(shift+1,'.')
else:
# Tack zeros on the front
assert(shift<0)
result=['0.']+['0']*(-shift-1)+result
if sign:
result.insert(0,'-')
return ''.join(result)
if __name__=='__main__':
tests=[
(0.1, 1, '0.1'),
(0.0000000000368568, 2,'0.000000000037'),
(0.00000000000000000000368568, 2,'0.0000000000000000000037'),
(756867, 3, '757000'),
(-756867, 3, '-757000'),
(-756867, 1, '-800000'),
(0.0999999999999,1,'0.1'),
(0.00999999999999,1,'0.01'),
(0.00999999999999,2,'0.010'),
(0.0099,2,'0.0099'),
(1.999999999999,1,'2'),
(1.999999999999,2,'2.0'),
(34500000000000000000000, 17, '34500000000000000000000'),
('34500000000000000000000', 17, '34500000000000000000000'),
(756867, 7, '756867.0'),
]
for number,sigfig,answer in tests:
try:
result=f(number,sigfig)
assert(result==answer)
print(result)
except AssertionError:
print('Error',number,sigfig,result,answer)
نصائح أخرى
إذا كنت تريد دقة النقطة العائمة ، فأنت بحاجة إلى استخدام decimal
الوحدة النمطية ، التي هي جزء من مكتبة بيثون القياسية:
>>> import decimal
>>> d = decimal.Decimal('0.0000000000368568')
>>> print '%.15f' % d
0.000000000036857
فيما يلي مقتطف يؤسس قيمة وفقًا لأشرطة الخطأ المحددة.
from math import floor, log10, round
def sigfig3(v, errplus, errmin):
i = int(floor(-log10(max(errplus,errmin)) + 2))
if i > 0:
fmt = "%%.%df" % (i)
return "{%s}^{%s}_{%s}" % (fmt % v,fmt % errplus, fmt % errmin)
else:
return "{%d}^{%d}_{%d}" % (round(v, i),round(errplus, i), numpy.round(i))
أمثلة:
5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000)
0.84312 +- 0.173124 becomes 0.84 +- 0.17
هناك حاجة إلى عوامات الدقة التعسفية للإجابة بشكل صحيح على هذا السؤال. لذلك باستخدام الوحدة النمطية العشرية انها ضرورة. لا توجد طريقة لتحويل عشري إلى سلسلة دون استخدام التنسيق الأسي (جزء من السؤال الأصلي) ، لذلك كتبت وظيفة للقيام بذلك:
def removeExponent(decimal):
digits = [str(n) for n in decimal.as_tuple().digits]
length = len(digits)
exponent = decimal.as_tuple().exponent
if length <= -1 * exponent:
zeros = -1 * exponent - length
digits[0:0] = ["0."] + ["0"] * zeros
elif 0 < -1 * exponent < length:
digits.insert(exponent, ".")
elif 0 <= exponent:
digits.extend(["0"] * exponent)
sign = []
if decimal.as_tuple().sign == 1:
sign = ["-"]
print "".join(sign + digits)
المشكلة تحاول أن تتجول إلى شخصيات مهمة. لن تدور طريقة "Quantize ()" للعشرية من النقطة العشرية ، وتؤدي وظيفة "Round ()" دائمًا إلى إرجاع تعويم. لا أعرف ما إذا كانت هذه هي الأخطاء ، ولكن هذا يعني أن الطريقة الوحيدة لتوصيل أرقام النقطة العائمة الدقيقة اللانهائية هي تحليلها كقائمة أو سلسلة والقيام بالتقريب يدويًا. بمعنى آخر ، لا توجد إجابة عاقلة على هذا السؤال.