| | 1 | /* Copyright 2000, 2002 Michael J. Roberts */ |
| | 2 | /* |
| | 3 | * TADS 3 library - number handling |
| | 4 | */ |
| | 5 | |
| | 6 | #include "adv3.h" |
| | 7 | |
| | 8 | |
| | 9 | /* ------------------------------------------------------------------------ */ |
| | 10 | /* |
| | 11 | * Spell out an integer number in words. Returns a string with the |
| | 12 | * spelled-out number. |
| | 13 | */ |
| | 14 | spellInt(val) |
| | 15 | { |
| | 16 | return spellIntExt(val, 0); |
| | 17 | } |
| | 18 | |
| | 19 | /* |
| | 20 | * Spell out an integer number in words, but only if it's below the |
| | 21 | * given threshhold. It's often awkward in prose to spell out large |
| | 22 | * numbers, but exactly what constitutes a large number depends on |
| | 23 | * context, so this routine lets the caller specify the threshhold. |
| | 24 | * |
| | 25 | * If the absolute value of val is less than (not equal to) the |
| | 26 | * threshhold value, we'll return a string with the number spelled out. |
| | 27 | * If the absolute value is greater than or equal to the threshhold |
| | 28 | * value, we'll return a string representing the number in decimal |
| | 29 | * digits. |
| | 30 | */ |
| | 31 | spellIntBelow(val, threshhold) |
| | 32 | { |
| | 33 | return spellIntBelowExt(val, threshhold, 0, 0); |
| | 34 | } |
| | 35 | |
| | 36 | /* |
| | 37 | * Spell out an integer number in words if it's below a threshhold, |
| | 38 | * using the SPELLINT_xxx flags given in spellFlags to control the |
| | 39 | * spelled-out format, and using the INTDEC_xxx flags in digitFlags to |
| | 40 | * control the digit format. |
| | 41 | */ |
| | 42 | spellIntBelowExt(val, threshhold, spellFlags, digitFlags) |
| | 43 | { |
| | 44 | local absval; |
| | 45 | |
| | 46 | /* compute the absolute value */ |
| | 47 | absval = (val < 0 ? -val : val); |
| | 48 | |
| | 49 | /* check the value to see whether to spell it or write it as digits */ |
| | 50 | if (absval < threshhold) |
| | 51 | { |
| | 52 | /* it's below the threshhold - spell it out in words */ |
| | 53 | return spellIntExt(val, spellFlags); |
| | 54 | } |
| | 55 | else |
| | 56 | { |
| | 57 | /* it's not below the threshhold - write it as digits */ |
| | 58 | return intToDecimal(val, digitFlags); |
| | 59 | } |
| | 60 | } |
| | 61 | |
| | 62 | /* |
| | 63 | * Format a number as a string of decimal digits. The INTDEC_xxx flags |
| | 64 | * specify how the number is to be formatted.` |
| | 65 | */ |
| | 66 | intToDecimal(val, flags) |
| | 67 | { |
| | 68 | local str; |
| | 69 | local sep; |
| | 70 | |
| | 71 | /* perform the basic conversion */ |
| | 72 | str = toString(val); |
| | 73 | |
| | 74 | /* add group separators as needed */ |
| | 75 | if ((flags & INTDEC_GROUP_COMMA) != 0) |
| | 76 | { |
| | 77 | /* explicitly use a comma as a separator */ |
| | 78 | sep = ','; |
| | 79 | } |
| | 80 | else if ((flags & INTDEC_GROUP_PERIOD) != 0) |
| | 81 | { |
| | 82 | /* explicitly use a period as a separator */ |
| | 83 | sep = '.'; |
| | 84 | } |
| | 85 | else if ((flags & INTDEC_GROUP_SEP) != 0) |
| | 86 | { |
| | 87 | /* use the current languageGlobals separator */ |
| | 88 | sep = languageGlobals.digitGroupSeparator; |
| | 89 | } |
| | 90 | else |
| | 91 | { |
| | 92 | /* no separator */ |
| | 93 | sep = nil; |
| | 94 | } |
| | 95 | |
| | 96 | /* if there's a separator, add it in */ |
| | 97 | if (sep != nil) |
| | 98 | { |
| | 99 | local i; |
| | 100 | local len; |
| | 101 | |
| | 102 | /* |
| | 103 | * Insert the separator before each group of three digits. |
| | 104 | * Start at the right end of the string and work left: peel off |
| | 105 | * the last three digits and insert a comma. Then, move back |
| | 106 | * four characters through the string - another three-digit |
| | 107 | * group, plus the comma we inserted - and repeat. Keep going |
| | 108 | * until the amount we'd want to peel off the end is as long or |
| | 109 | * longer than the entire remaining string. |
| | 110 | */ |
| | 111 | for (i = 3, len = str.length() ; len > i ; i += 4) |
| | 112 | { |
| | 113 | /* insert this comma */ |
| | 114 | str = str.substr(1, len - i) + sep + str.substr(len - i + 1); |
| | 115 | |
| | 116 | /* note the new length */ |
| | 117 | len = str.length(); |
| | 118 | } |
| | 119 | } |
| | 120 | |
| | 121 | /* return the result */ |
| | 122 | return str; |
| | 123 | } |
| | 124 | |
| | 125 | |
| | 126 | /* ------------------------------------------------------------------------ */ |
| | 127 | /* |
| | 128 | * Convert an integer number to Roman numerals. Returns a string with |
| | 129 | * the Roman numeral format. This can only accept numbers in the range |
| | 130 | * 1 to 4999; returns nil for anything outside of this range. |
| | 131 | */ |
| | 132 | intToRoman(val) |
| | 133 | { |
| | 134 | local str; |
| | 135 | local info = |
| | 136 | [ |
| | 137 | /* numeral value / corresponding string */ |
| | 138 | 1000, 'M', |
| | 139 | 900, 'CM', |
| | 140 | 500, 'D', |
| | 141 | 400, 'CD', |
| | 142 | 100, 'C', |
| | 143 | 90, 'XC', |
| | 144 | 50, 'L', |
| | 145 | 40, 'XL', |
| | 146 | 10, 'X', |
| | 147 | 9, 'IX', |
| | 148 | 5, 'V', |
| | 149 | 4, 'IV', |
| | 150 | 1, 'I' |
| | 151 | ]; |
| | 152 | local i; |
| | 153 | |
| | 154 | /* if the value is outside of the legal range, fail immediately */ |
| | 155 | if (val < 1 || val > 4999) |
| | 156 | return nil; |
| | 157 | |
| | 158 | /* |
| | 159 | * iterate over the specifiers and apply each one as many times as |
| | 160 | * possible |
| | 161 | */ |
| | 162 | for (str = '', i = 1 ; val != 0 ; ) |
| | 163 | { |
| | 164 | /* |
| | 165 | * If we're greater than the current specifier, apply it; |
| | 166 | * otherwise, move on to the next specifier. |
| | 167 | */ |
| | 168 | if (val >= info[i]) |
| | 169 | { |
| | 170 | /* add this specifier's roman numeral into the result */ |
| | 171 | str += info[i+1]; |
| | 172 | |
| | 173 | /* subtract the corresponding value */ |
| | 174 | val -= info[i]; |
| | 175 | } |
| | 176 | else |
| | 177 | { |
| | 178 | /* move to the next specifier */ |
| | 179 | i += 2; |
| | 180 | } |
| | 181 | } |
| | 182 | |
| | 183 | /* return the result */ |
| | 184 | return str; |
| | 185 | } |
| | 186 | |