1use crate::float::{ParseError, Status};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum Width {
33 D32,
35 D64,
37 D128,
39}
40
41impl Width {
42 #[must_use]
44 pub const fn bits(self) -> u32 {
45 match self {
46 Width::D32 => 32,
47 Width::D64 => 64,
48 Width::D128 => 128,
49 }
50 }
51
52 #[must_use]
54 pub const fn digits(self) -> u32 {
55 match self {
56 Width::D32 => 7,
57 Width::D64 => 16,
58 Width::D128 => 34,
59 }
60 }
61
62 #[must_use]
64 pub const fn bias(self) -> i32 {
65 match self {
66 Width::D32 => 101,
67 Width::D64 => 398,
68 Width::D128 => 6176,
69 }
70 }
71
72 #[must_use]
74 pub const fn min_exponent(self) -> i32 {
75 -self.bias()
76 }
77
78 #[must_use]
80 pub const fn max_exponent(self) -> i32 {
81 match self {
82 Width::D32 => 90,
83 Width::D64 => 369,
84 Width::D128 => 6111,
85 }
86 }
87
88 const fn exponent_bits(self) -> u32 {
90 match self {
91 Width::D32 => 8,
92 Width::D64 => 10,
93 Width::D128 => 14,
94 }
95 }
96
97 const fn coefficient_bits(self) -> u32 {
100 self.bits() - 1 - self.exponent_bits()
101 }
102
103 #[must_use]
105 pub const fn max_coefficient(self) -> u128 {
106 pow10(self.digits()) - 1
107 }
108}
109
110const fn pow10(n: u32) -> u128 {
112 let mut value = 1u128;
113 let mut i = 0;
114 while i < n {
115 value *= 10;
116 i += 1;
117 }
118 value
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum Decoded {
124 Finite {
126 sign: bool,
128 coefficient: u128,
130 exponent: i32,
132 },
133 Infinite {
135 sign: bool,
137 },
138 Nan {
140 sign: bool,
142 signaling: bool,
144 },
145}
146
147#[must_use]
149pub const fn sign_bit(width: Width) -> u128 {
150 1u128 << (width.bits() - 1)
151}
152
153#[must_use]
161pub fn encode(sign: bool, coefficient: u128, exponent: i32, width: Width) -> u128 {
162 assert!(coefficient <= width.max_coefficient(), "the coefficient was not rounded");
163 assert!(
164 (width.min_exponent()..=width.max_exponent()).contains(&exponent),
165 "the exponent was not brought into range"
166 );
167 let biased = u128::try_from(exponent + width.bias()).expect("in range, so not negative");
168 let small = width.coefficient_bits();
169 let sign = if sign { sign_bit(width) } else { 0 };
170 if coefficient < 1u128 << small {
171 sign | (biased << small) | coefficient
172 } else {
173 let rest = small - 2;
178 sign | (0b11 << (width.bits() - 3))
179 | (biased << rest)
180 | (coefficient & ((1u128 << rest) - 1))
181 }
182}
183
184#[must_use]
186pub const fn infinity(sign: bool, width: Width) -> u128 {
187 let bits = 0b11110u128 << (width.bits() - 6);
188 if sign { bits | sign_bit(width) } else { bits }
189}
190
191#[must_use]
193pub const fn nan(width: Width) -> u128 {
194 0b11_1110u128 << (width.bits() - 7)
195}
196
197#[must_use]
202pub fn decode(bits: u128, width: Width) -> Decoded {
203 let top = width.bits();
204 let sign = bits & sign_bit(width) != 0;
205 let combination = (bits >> (top - 6)) & 0b11111;
206 if combination == 0b11110 {
207 return Decoded::Infinite { sign };
208 }
209 if combination == 0b11111 {
210 let signaling = (bits >> (top - 7)) & 1 != 0;
211 return Decoded::Nan { sign, signaling };
212 }
213 let small = width.coefficient_bits();
214 let field = (1u128 << width.exponent_bits()) - 1;
215 let (biased, coefficient) = if (bits >> (top - 3)) & 0b11 == 0b11 {
216 let rest = small - 2;
217 ((bits >> rest) & field, (0b100 << rest) | (bits & ((1u128 << rest) - 1)))
218 } else {
219 ((bits >> small) & field, bits & ((1u128 << small) - 1))
220 };
221 let exponent = i32::try_from(biased).unwrap_or(0) - width.bias();
223 let coefficient = if coefficient > width.max_coefficient() { 0 } else { coefficient };
224 Decoded::Finite { sign, coefficient, exponent }
225}
226
227pub fn parse(text: &str, width: Width) -> Result<(u128, Status), ParseError> {
239 let bytes = text.as_bytes();
240 let (sign, rest) = match bytes.first() {
241 Some(b'-') => (true, &bytes[1..]),
242 Some(b'+') => (false, &bytes[1..]),
243 _ => (false, bytes),
244 };
245 let (mantissa, exponent_text) = match rest.iter().position(|&b| b | 32 == b'e') {
246 Some(at) => (&rest[..at], Some(&rest[at + 1..])),
247 None => (rest, None),
248 };
249
250 let mut digits = Vec::new();
253 let mut seen_digit = false;
254 let mut after_point: i64 = 0;
255 let mut point = false;
256 for &b in mantissa {
257 match b {
258 b'0'..=b'9' => {
259 seen_digit = true;
260 if point {
261 after_point += 1;
262 }
263 if b != b'0' || !digits.is_empty() {
264 digits.push(b - b'0');
265 }
266 }
267 b'.' if !point => point = true,
268 _ => return Err(ParseError::Invalid),
269 }
270 }
271 if !seen_digit {
272 return Err(ParseError::NoDigits);
273 }
274
275 let written = match exponent_text {
276 None => 0,
277 Some(text) => exponent(text)?,
278 };
279 let limit = i64::from(width.max_exponent()) + 200;
282 let mut exponent = (written - after_point).clamp(-limit * 2, limit * 2);
283
284 let precision = width.digits() as usize;
285 let mut status = Status::NONE;
286 if digits.len() > precision {
287 let dropped = digits.len() - precision;
288 exponent += i64::try_from(dropped).unwrap_or(i64::MAX / 4).min(limit * 4);
289 let up = rounds_up(&digits[..precision], &digits[precision..]);
290 if digits[precision..].iter().any(|&d| d != 0) {
291 status = status.with(Status::INEXACT);
292 }
293 digits.truncate(precision);
294 let mut coefficient = value(&digits);
295 if up {
296 coefficient += 1;
297 if coefficient > width.max_coefficient() {
298 coefficient /= 10;
299 exponent += 1;
300 }
301 }
302 return Ok(fit(sign, coefficient, exponent, width, status));
303 }
304 Ok(fit(sign, value(&digits), exponent, width, status))
305}
306
307fn exponent(text: &[u8]) -> Result<i64, ParseError> {
309 let (negative, digits) = match text.first() {
310 Some(b'-') => (true, &text[1..]),
311 Some(b'+') => (false, &text[1..]),
312 _ => (false, text),
313 };
314 if digits.is_empty() {
315 return Err(ParseError::NoExponentDigits);
316 }
317 let mut value: i64 = 0;
318 for &b in digits {
319 if !b.is_ascii_digit() {
320 return Err(ParseError::Invalid);
321 }
322 value = (value * 10 + i64::from(b - b'0')).min(1 << 40);
323 }
324 Ok(if negative { -value } else { value })
325}
326
327fn value(digits: &[u8]) -> u128 {
329 digits.iter().fold(0u128, |value, &d| value * 10 + u128::from(d))
330}
331
332fn rounds_up(kept: &[u8], dropped: &[u8]) -> bool {
335 match dropped.first() {
336 Some(&first) if first > 5 => true,
337 Some(5) => dropped[1..].iter().any(|&d| d != 0) || kept.last().is_some_and(|&d| d % 2 == 1),
338 _ => false,
339 }
340}
341
342fn fit(
351 sign: bool,
352 mut coefficient: u128,
353 mut exponent: i64,
354 width: Width,
355 mut status: Status,
356) -> (u128, Status) {
357 let min = i64::from(width.min_exponent());
358 let max = i64::from(width.max_exponent());
359 if coefficient == 0 {
360 let exponent = i32::try_from(exponent.clamp(min, max)).expect("clamped into range");
361 return (encode(sign, 0, exponent, width), status);
362 }
363 while exponent > max && coefficient * 10 <= width.max_coefficient() {
364 coefficient *= 10;
365 exponent -= 1;
366 }
367 if exponent > max {
368 return (infinity(sign, width), status.with(Status::OVERFLOW).with(Status::INEXACT));
369 }
370 if exponent < min {
371 let mut remainder_nonzero = false;
372 let mut last = 0u128;
373 let mut first = true;
374 let mut steps = 0;
375 while exponent < min {
376 if !first && last != 0 {
377 remainder_nonzero = true;
378 }
379 first = false;
380 last = coefficient % 10;
381 coefficient /= 10;
382 exponent += 1;
383 steps += 1;
384 if coefficient == 0 && steps > 40 {
385 exponent = min;
386 }
387 }
388 let up = last > 5 || (last == 5 && (remainder_nonzero || coefficient % 2 == 1));
391 if last != 0 || remainder_nonzero {
392 status = status.with(Status::INEXACT).with(Status::UNDERFLOW);
393 }
394 if up {
395 coefficient += 1;
396 }
397 }
398 let exponent = i32::try_from(exponent).expect("brought into range");
399 (encode(sign, coefficient, exponent, width), status)
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 fn bits(text: &str, width: Width) -> u128 {
407 parse(text, width).expect("a number").0
408 }
409
410 fn finite(text: &str, width: Width) -> (bool, u128, i32) {
411 match decode(bits(text, width), width) {
412 Decoded::Finite { sign, coefficient, exponent } => (sign, coefficient, exponent),
413 other => panic!("{text} is {other:?}"),
414 }
415 }
416
417 #[test]
419 fn the_bits_are_the_ones_gcc_writes() {
420 assert_eq!(bits("0.", Width::D64), 0x31c0_0000_0000_0000);
421 assert_eq!(bits("-0.", Width::D64), 0xb1c0_0000_0000_0000);
422 assert_eq!(bits("1", Width::D64), 0x31c0_0000_0000_0001);
423 assert_eq!(bits("1.5", Width::D32), 0x3200_000f);
424 assert_eq!(bits("1", Width::D128), 0x3040_0000_0000_0000_0000_0000_0000_0001);
425 assert_eq!(bits("9999999", Width::D32), 0x6cb8_967f);
426 assert_eq!(bits("1.20", Width::D64), 0x3180_0000_0000_0078);
427 assert_eq!(bits("12345675", Width::D32), 0x3312_d688);
428 assert_eq!(bits("99999995", Width::D32), 0x338f_4240);
429 assert_eq!(bits("1e96", Width::D32), 0x5f8f_4240);
430 assert_eq!(bits("15e-102", Width::D32), 0x0000_0002);
431 assert_eq!(bits("0e999", Width::D64), 0x5fe0_0000_0000_0000);
432 assert_eq!(bits("0.000", Width::D32), 0x3100_0000);
433 }
434
435 #[test]
436 fn a_constant_keeps_the_exponent_its_text_wrote() {
437 assert_eq!(finite("1.20", Width::D64), (false, 120, -2));
438 assert_eq!(finite("1.2", Width::D64), (false, 12, -1));
439 assert_eq!(finite("12e3", Width::D64), (false, 12, 3));
440 assert_eq!(finite("0.000", Width::D32), (false, 0, -3));
441 assert_eq!(finite("-00012.5E-1", Width::D128), (true, 125, -2));
442 }
443
444 #[test]
445 fn too_many_digits_round_to_nearest_with_ties_to_even() {
446 assert_eq!(finite("12345675", Width::D32), (false, 1_234_568, 1));
447 assert_eq!(finite("12345665", Width::D32), (false, 1_234_566, 1));
448 assert_eq!(finite("123456650001", Width::D32), (false, 1_234_567, 5));
449 assert_eq!(finite("99999995", Width::D32), (false, 1_000_000, 2));
450 let (_, status) = parse("12345675", Width::D32).expect("a number");
451 assert!(status.has(Status::INEXACT));
452 let (_, status) = parse("12345670", Width::D32).expect("a number");
453 assert!(status.is_none(), "only zeros were dropped");
454 }
455
456 #[test]
457 fn an_exponent_out_of_range_is_clamped_overflows_or_underflows() {
458 assert_eq!(finite("1e96", Width::D32), (false, 1_000_000, 90));
459 let (value, status) = parse("1e97", Width::D32).expect("a number");
460 assert_eq!(value, infinity(false, Width::D32));
461 assert!(status.has(Status::OVERFLOW));
462 assert_eq!(finite("1e-101", Width::D32), (false, 1, -101));
463 let (value, status) = parse("15e-102", Width::D32).expect("a number");
464 assert_eq!(
465 decode(value, Width::D32),
466 Decoded::Finite { sign: false, coefficient: 2, exponent: -101 }
467 );
468 assert!(status.has(Status::UNDERFLOW));
469 let (value, _) = parse("1e-200", Width::D32).expect("a number");
470 assert_eq!(
471 decode(value, Width::D32),
472 Decoded::Finite { sign: false, coefficient: 0, exponent: -101 }
473 );
474 assert_eq!(finite("0e999999999999", Width::D64), (false, 0, 369));
475 }
476
477 #[test]
478 fn every_width_round_trips_its_largest_coefficient() {
479 for width in [Width::D32, Width::D64, Width::D128] {
480 for exponent in [width.min_exponent(), 0, width.max_exponent()] {
481 for sign in [false, true] {
482 let max = width.max_coefficient();
483 let bits = encode(sign, max, exponent, width);
484 assert_eq!(
485 decode(bits, width),
486 Decoded::Finite { sign, coefficient: max, exponent }
487 );
488 }
489 }
490 }
491 }
492
493 #[test]
494 fn infinities_and_nans_read_back_as_themselves() {
495 for width in [Width::D32, Width::D64, Width::D128] {
496 assert_eq!(decode(infinity(true, width), width), Decoded::Infinite { sign: true });
497 assert_eq!(decode(nan(width), width), Decoded::Nan { sign: false, signaling: false });
498 }
499 assert_eq!(infinity(false, Width::D64), 0x7800_0000_0000_0000);
500 assert_eq!(nan(Width::D64), 0x7c00_0000_0000_0000);
501 }
502
503 #[test]
504 fn text_that_is_not_a_decimal_number_is_refused() {
505 assert_eq!(parse("", Width::D64), Err(ParseError::NoDigits));
506 assert_eq!(parse(".", Width::D64), Err(ParseError::NoDigits));
507 assert_eq!(parse("1e", Width::D64), Err(ParseError::NoExponentDigits));
508 assert_eq!(parse("0x1p3", Width::D64), Err(ParseError::Invalid));
509 assert_eq!(parse("1.2.3", Width::D64), Err(ParseError::Invalid));
510 }
511}