decimal.odin 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // #import "fmt.odin";
  2. // Multiple precision decimal numbers
  3. // NOTE: This is only for floating point printing and nothing else
  4. type Decimal struct {
  5. digits: [384]u8, // big-endian digits
  6. count: int,
  7. decimal_point: int,
  8. neg, trunc: bool,
  9. }
  10. proc decimal_to_string(buf: []u8, a: ^Decimal) -> string {
  11. proc digit_zero(buf: []u8) -> int {
  12. for _, i in buf {
  13. buf[i] = '0';
  14. }
  15. return len(buf);
  16. }
  17. var n = 10 + a.count + abs(a.decimal_point);
  18. // TODO(bill): make this work with a buffer that's not big enough
  19. assert(len(buf) >= n);
  20. buf = buf[0..<n];
  21. if a.count == 0 {
  22. buf[0] = '0';
  23. return string(buf[0..<1]);
  24. }
  25. var w = 0;
  26. if a.decimal_point <= 0 {
  27. buf[w] = '0'; w++;
  28. buf[w] = '.'; w++;
  29. w += digit_zero(buf[w ..< w-a.decimal_point]);
  30. w += copy(buf[w..], a.digits[0..<a.count]);
  31. } else if a.decimal_point < a.count {
  32. w += copy(buf[w..], a.digits[0..<a.decimal_point]);
  33. buf[w] = '.'; w++;
  34. w += copy(buf[w..], a.digits[a.decimal_point ..< a.count]);
  35. } else {
  36. w += copy(buf[w..], a.digits[0..<a.count]);
  37. w += digit_zero(buf[w ..< w+a.decimal_point-a.count]);
  38. }
  39. return string(buf[0..<w]);
  40. }
  41. // trim trailing zeros
  42. proc trim(a: ^Decimal) {
  43. for a.count > 0 && a.digits[a.count-1] == '0' {
  44. a.count--;
  45. }
  46. if a.count == 0 {
  47. a.decimal_point = 0;
  48. }
  49. }
  50. proc assign(a: ^Decimal, i: u64) {
  51. var buf: [32]u8;
  52. var n = 0;
  53. for i > 0 {
  54. var j = i/10;
  55. i -= 10*j;
  56. buf[n] = u8('0'+i);
  57. n++;
  58. i = j;
  59. }
  60. a.count = 0;
  61. for n--; n >= 0; n-- {
  62. a.digits[a.count] = buf[n];
  63. a.count++;
  64. }
  65. a.decimal_point = a.count;
  66. trim(a);
  67. }
  68. proc shift_right(a: ^Decimal, k: uint) {
  69. var r = 0; // read index
  70. var w = 0; // write index
  71. var n: uint;
  72. for ; n>>k == 0; r++ {
  73. if r >= a.count {
  74. if n == 0 {
  75. // Just in case
  76. a.count = 0;
  77. return;
  78. }
  79. for n>>k == 0 {
  80. n = n * 10;
  81. r++;
  82. }
  83. break;
  84. }
  85. var c = uint(a.digits[r]);
  86. n = n*10 + c - '0';
  87. }
  88. a.decimal_point -= r-1;
  89. var mask: uint = (1<<k) - 1;
  90. for ; r < a.count; r++ {
  91. var c = uint(a.digits[r]);
  92. var dig = n>>k;
  93. n &= mask;
  94. a.digits[w] = u8('0' + dig);
  95. w++;
  96. n = n*10 + c - '0';
  97. }
  98. for n > 0 {
  99. var dig = n>>k;
  100. n &= mask;
  101. if w < len(a.digits) {
  102. a.digits[w] = u8('0' + dig);
  103. w++;
  104. } else if dig > 0 {
  105. a.trunc = true;
  106. }
  107. n *= 10;
  108. }
  109. a.count = w;
  110. trim(a);
  111. }
  112. proc shift_left(a: ^Decimal, k: uint) {
  113. var delta = int(k/4);
  114. var r = a.count; // read index
  115. var w = a.count+delta; // write index
  116. var n: uint;
  117. for r--; r >= 0; r-- {
  118. n += (uint(a.digits[r]) - '0') << k;
  119. var quo = n/10;
  120. var rem = n - 10*quo;
  121. w--;
  122. if w < len(a.digits) {
  123. a.digits[w] = u8('0' + rem);
  124. } else if rem != 0 {
  125. a.trunc = true;
  126. }
  127. n = quo;
  128. }
  129. for n > 0 {
  130. var quo = n/10;
  131. var rem = n - 10*quo;
  132. w--;
  133. if 0 <= w && w < len(a.digits) {
  134. a.digits[w] = u8('0' + rem);
  135. } else if rem != 0 {
  136. a.trunc = true;
  137. }
  138. n = quo;
  139. }
  140. a.count += delta;
  141. a.count = min(a.count, len(a.digits));
  142. a.decimal_point += delta;
  143. trim(a);
  144. }
  145. proc shift(a: ^Decimal, k: int) {
  146. const (
  147. uint_size = 8*size_of(uint);
  148. max_shift = uint_size-4;
  149. )
  150. match {
  151. case a.count == 0:
  152. // no need to update
  153. case k > 0:
  154. for k > max_shift {
  155. shift_left(a, max_shift);
  156. k -= max_shift;
  157. }
  158. shift_left(a, uint(k));
  159. case k < 0:
  160. for k < -max_shift {
  161. shift_right(a, max_shift);
  162. k += max_shift;
  163. }
  164. shift_right(a, uint(-k));
  165. }
  166. }
  167. proc can_round_up(a: ^Decimal, nd: int) -> bool {
  168. if nd < 0 || nd >= a.count { return false ; }
  169. if a.digits[nd] == '5' && nd+1 == a.count {
  170. if a.trunc {
  171. return true;
  172. }
  173. return nd > 0 && (a.digits[nd-1]-'0')%2 != 0;
  174. }
  175. return a.digits[nd] >= '5';
  176. }
  177. proc round(a: ^Decimal, nd: int) {
  178. if nd < 0 || nd >= a.count { return; }
  179. if can_round_up(a, nd) {
  180. round_up(a, nd);
  181. } else {
  182. round_down(a, nd);
  183. }
  184. }
  185. proc round_up(a: ^Decimal, nd: int) {
  186. if nd < 0 || nd >= a.count { return; }
  187. for var i = nd-1; i >= 0; i-- {
  188. if var c = a.digits[i]; c < '9' {
  189. a.digits[i]++;
  190. a.count = i+1;
  191. return;
  192. }
  193. }
  194. // Number is just 9s
  195. a.digits[0] = '1';
  196. a.count = 1;
  197. a.decimal_point++;
  198. }
  199. proc round_down(a: ^Decimal, nd: int) {
  200. if nd < 0 || nd >= a.count { return; }
  201. a.count = nd;
  202. trim(a);
  203. }
  204. // Extract integer part, rounded appropriately. There are no guarantees about overflow.
  205. proc rounded_integer(a: ^Decimal) -> u64 {
  206. if a.decimal_point > 20 {
  207. return 0xffff_ffff_ffff_ffff;
  208. }
  209. var i: int;
  210. var n: u64 = 0;
  211. var m = min(a.decimal_point, a.count);
  212. for i = 0; i < m; i++ {
  213. n = n*10 + u64(a.digits[i]-'0');
  214. }
  215. for ; i < a.decimal_point; i++ {
  216. n *= 10;
  217. }
  218. if can_round_up(a, a.decimal_point) {
  219. n++;
  220. }
  221. return n;
  222. }