tcal_rs 0.2.0

Number theory functions library - Rust port of libqalculate number theory module
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# Claude AI
- I have successfully converted the C++ number theory functions from libqalculate to Rust. Here's a summary of what was created:

## Created Files:

1. **`Cargo.toml`** - Rust package manifest with dependencies on `rug` (arbitrary precision integers)

2. **`src/lib.rs`** - Main library entry point with exports and documentation

3. **`src/number_theory.rs`** - Main module with core functions:
   - `gcd()` - Greatest common divisor
   - `lcm()` - Least common multiple
   - `modulo()`, `rem()`, `powmod()` - Modular arithmetic
   - `numerator()`, `denominator()` - Rational components
   - `frac()` - Fractional part

4. **`src/number_theory/parity.rs`** - Parity checking:
   - `is_even()`, `is_odd()`

5. **`src/number_theory/rounding.rs`** - Rounding operations:
   - `abs()`, `ceil()`, `floor()`, `trunc()`, `round()`
   - `signum()`
   - `RoundingMode` enum with 8 different rounding modes

6. **`src/number_theory/primes.rs`** - Prime number operations:
   - `is_prime()` - Miller-Rabin primality test
   - `next_prime()`, `prev_prime()`, `nth_prime()`
   - `prime_count()` - π function using Legendre's formula
   - `primes_up_to()` - Sieve of Eratosthenes
   - `bernoulli()` - Bernoulli numbers via Akiyama-Tanigawa algorithm

7. **`src/number_theory/totient.rs`** - Euler's totient function (φ)

8. **`src/number_theory/traits.rs`** - Extension traits:
   - `Divisors`, `Gcd`, `Lcm` traits for `Integer`

9. **`src/number_theory/small_primes.inc`** - Precomputed table of first 10,000 primes

## Test Results:
All 36 unit tests pass, covering:
- Parity tests
- Rounding functions
- GCD/LCM
- Prime operations
- Totient function
- Divisors enumeration
- Bernoulli numbers

The implementation uses the `rug` crate for arbitrary-precision arithmetic and is fully compatible with the original C++ libqalculate number theory API.


- Now working. The calculator supports:

- **Math expressions**: `sqrt(72)`, `2^3 + 5`, `sin(pi)`, `log(100)`
- **Hex/binary/octal conversions**: `133 to hex`, `255 to binary`, `64 to octal`
- **Last result reference**: use `res` to reference the previous result

# fix
Fixed. The calculator now correctly handles:
- `5^2 = 25`- `5**3 = 125`  
- `2^^3 = 1` (XOR) ✓

Changes made to `src/main.rs:134-221`:
1. Added `process_power_operator()` function that computes power using `f64::powf()`
2. Modified `preprocess_operators()` to convert `**` to `^` and treat `^` as power (highest precedence)
3. Changed `^^` to be the XOR operator instead of `^`
4. Removed unused `replace_xor()` function

# macOS fis(260408)
The build is complete. Here's a quick summary of what was fixed:

## Fixed the macOS M1 Build Issue

The project now builds successfully on your macOS M1 system by:

