decimal_utils.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from decimal import Decimal, getcontext
  2. # 增加精度以处理更多小数位,如果需要的话
  3. getcontext().prec = 50
  4. def decimal_to_string_no_scientific(d_value: Decimal) -> str:
  5. """
  6. 将 Decimal 类型转换为无科学计数法的字符串。
  7. 尝试使用 normalize() 确保消除科学计数法,并保持所有有效数字。
  8. """
  9. if not isinstance(d_value, Decimal):
  10. raise TypeError("Input must be a Decimal type.")
  11. # normalize() 方法可以移除尾随的0并调整指数,但不会强制转换为非科学计数法
  12. # 转换为字符串时,Python的Decimal默认会尽可能不使用科学计数法,
  13. # 除非数字非常大或非常小。
  14. # 对于极小的数,直接str()可能会出现科学计数法。
  15. # 更稳妥的方法是,将其格式化为字符串,并确保它没有科学计数法。
  16. # 我们可以通过精确控制小数点后的位数来避免科学计数法。
  17. # 但由于我们不知道具体需要多少位,所以直接用str()通常是最直接的。
  18. s_value = str(d_value)
  19. # 检查是否包含科学计数法标识 'e' 或 'E'
  20. if 'e' in s_value.lower():
  21. # 如果包含,我们需要更精细地处理
  22. # 先将其标准化,然后转换为字符串
  23. normalized_d = d_value.normalize()
  24. s_value = str(normalized_d)
  25. # 即使normalize(),对于非常大的或非常小的数,str()仍然可能使用科学计数法。
  26. # 强制转换为非科学计数法,可以通过指定一个非常大的小数点后位数来实现。
  27. # 这里,我们查找小数点后的最大位数来避免信息丢失。
  28. if 'e' in s_value.lower(): # 再次检查,以防normalize()后仍有
  29. # 通常,当Decimal转换成字符串时显示科学计数法,说明它要么很大,要么很小。
  30. # 对于很小的数,我们通过扩展精度来确保所有零被显示。
  31. # 例如:0.00000001
  32. # 可以通过创建负指数的Decimal来表示小数点后的位数
  33. # 例如 Decimal(10)**(-N)
  34. # 但一个更通用的方法是,将其转换为字符串后,如果发现'e',则手动解析和格式化。
  35. # Decimal模块通常在str()时会避免科学计数法,除非绝对必要。
  36. # 如果出现,尝试使用to_eng_string(),它可能看起来更像非科学计数法。
  37. # 但最直接的是手动控制精度
  38. s_value = _format_decimal_to_plain_string(d_value)
  39. return s_value
  40. def _format_decimal_to_plain_string(d_value: Decimal) -> str:
  41. """
  42. 辅助函数:将Decimal强制转换为无科学计数法的字符串。
  43. 通过动态确定小数点后的位数。
  44. """
  45. s = str(d_value)
  46. if 'E' in s or 'e' in s:
  47. # 提取指数部分
  48. if 'E' in s:
  49. parts = s.split('E')
  50. else:
  51. parts = s.split('e')
  52. coefficient = parts[0]
  53. exponent = int(parts[1])
  54. # 处理负号
  55. sign = ""
  56. if coefficient.startswith('-'):
  57. sign = "-"
  58. coefficient = coefficient[1:]
  59. # 处理小数点
  60. if '.' in coefficient:
  61. int_part, frac_part = coefficient.split('.')
  62. else:
  63. int_part = coefficient
  64. frac_part = ""
  65. # 根据指数移动小数点
  66. if exponent >= 0:
  67. # 扩大整数部分,补零
  68. num_zeros = exponent - len(frac_part)
  69. if num_zeros >= 0:
  70. result = int_part + frac_part + '0' * num_zeros
  71. else:
  72. result = int_part + frac_part[:exponent] + '.' + frac_part[exponent:]
  73. else: # exponent < 0
  74. # 缩小整数部分,在前面补零
  75. abs_exponent = abs(exponent)
  76. if len(int_part) <= abs_exponent:
  77. # 例如 1.23E-4 -> 0.000123
  78. result = '0.' + '0' * (abs_exponent - len(int_part)) + int_part + frac_part
  79. else:
  80. # 例如 123.45E-1 -> 12.345
  81. insert_pos = len(int_part) - abs_exponent
  82. result = int_part[:insert_pos] + '.' + int_part[insert_pos:] + frac_part
  83. # 移除结果中可能的尾随小数点(例如 '123.' -> '123')
  84. if result.endswith('.'):
  85. result = result[:-1]
  86. # 移除结果中多余的前导零(例如 '007' -> '7',但保留 '0.xx')
  87. if result != '0' and result.startswith('0') and not result.startswith('0.'):
  88. result = result.lstrip('0')
  89. if not result: # 如果只剩下0,则保留一个0
  90. result = '0'
  91. return sign + result
  92. else:
  93. return s
  94. # ---- 测试 ----
  95. if __name__ == "__main__":
  96. # 示例 1: 原始示例中成功的 case
  97. params1 = {
  98. 'price': Decimal('0.0004098'),
  99. 'quantity': 160000,
  100. 'side': 'SELL',
  101. 'symbol': 'HASHAIUSDT',
  102. 'type': 'LIMIT'
  103. }
  104. price_str1 = decimal_to_string_no_scientific(params1['price'])
  105. print(f"Original Decimal: {params1['price']}, Converted String: '{price_str1}'")
  106. # 预期输出: '0.0004098'
  107. # 示例 2: 原始示例中失败的 case
  108. params2 = {
  109. 'price': Decimal('1.617015400E-8'),
  110. 'quantity': 320000000,
  111. 'side': 'BUY',
  112. 'symbol': 'MANYUSDT',
  113. 'type': 'LIMIT'
  114. }
  115. price_str2 = decimal_to_string_no_scientific(params2['price'])
  116. print(f"Original Decimal: {params2['price']}, Converted String: '{price_str2}'")
  117. # 预期输出: '0.00000001617015400'
  118. # 更多测试用例
  119. # 较大整数
  120. d3 = Decimal('12345678901234567890.1')
  121. s3 = decimal_to_string_no_scientific(d3)
  122. print(f"Original Decimal: {d3}, Converted String: '{s3}'")
  123. # 预期输出: '12345678901234567890.1'
  124. # 较大整数带科学计数法表示
  125. d4 = Decimal('1.23E+10')
  126. s4 = decimal_to_string_no_scientific(d4)
  127. print(f"Original Decimal: {d4}, Converted String: '{s4}'")
  128. # 预期输出: '12300000000'
  129. # 较小浮点数,且 str() 本身可能显示科学计数法
  130. d5 = Decimal('0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') # 这是一个非常非常小的数
  131. s5 = decimal_to_string_no_scientific(d5)
  132. print(f"Original Decimal: {d5}, Converted String: '{s5}'")
  133. # 预期输出: 应该是一长串0.000...000后跟着一个1
  134. # 负数
  135. d6 = Decimal('-1.2345E-5')
  136. s6 = decimal_to_string_no_scientific(d6)
  137. print(f"Original Decimal: {d6}, Converted String: '{s6}'")
  138. # 预期输出: '-0.000012345'
  139. d7 = Decimal('0')
  140. s7 = decimal_to_string_no_scientific(d7)
  141. print(f"Original Decimal: {d7}, Converted String: '{s7}'")
  142. # 预期输出: '0'
  143. d8 = Decimal('-0.00') # 保持尾随的0
  144. s8 = decimal_to_string_no_scientific(d8)
  145. print(f"Original Decimal: {d8}, Converted String: '{s8}'")
  146. # 预期输出: '-0.00'
  147. d9 = Decimal('12300.00')
  148. s9 = decimal_to_string_no_scientific(d9)
  149. print(f"Original Decimal: {d9}, Converted String: '{s9}'")
  150. # 预期输出: '12300.00'