parse.rs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. use std::{iter::FromIterator, str::FromStr};
  2. use num_bigint::BigUint;
  3. use crate::{Error, Result};
  4. fn is_digit(c: char) -> bool {
  5. ('0'..='9').contains(&c)
  6. }
  7. fn char_eq(a: char, b: char) -> bool {
  8. a == b
  9. }
  10. pub fn decode_base10(amount: &str, decimal_places: usize, strict: bool) -> Result<BigUint> {
  11. let mut s: Vec<char> = amount.to_string().chars().collect();
  12. // Get rid of the decimal point:
  13. let point: usize = if let Some(p) = amount.find('.') {
  14. s.remove(p);
  15. p
  16. } else {
  17. s.len()
  18. };
  19. // Only digits should remain
  20. for i in &s {
  21. if !is_digit(*i) {
  22. return Err(Error::ParseFailed("Found non-digits"))
  23. }
  24. }
  25. // Add digits to the end if there are too few:
  26. let actual_places = s.len() - point;
  27. if actual_places < decimal_places {
  28. s.extend(vec!['0'; decimal_places - actual_places])
  29. }
  30. // Remove digits from the end if there are too many:
  31. let mut round = false;
  32. if actual_places > decimal_places {
  33. let end = point + decimal_places;
  34. for i in &s[end..s.len()] {
  35. if !char_eq(*i, '0') {
  36. round = true;
  37. break
  38. }
  39. }
  40. s.truncate(end);
  41. }
  42. if strict && round {
  43. return Err(Error::ParseFailed("Would end up rounding while strict"))
  44. }
  45. // Convert to an integer
  46. let number = BigUint::from_str(&String::from_iter(&s))?;
  47. // Round and return
  48. /*
  49. if round && number == u64::MAX {
  50. return Err(Error::ParseFailed("u64 overflow"));
  51. }
  52. */
  53. Ok(number + round as u64)
  54. }
  55. pub fn encode_base10(amount: BigUint, decimal_places: usize) -> String {
  56. let mut s: Vec<char> =
  57. format!("{:0width$}", amount, width = 1 + decimal_places).chars().collect();
  58. s.insert(s.len() - decimal_places, '.');
  59. String::from_iter(&s).trim_end_matches('0').trim_end_matches('.').to_string()
  60. }
  61. pub fn truncate(amount: u64, decimals: u16, token_decimals: u16) -> Result<u64> {
  62. let mut amount: Vec<char> = amount.to_string().chars().collect();
  63. if token_decimals > decimals {
  64. if amount.len() <= (token_decimals - decimals) as usize {
  65. return Ok(0)
  66. }
  67. amount.truncate(amount.len() - (token_decimals - decimals) as usize);
  68. }
  69. if token_decimals < decimals {
  70. amount.resize(amount.len() + (decimals - token_decimals) as usize, '0');
  71. }
  72. let amount = u64::from_str(&String::from_iter(amount))?;
  73. Ok(amount)
  74. }
  75. #[cfg(test)]
  76. mod tests {
  77. use super::{decode_base10, encode_base10, truncate};
  78. use num_bigint::ToBigUint;
  79. #[test]
  80. fn test_decode_base10() {
  81. assert_eq!(124.to_biguint().unwrap(), decode_base10("12.33", 1, false).unwrap());
  82. assert_eq!(1233000.to_biguint().unwrap(), decode_base10("12.33", 5, false).unwrap());
  83. assert_eq!(1200000.to_biguint().unwrap(), decode_base10("12.", 5, false).unwrap());
  84. assert_eq!(1200000.to_biguint().unwrap(), decode_base10("12", 5, false).unwrap());
  85. assert!(decode_base10("12.33", 1, true).is_err());
  86. }
  87. #[test]
  88. fn test_encode_base10() {
  89. assert_eq!("23.4321111", &encode_base10(234321111_u64.to_biguint().unwrap(), 7));
  90. assert_eq!("23432111.1", &encode_base10(234321111_u64.to_biguint().unwrap(), 1));
  91. assert_eq!("234321.1", &encode_base10(2343211_u64.to_biguint().unwrap(), 1));
  92. assert_eq!("2343211", &encode_base10(2343211_u64.to_biguint().unwrap(), 0));
  93. assert_eq!("0.00002343", &encode_base10(2343_u64.to_biguint().unwrap(), 8));
  94. }
  95. #[test]
  96. fn test_truncate() {
  97. // Token decimals is equal to 8
  98. assert_eq!(100, truncate(100, 8, 8).unwrap());
  99. assert_eq!(12, truncate(12, 8, 8).unwrap());
  100. // Token decimals is bigger than 8
  101. assert_eq!(100000000, truncate(1000000000, 8, 9).unwrap());
  102. assert_eq!(10, truncate(100, 8, 9).unwrap());
  103. assert_eq!(1, truncate(12, 8, 9).unwrap());
  104. assert_eq!(10, truncate(102, 8, 9).unwrap());
  105. assert_eq!(0, truncate(1, 8, 9).unwrap());
  106. assert_eq!(1, truncate(100000000, 8, 16).unwrap());
  107. assert_eq!(10, truncate(100000000, 8, 15).unwrap());
  108. assert_eq!(0, truncate(100000000, 8, 17).unwrap());
  109. assert_eq!(0, truncate(10, 8, 16).unwrap());
  110. // Token decimals is less than 8
  111. assert_eq!(1000, truncate(100, 8, 7).unwrap());
  112. assert_eq!(12000, truncate(120, 8, 6).unwrap());
  113. assert_eq!(1000000, truncate(100, 8, 4).unwrap());
  114. // token decimals is 0
  115. assert_eq!(00000000, truncate(0, 8, 0).unwrap());
  116. assert_eq!(100000000, truncate(1, 8, 0).unwrap());
  117. //
  118. // reverse truncate
  119. //
  120. // Token decimals is less than decimals
  121. assert_eq!(1000000000, truncate(100000000, 9, 8).unwrap());
  122. assert_eq!(100000000, truncate(10000000, 9, 8).unwrap());
  123. assert_eq!(100, truncate(10, 9, 8).unwrap());
  124. assert_eq!(10, truncate(1, 9, 8).unwrap());
  125. assert_eq!(100, truncate(10, 9, 8).unwrap());
  126. assert_eq!(0, truncate(0, 9, 8).unwrap());
  127. assert_eq!(100000000, truncate(1, 16, 8).unwrap());
  128. assert_eq!(100000000, truncate(10, 15, 8).unwrap());
  129. assert_eq!(0, truncate(0, 17, 8).unwrap());
  130. // Token decimals is bigger than decimals
  131. assert_eq!(100, truncate(1000, 7, 8).unwrap());
  132. assert_eq!(120, truncate(12000, 6, 8).unwrap());
  133. assert_eq!(100, truncate(1000000, 4, 8).unwrap());
  134. // token decimals is 0
  135. assert_eq!(0, truncate(00000000, 0, 8).unwrap());
  136. assert_eq!(1, truncate(100000000, 0, 8).unwrap());
  137. }
  138. }