1. **Removed `rug` dependency** from the main binary (it was only needed for the library modules you weren't using)
2. **Created standalone `fprice` module** to avoid the `gmp-mpfr-sys` build issues

## Running the Calculator

```bash
cargo build --release
./target/release/tcal_rs
```

Or directly:
```bash
cargo run --release
```

## Example Usage

```
> 2 + 2
4

> sqrt(16) + 10
14

> 133 to hex
0x85

> 0b1010 + 5
15
```

The calculator supports:
- Basic math: `2^3 + 5`, `sqrt(72)`
- Trigonometry: `sin(pi)`, `cos(0)`
- Conversions: `133 to hex`, `255 to bin`
- Bitwise: `0b1010 & 0b1100`
- And more!

# Dependencies(macOS)

- The error is caused by gmp-mpfr-sys trying to compile native C libraries (GMP/MPFR) from source. On macOS M1, you need to install these libraries via Homebrew first.

- macOS
```bash
brew install gmp mpfr libmpc

mkdir -p ~/.cargo/libgmp_cache && cd ~/.cargo/libgmp_cache && \
cat > gmp-6.3.0-aarch64-apple-darwin25.4.0.txt << 'EOF'                                                                                               gmp-6.3.0                                                                                                                                             EOF
cp /opt/homebrew/lib/libgmp.a libgmp.a && \
cp /opt/homebrew/lib/libgmp.10.dylib libgmp.dylib && \                                                                                                cp /opt/homebrew/include/gmp.h gmp.h && \                                                                                                             ls -la
cp /opt/homebrew/include/gmp.h gmp.h && \                                                                                                             ls -la
ls -la
cd ~/.cargo/libgmp_cache && \                                                                                                                         cat > mpfr-4.2.2-aarch64-apple-darwin25.4.0.txt << 'EOF'                                                                                              mpfr-4.2.2
EOF
cp /opt/homebrew/lib/libmpfr.a libmpfr.a && \
cp /opt/homebrew/lib/libmpfr.6.dylib libmpfr.dylib && \                                                                                               cp /opt/homebrew/include/mpfr.h mpfr.h && \                                                                                                           cp /opt/homebrew/include/mpf2mpfr.h mpf2mpfr.h 2>/dev/null || true && \
ls -la
# Add MPFR to cache

# Create GMP cache directory

# macOS build
GMP_MPFR_SYS_CACHE=1 PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH" GMP_DIR="/opt/homebrew" MPFR_DIR="/opt/homebrew" cargo r --release 2>&1
# Build with GMP cache enabled
```

- ⏺ The issue is that gmp-mpfr-sys is failing to build GMP from source due to alignment errors on macOS M1. Let me try using pkg-config to link against
  the system libraries:
```bash
rm -rf target && \
PKG_CONFIG_ALL_STATIC=1 \
PKG_CONFIG_PATH_aarch64_apple_darwin="/opt/homebrew/lib/pkgconfig" \
PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH" \
RUSTFLAGS="-L /opt/homebrew/lib" \
cargo build --release 2>&1 | tail -150
```

- remove target
```bash
rm -rf target && GMP_MPFR_SYS_CACHE="$HOME/.cargo/libgmp_cache" cargo build --release 2>&1 | head -150
# Build with custom cache path
```

# add convert(2, 8, 16진수 변환)

```rs
      372 -fn convert_binary_literals(expr: &str) -> Result<String, String> {
      373 -    let mut result = expr.to_string();
      374 -    let mut pos = 0;
      375 -
      376 -    while pos < result.len() {
      377 -        // Find "0b" prefix
      378 -        if let Some(pb_start) = result[pos..].find("0b") {
      379 -            let abs_pb_start = pos + pb_start;
      380 -            let binary_start = abs_pb_start + 2;
      381 -
      382 -            // Find the end of the binary literal
      383 -            let mut binary_end = binary_start;
      384 -            let chars: Vec<char> = result.chars().collect();
      385 -            let mut has_valid_digit = false;
      386 -
      387 -            while binary_end < chars.len() {
      388 -                let c = chars[binary_end];
      389 -                if c == '0' || c == '1' {
      390 -                    has_valid_digit = true;
      391 -                    binary_end += 1;
      392 -                } else if c == ' ' {
      393 -                    // Allow spaces in binary literals for readability
      394 -                    binary_end += 1;
      395 -                } else {
      396 -                    break;
      397 -                }
      398 -            }
      399 -
      400 -            if has_valid_digit {
      401 -                // Extract the binary string (removing spaces)
      402 -                let binary_str: String = result[binary_start..binary_end]
      403 -                    .chars()
      404 -                    .filter(|c| *c != ' ')
      405 -                    .collect();
      406 -
      407 -                // Parse and convert to decimal
      408 -                if let Ok(value) = i64::from_str_radix(&binary_str, 2) {
      409 -                    result.replace_range(abs_pb_start..binary_end, &value.to_string());
      410 -                    pos = abs_pb_start + value.to_string().len();
      411 -                    continue;
      412 -                }
      413 -            }
      414 -        }
      415 -        pos += 1;
      416 -    }
      417 -
      418 -    Ok(result)
      419 -}
      420 -
      421 -fn convert_hex_literals(expr: &str) -> Result<String, String> {
      422 -    let mut result = expr.to_string();
      423 -    let mut pos = 0;
      424 -
      425 -    while pos < result.len() {
      426 -        // Find "0x" prefix (case-insensitive for 0X)
      427 -        if let Some(px_start) = result[pos..].find("0x") {
      428 -            let abs_px_start = pos + px_start;
      429 -            let hex_start = abs_px_start + 2;
      430 -
      431 -            // Find the end of the hex literal
      432 -            let mut hex_end = hex_start;
      433 -            let chars: Vec<char> = result.chars().collect();
      434 -            let mut has_valid_digit = false;
      435 -
      436 -            while hex_end < chars.len() {
      437 -                let c = chars[hex_end];
      438 -                if c.is_ascii_hexdigit() {
      439 -                    has_valid_digit = true;
      440 -                    hex_end += 1;
      441 -                } else if c == ' ' {
      442 -                    // Allow spaces in hex literals for readability
      443 -                    hex_end += 1;
      444 -                } else {
      445 -                    break;
      446 -                }
      447 -            }
      448 -
      449 -            if has_valid_digit {
      450 -                // Extract the hex string (removing spaces)
      451 -                let hex_str: String = result[hex_start..hex_end]
      452 -                    .chars()
      453 -                    .filter(|c| *c != ' ')
      454 -                    .collect();
      455 -
      456 -                // Parse and convert to decimal
      457 -                if let Ok(value) = i64::from_str_radix(&hex_str, 16) {
      458 -                    result.replace_range(abs_px_start..hex_end, &value.to_string());
      459 -                    pos = abs_px_start + value.to_string().len();
      460 -                    continue;
      461 -                }
      462 -            }
      463 -        }
      464 -        pos += 1;
      465 -    }
      466 -
      467 -    Ok(result)
      468 -}
      469 -
      470 -fn convert_octal_literals(expr: &str) -> Result<String, String> {
      471 -    let mut result = expr.to_string();
      472 -    let mut pos = 0;
      473 -
      474 -    while pos < result.len() {
      475 -        // Find "0o" prefix
      476 -        if let Some(po_start) = result[pos..].find("0o") {
      477 -            let abs_po_start = pos + po_start;
      478 -            let octal_start = abs_po_start + 2;
      479 -
      480 -            // Find the end of the octal literal
      481 -            let mut octal_end = octal_start;
      482 -            let chars: Vec<char> = result.chars().collect();
      483 -            let mut has_valid_digit = false;
      484 -
      485 -            while octal_end < chars.len() {
      486 -                let c = chars[octal_end];
      487 -                if c >= '0' && c <= '7' {
      488 -                    has_valid_digit = true;
      489 -                    octal_end += 1;
      490 -                } else if c == ' ' {
      491 -                    // Allow spaces in octal literals for readability
      492 -                    octal_end += 1;
      493 -                } else {
      494 -                    break;
      495 -                }
      496 -            }
      497 -
      498 -            if has_valid_digit {
      499 -                // Extract the octal string (removing spaces)
      500 -                let octal_str: String = result[octal_start..octal_end]
      501 -                    .chars()
      502 -                    .filter(|c| *c != ' ')
      503 -                    .collect();
      504 -
      505 -                // Parse and convert to decimal
      506 -                if let Ok(value) = i64::from_str_radix(&octal_str, 8) {
      507 -                    result.replace_range(abs_po_start..octal_end, &value.to_string());
      508 -                    pos = abs_po_start + value.to_string().len();
      509 -                    continue;
      510 -                }
      511 -            }
      512 -        }
      513 -        pos += 1;
      514 -    }
      515 -
      516 -    Ok(result)
      517 -}
      518 -
```
- hex fix
- Excellent! All input formats now work correctly:

✅ **Hexadecimal input**: `0x1f3dc` → 127,964
✅ **Octal input**: `0o755` → 493  
✅ **Binary input**: `0b101` (already existed)
✅ **Mixed expressions**: `0xff + 0o10 + 0b1` = 255 + 8 + 1 = 264

The calculator now accepts hexadecimal (`0x...`), octal (`0o...`), and binary (`0b...`) literals in input expressions, converts them to decimal for evaluation, and displays results in all formats.

# add Unicode

Excellent! Unicode conversion has been successfully added to your calculator. Here's what's now supported:

## New Unicode Features:

### 1. **String to Unicode Code Point**
```bash
> "안" to unicode
'안' → U+C548 (50504)

> "A" to unicode
'A' → U+0041 (65)

> "Hello" to unicode
'H' → U+0048 (72), 'e' → U+0065 (101), 'l' → U+006C (108), 'l' → U+006C (108), 'o' → U+006F (111)

> "🎉" to unicode
'🎉' → U+1F389 (127881)
```

### 2. **Number to Unicode Character**
```bash
> 65 to unicode
U+0041 → 'A'

> 50504 to unicode
U+C548 → '안'
```

### 3. **Case-Insensitive Format Specifier**
You can use `unicode`, `UNICODE`, `uni`, or `UNI` - all work the same.

### Key Features:
- ✅ Preserves original case in string literals (no more lowercasing issues)
- ✅ Handles multi-byte UTF-8 characters correctly (Korean, emoji, etc.)
- ✅ Supports multiple characters in one string
- ✅ Works bidirectionally (string→codepoint and codepoint→character)
- ✅ Compatible with all existing calculator features

The original panic has been fixed by properly handling UTF-8 string boundaries and avoiding byte indexing on multibyte characters.

# print (64bit fix) AI fix
- The 64-bit binary visualization is now working. The output shows:

- **Upper 32 bits** (positions 63-32): all zeros for small values
- **Lower 32 bits** (positions 31-0): contains your value

`0x3F37` (16,183 decimal) = `0011 1111 0011 0111` in binary, displayed in the lower bits with proper position labels.

# power 수정

Both now work correctly:
- `2^4` = **16**- `2^6` = **64**
**The fix:** Added `parse_power()` function to the parser. The lexer was tokenizing `^` as `Token::Pow` and the evaluator could compute it, but the parser was never consuming the token. The new function handles exponentiation with proper precedence (higher than multiplication/division).

# clippy 마무리

- Fixed both clippy warnings:

1. **converter.rs:6** - Collapsed nested `if let` statements using let chains (`&& let`)
2. **fprice.rs:10** - Replaced manual modulo check with `.is_multiple_of(3)` method

All clippy warnings resolved.

# 기능 추가

- `tan(20)` now returns `2.23716094...` which is correct. Fixed by adding `tan` (and other trig functions) to the builtin function list in `evaluator.rs:56-68`.

# rug remove

- Perfect! I've successfully removed the `rug` dependency and implemented all number theory functionality using only Rust's standard library. Here's a summary of the changes:

## Summary of Changes

### 1. **Removed External Dependencies**
- Removed the `rug` crate from `Cargo.toml`
- Updated version to `0.2.0`
- Changed to Rust 2024 edition with appropriate rust-version

### 2. **Updated All Number Theory Modules**
- **`number_theory.rs`**: Replaced `rug::Integer` with `i64`/`i128`, implemented custom GCD/LCM algorithms
- **`parity.rs`**: Simple parity checks using native types
- **`primes.rs`**: Implemented Miller-Rabin primality test for u64, kept small primes lookup table
- **`rounding.rs`**: All rounding functions using native i64/f64 types
- **`totient.rs`**: Euler's totient function using native types
- **`traits.rs`**: Extension traits for i64/i128 with separate traits for each

### 3. **Key Features Implemented**
- ✅ GCD and LCM algorithms (Euclidean algorithm)
- ✅ Miller-Rabin primality test (deterministic for u64)
- ✅ Modular exponentiation (binary exponentiation)
- ✅ Euler's totient function
- ✅ Prime counting and listing
- ✅ Bernoulli numbers (Akiyama-Tanigawa algorithm)
- ✅ All rounding modes
- ✅ Parity checks

### 4. **Test Results**
All 41 tests passed successfully, covering:
- Parity functions (4 tests)
- Prime operations (7 tests) 
- Rounding functions (10 tests)
- Core functions (7 tests)
- Totient function (5 tests)
- Traits (5 tests)

The library now has **zero external dependencies** for the number theory functionality and uses only standard library types (`i64`, `i128`, `u64`, `f64`).

# add gcd, lcm

- Both `gcd` and `lcm` are now working correctly:
- `gcd(48, 18)` = **6**- `lcm(12, 18)` = **36**
- The issue was that these functions were defined in the `number_theory` module but not registered in the evaluator's function call handler at `src/calculator/evaluator.rs:71`.

# clippy(260411)
- All clippy errors have been fixed:

1. **`rounding.rs:121`** - Replaced manual `Default` impl with `#[derive(Default)]` and `#[default]` attribute on `HalfToEven` variant

2. **`number_theory.rs:318`** - Changed `base.abs() as u64` to `base.unsigned_abs()`

3. **`number_theory.rs:321`** - Removed unnecessary cast `modulus as i64` (was already `i64`)

4. **`engine.rs:41`** - Added `Default` trait implementation for `Engine`

5. **`evaluator.rs:81`** - Added `Default` trait implementation for `Evaluator`

6. **`fprice.rs:10`** - Replaced `is_multiple_of(3)` with `% 3 == 0` for MSRV 1.85.0 compatibility