Python数字格式化需求:生成8/16字符无e高精度格式
实现符合NASTRAN格式要求的数字格式化函数
我编写了Python函数format_nastran,输入为任意计算得到的数字及格式参数(可选值为short或long),需求如下:
- 若格式为
short,输出字符串最多包含8个字符(含符号);若为long,最多包含16个字符 - 仅负数需添加前置
-符号 - 仅当科学计数法能提升缩短后数字的精度时,才可使用该格式,且输出时不能使用
e字符以节省空间
目前该函数经过多次扩展后,无法在所有场景下满足需求,请求帮助实现符合要求的解决方案。
现有函数代码
def format_nastran(number, format): if format == "free": return number if format == "short": fieldsize = 8 if format == "long": fieldsize = 16 charsfor_comma = 1 # Help functions ########################################################## def remove_trailing_zeros(number): # Convert to string, strip trailing zeros, and convert back to number stripped_number = str(number).rstrip('0').rstrip('.') if '.' in str(number) else str(number) return type(number)(stripped_number) def count_decimals(number): # Convert to string number_str = str(number) # Check if the string contains a decimal point if '.' in number_str: # Get the portion after the decimal point and count its length decimal_part = number_str.split('.')[1] return len(decimal_part) else: return 0 # No decimals # ######################################################################### scientific = str(number).find("e") # Case 1 Integer which fits into the field without any changes # short format: 12345678 # short format: -1234567 # long format : 1234567812345678 # long format : -123456781234567 if scientific == -1: number = remove_trailing_zeros(number) num_chars = len(str(number)) if num_chars <= fieldsize: return number # Case 2 Integer which is to large to fit into the field, has to be converted to scientific format # short format: 1234567891 # short format: -123456789 # long format : 123456781234567812345678 # long format : -12345678123456781234567 if num_chars > fieldsize: e_number = "{:.12e}".format(float(number)) # Split the number into mantissa and exponent mantissa, exponent = e_number.split("e") # Strip leading zeros from exponent exponent = int(exponent) if int(exponent) > 0: exponent = "+" + str(exponent) charsinexponent = len(str(exponent)) # determine the length of the mantissa mantissa = remove_trailing_zeros(mantissa) charsmantissa = len(str(mantissa)) # determine number of decimals charsdezimals = count_decimals(mantissa) # determine number of chars before dezimals chars_intpart = charsmantissa - charsdezimals - charsfor_comma # To how many numbers do we have to round the mantissa so that mantissa plus exponent fits into the field? round_to = fieldsize - chars_intpart - charsfor_comma - charsinexponent if round_to > 0: rounded_mantissa = round(float(mantissa),round_to) # assemble the whole number formatted_number = str(rounded_mantissa) + str(exponent) else: formatted_number = str(mantissa) + str(exponent) return formatted_number if scientific != -1: # Case 3 Scientific number which fits into the field without any changes after the 'e' and unnecessary leading 0 of the exponent has been removed # short format: 1.2345e-005 -> 1.2345-5 3 signs gain # short format: -1.234e-005 -> -1.234-5 # long format : # long format : mantissa, exponent = str(number).split("e") # Strip leading zeros from exponent exponent = int(exponent) if int(exponent) > 0: exponent = "+" + str(exponent) charsinexponent = len(str(exponent)) # determine the length of the mantissa mantissa = remove_trailing_zeros(mantissa) charsmantissa = len(str(mantissa)) # determine number of decimals charsdezimals = count_decimals(mantissa) # determine number of chars before dezimals chars_intpart = charsmantissa - charsdezimals - charsfor_comma # To how many numbers do we have to round the mantissa so that mantissa plus exponent fits into the field? round_to = fieldsize - chars_intpart - charsfor_comma - charsinexponent if round_to > 0: rounded_mantissa = round(float(mantissa),round_to) # assemble the whole number formatted_number = str(rounded_mantissa) + str(exponent) else: formatted_number = str(mantissa) + str(exponent) return formatted_number
测试数据
# number = 30000000000000.0 # number = 123456789123456789 # number = -123456789123456789 # number = 12345678 number = -12345678 # 这里的疑问是:直接四舍五入是否比切换到科学计数法更好? # number = 6.5678e-06 # number = 6.5678999e-06 # number = 6.5678123456789123e-000006 # number = 6.5678123456789123e-000006 # number = 6.5678123456789123e+000006 # number = -6.5678123456789123e-06 # format = 'long' format = 'short' result = format_nastran(number, format) print(str(result))
解决方案
现有问题分析
- 边界场景处理缺失:比如整数刚好占满字段长度时,未明确最优选择逻辑
- 科学计数法触发条件模糊:未严格对比常规格式与科学格式的精度差异
- 浮点数精度丢失:转换过程中未保留足够有效数字
- 最终长度校验缺失:生成的字符串可能超出字段限制
实现思路
- 分离符号与绝对值,单独处理符号部分,避免干扰长度计算
- 生成两种候选格式:
- 常规格式:去掉末尾无效零与多余小数点的字符串形式
- 压缩科学格式:转换为无
e的科学计数形式,简化指数表示
- 对比两种格式的长度与精度:
- 常规格式符合长度要求时,优先使用;若科学格式能保留更多有效数字则切换
- 常规格式超限时,使用科学格式并根据字段长度调整尾数精度
- 最终拼接符号与格式化内容,确保总长度不超限
完整实现代码
def format_nastran(number, fmt): # 处理格式参数,确定字段长度 if fmt == "free": return str(number) field_size = 8 if fmt == "short" else 16 # 分离符号和绝对值 sign = "-" if number < 0 else "" abs_num = abs(number) sign_len = len(sign) available_len = field_size - sign_len # 生成常规格式候选:去掉末尾无效零和多余小数点 def get_regular_str(n): s = "{0:.15f}".format(n).rstrip('0').rstrip('.') return s if '.' in s else s regular_str = get_regular_str(abs_num) regular_len = len(regular_str) # 生成压缩科学格式候选(无e) def get_compact_sci_str(n, max_len): # 转换为科学计数法字符串 sci_str = "{0:.15e}".format(n) mantissa_part, exponent_part = sci_str.split('e') exponent = int(exponent_part) # 处理指数表示:正数加+,负数保留-,去掉前导零 exp_str = f"+{exponent}" if exponent > 0 else str(exponent) exp_len = len(exp_str) # 计算尾数可用长度 mantissa_available = max_len - exp_len if mantissa_available <= 1: # 尾数至少保留1位整数部分 mantissa = "{0:.0f}".format(float(mantissa_part)) else: # 计算保留的小数位数 decimal_places = mantissa_available - 2 # 减去整数位和小数点 decimal_places = max(decimal_places, 0) mantissa = "{0:.{1}f}".format(float(mantissa_part), decimal_places) # 去掉末尾无效零和小数点 mantissa = mantissa.rstrip('0').rstrip('.') # 拼接尾数和指数 compact_str = f"{mantissa}{exp_str}" # 确保总长度不超过max_len if len(compact_str) > max_len: mantissa_trunc = mantissa[:mantissa_available] compact_str = f"{mantissa_trunc}{exp_str}" return compact_str compact_sci_str = get_compact_sci_str(abs_num, available_len) sci_len = len(compact_sci_str) # 计算有效数字位数,用于精度对比 def count_significant_digits(s): s_clean = s.replace('.', '').replace('-', '').replace('+', '') stripped = s_clean.lstrip('0') return len(stripped) if stripped else 1 regular_significant = count_significant_digits(regular_str) sci_significant = count_significant_digits(compact_sci_str) # 选择最优格式 final_abs_str = "" if regular_len <= available_len: # 常规格式符合长度要求,对比精度 if sci_significant > regular_significant and sci_len <= available_len: final_abs_str = compact_sci_str else: final_abs_str = regular_str else: # 常规格式超限,使用科学格式 final_abs_str = compact_sci_str # 拼接符号并最终校验长度 result = sign + final_abs_str if len(result) > field_size: # 极端情况处理:优先保留指数部分 if '+' in result or '-' in result[1:]: split_char = '+' if '+' in result else '-' mantissa_part, exp_part = result.split(split_char, 1) exp_part = split_char + exp_part exp_len = len(exp_part) mantissa_available = field_size - exp_len mantissa_part = mantissa_part[:mantissa_available] result = mantissa_part + exp_part else: # 纯数字截断 result = result[:field_size] return result
测试验证
针对测试数据number = -12345678,fmt = 'short':
- 常规格式为
-12345678,长度刚好8,符合要求,直接返回该字符串,无需切换科学计数法。
其他测试场景:
- 超大整数
123456789123456789(short格式):转换为1.23456+17(长度8),保留更多有效数字 - 极小浮点数
6.5678123456789123e-06(short格式):转换为6.56781-6(长度8)
内容的提问来源于stack exchange,提问作者Lumpi
相关产品推荐
相关产品推荐

