今年疫情期间应物理系的朋友要求做了一个做所谓数值和运算修约的程序。
具体包括数值修约(说得简单些就是舍入,参考GB/T 8170-1987)和运算过程的有效数字的保留(就是加减乘除过程中对数值保留位数有要求。具体看我朋友给我提供的这份文件吧:https://shimo.im/docs/PH3hvV6CygW3wK36/)
不说废话了,下面直接贴完整代码吧,Python 3.7和3.8测试过是可以直接跑的。代码使用Python语言实现,所以需要Python解释器。限于篇幅,我在这里就不说怎么安装Python了。
如果你也在找数值运算修约程序,为什么不试试呢?我试过,这个东西手算实在是太费脑筋了,还特别容易错。
如果你看不懂 class或者self是什么,那就直接划到最底下看一下示范的用法,然后copy, paste and run吧。
如果本程序使用的修约规则与你所需要的不同,你可以进行更改,毕竟很多基本的逻辑里面都已经实现好了。
等我有时间再考虑要不要把实现思路给写出来吧。
import math from decimal import * # Ver 0.4.1, Copyleft 2020.10 Peng Lingbo # The documentation is written in CHINESE, since the program is based on GB/T 8170-1987 # if you can read Chinese but the following docs doesn't make sense, try to reopen file with UTF-8 encoding. # v0.3 增加debug功能,可以用于查看追踪过程 # v0.4 支持用任何类型的数字来进行初始化,然而浮点类型本身存在精度问题,所以会收到一个警告要求你使用str()或者Decimal() # 此外,现在已经支持运算中加入非Number类对象(内置数字),不过出于上面的原因,还是 # 建议用str类型--显式的str()转换或者是直接用'单引号'括起来--或者用Decimal类型. # 由于使用者所在单位采取的运算修约标准可能与本程序所使用的有轻微不同,建议使用者先用几个测例打开debug=True检查各类运算是否符合期望 # 然后对于不符合的部分做自己的修改再使用。 # 使用本程序,即代表你已经同意程序作者对由于使用本程序所带来的一切可能后果不负任何责任。 class Number: """ 帮你自动完成数值运算修约! """ def __init__(self,value,debug=False): """ Args: 一个Decimal对象或一个数字的字符串形式,比如: a = Number('3.51E2') b = Number('-8.000') <-注意这里的引号 x = Decimal('3.14159') c = Number(x) debug: 是否查看追踪过程,默认值False """ if isinstance(value,Number): self = value return if isinstance(value,float): print(f"User Warning: Floating type for 'Number' can cause numerical problems. Use '{value}' or str({value}) instead of {value} to initialize to suppress this warning.") value = str(value) self.debug = debug assert isinstance(value,str) self.effective_digits = Number.eff(value) # 这个数字的有效数字位数 self.value = Decimal(value) # 这个数字的数值,以Decimal对象形式保存 if self.value != 0: self.highest_digit = math.floor(math.log10(abs(self.value))) # 这个数字的最高位的log10值,也就是它最高位的幂数 self.lowest_digit = self.highest_digit - self.effective_digits + 1 # 这个数字的最低位的log10值,也就是它最低位的幂数 else: #self.value = Decimal('0') # 目前对数字0,默认有效位数为1,最高最低位幂指数为0 self.highest_digit = 0 self.lowest_digit = 0 if self.debug: print(f"[{self.value}] Effectives={self.effective_digits}, Highest(log)={self.highest_digit}, Lowest(log)={self.lowest_digit}") def round(self, precision=0): """ 输入:precision(需要保留的有效数字数量),输出:舍入之后的Number对象\n ///不改变原变量 """ power = self.highest_digit - precision + 1 if self.debug: print(f" rounding to 1e{power}, effectives={precision}") res = self.value.quantize(Decimal('1e'+str(power)),ROUND_HALF_EVEN) if self.debug: print(f" result={res}") return Number(res) @staticmethod def eff(num): """@静态方法:接受一个数字num(字符串形式)并返回其有效数字数量""" assert isinstance(num,str) digits = 0 is_leading_zero = True for i in range(len(num)): if num[i]=='E' or num[i]=='e': break digit = ord(num[i]) - ord('0') if 0 <= digit <= 9: if digit > 0 or (digit == 0 and not is_leading_zero): digits += 1 # 每遇到有效数字: digit++ is_leading_zero = False # 遇到第一个有效数字后所有的0都不是leading zero了,都是有效数字 return max(digits,1) def __add__(self,rhs): if not isinstance(rhs,Number): rhs = Number(rhs) if self.value == 0: return rhs if rhs.value == 0: return self if rhs.debug: self.debug=True result = self.value + rhs.value low_digit = max(self.lowest_digit, rhs.lowest_digit) if self.debug: print(f"[{self}] + {rhs}, result_lowest_digit={low_digit}") result = result.quantize(Decimal('1e'+str(low_digit)), ROUND_HALF_EVEN) result = Number(result) if self.debug: print(f"=======>{result}") result.debug = self.debug or rhs.debug return result def __sub__(self,rhs): if not isinstance(rhs,Number): rhs = Number(rhs) if self.value == 0: rhs.value = -rhs.value return rhs if rhs.value == 0: return self if rhs.debug: self.debug=True result = self.value - rhs.value low_digit = max(self.lowest_digit, rhs.lowest_digit) if self.debug: print(f"[{self}] - {rhs}, result_lowest_digit={low_digit}") result = result.quantize(Decimal('1e'+str(low_digit)), ROUND_HALF_EVEN) result = Number(result) if self.debug: print(f"=======>{result}") result.debug = self.debug or rhs.debug return result def __str__(self): return str(self.value) def __neg__(self): tmp = self tmp.value = -tmp.value return tmp def __repr__(self): return self.value.__repr__() def __mul__(self,rhs): if not isinstance(rhs,Number): rhs = Number(rhs) if rhs.debug: self.debug=True result = self.value * rhs.value left_high = self.value // Decimal(10**self.highest_digit) right_high = rhs.value // Decimal(10**rhs.highest_digit) carry = (left_high.log10() + right_high.log10()).__floor__() if self.debug: print(f"[{self}] * {rhs}, carry={carry}") precision = min(self.effective_digits, rhs.effective_digits) + carry if self.debug: print(f" result_effectives={precision}") result = Number(result) result = result.round(precision) if self.debug: print(f"=======>{result}") result.debug=self.debug or rhs.debug return result def __truediv__(self,rhs): if not isinstance(rhs,Number): rhs = Number(rhs) assert rhs.value != 0, "Division By Zero" if rhs.debug: self.debug=True result = self.value / rhs.value precision = min(self.effective_digits, rhs.effective_digits) if self.debug: print(f"[{self}] / {rhs}, result.effectives={precision}") result = Number(result) result = result.round(precision) if self.debug: print(f"=======>{result}") result.debug=self.debug or rhs.debug return result def __floordiv__(self,rhs): if not isinstance(rhs,Number): rhs = Number(rhs) assert rhs.value != 0, "Division By Zero" if rhs.debug: self.debug=True result = self.value // rhs.value precision = min(self.effective_digits, rhs.effective_digits) if self.debug: print(f"[{self}] // {rhs},precision={precision}") result = Number(result, self.debug or rhs.debug) result = result.round(precision) if self.debug: print(f" result={result}") return result def __radd__(self, lhs): return self+lhs def __rsub__(self, lhs): return Number(lhs)-self def __rmul__(self, lhs): return self*lhs def __rtruediv__(self, lhs): return Number(lhs)/self def __rfloordiv__(self, lhs): return Number(lhs)//self def sqrt(self): """求平方根,自动完成修约""" res = self.value.sqrt() result = Number(res) return result.round(self.effective_digits) def log10(self): """求log10,自动完成修约""" res = self.value.log10() result = res.quantize(Decimal('1e-'+str(self.effective_digits)), ROUND_HALF_EVEN) return Number(result) if __name__ == "__main__": a = 4.00000 a = Number('1',debug=True) b = Number('1.44') c = Number('2.25') p1,p2,p3=Number("0.3"),Number("0.2"),Number(".5") print(a*p1+b*p2+c*p3) c = 52.9e6 print(a*b*c/1e+9+10.00000) pi = Number(math.pi) print(pi) pi = pi.round(2) print(pi)写在最后:
她说所有要做大学物理实验的学生都要做这个东西,但是我们在网上怎么找也找不到能够满足要求的代码,大部分实现只实现了数值修约部分——然而如果你细看过程序,就会发现,Python的Decimal里面的quantize已经有所需要的舍入方法了(事实上,那里面什么都有!)。由于实在是找不到,而她又确实是有这样的需求,我那时(疫情期间嘛)比较闲,就干脆帮她做了,意外地并不难,第一版也就百来行。经过几次迭代,现在最终版本也只有近200行。
说实话很奇怪,如果有这么大的需求,难道不是应该早就有这样的代码了吗?但是我们俩实在找不到这样的代码。所以虽然可能和其他人撞车,但是我(隔了几个月之后)最终还是决定把它公开,能帮到多少人是多少人。
如果帮到了你,那最好咯。