Created
January 10, 2013 21:00
-
-
Save Kimundi/4505745 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #[path="math/round.rs"] | |
| mod round; | |
| mod numgenerics; | |
| //use core::Num; | |
| //use core::num; | |
| //use core::num::*; | |
| //use core::prelude::*; | |
| // Todo: cleanum imports in code. | |
| use core::num::Num; | |
| use core::num::Num::*; | |
| use core::num::{Zero, One}; | |
| use core::cmp::*; | |
| use core::vec::*; | |
| use round::*; | |
| use numgenerics::*; | |
| pub enum ExponentFormat { | |
| ExpNone, | |
| ExpDec, | |
| ExpBin | |
| } | |
| pub enum SignificantDigits { | |
| DigAll, | |
| DigMax(uint), | |
| DigExact(uint) | |
| } | |
| /** | |
| * | |
| */ | |
| pub fn from_digit_ascii(num: uint, radix: uint) -> Option<char> { | |
| if radix > 36 { | |
| fail fmt!("from_digit_ascii: radix %? is to high (maximum 36)", num) | |
| } | |
| if num < radix { | |
| if num < 10 { | |
| Some(('0' as uint + num) as char) | |
| } else { | |
| Some(('a' as uint + num - 10u) as char) | |
| } | |
| } else { | |
| None | |
| } | |
| } | |
| /** | |
| * Checks if a character parses as a numeric digit. | |
| * | |
| * Returns `true` if `c` is a valid digit under `radix`, and `false` | |
| * otherwise. | |
| * | |
| * Fails if given a `radix` > 36. | |
| * | |
| * Note: This is just a convenience wrapper that | |
| * matches on `to_digit_ascii()`. | |
| */ | |
| pub fn is_digit_ascii(c: char, radix: uint) -> bool { | |
| match to_digit_ascii(c, radix) { | |
| Some(_) => true, | |
| None => false | |
| } | |
| } | |
| /** | |
| * Parses a character as a numeric digit. | |
| * | |
| * Returns `Some(n)` if `c` is a valid digit under `radix`, and `None` | |
| * otherwise. | |
| * | |
| * Fails if given a `radix` > 36. | |
| */ | |
| pub fn to_digit_ascii(c: char, radix: uint) -> Option<uint> { | |
| if radix > 36 { | |
| fail fmt!("to_digit_ascii: radix %? is to high (maximum 36)", radix) | |
| } | |
| let v; | |
| match c { | |
| '0' .. '9' => v = (c as uint) - ('0' as uint), | |
| 'a' .. 'z' => v = (c as uint) - ('a' as uint) + 10u, | |
| 'A' .. 'Z' => v = (c as uint) - ('A' as uint) + 10u, | |
| _ => return None | |
| } | |
| if v < radix { Some(v) } | |
| else { None } | |
| } | |
| /** | |
| * Converts a number of type `T` to an ASCII byte vector. This is meant to | |
| * be a common base implementation for all numeric string conversion | |
| * functions like `to_str()` or `to_str_radix()`. | |
| * | |
| * # Arguments | |
| * - `num` - The number to convert. Accepts any generic number type, | |
| * integer as well as float. | |
| * - `radix` - The base to use. Accepts only the values 2-36. | |
| * - `digits` - The amount of digits to use for emitting the | |
| * fractional part, if any. Options are all digits (`DigAll`), | |
| * maximum N digits (`DigMax(uint)`) or exactly N digits | |
| * (`DigExact(uint)`). | |
| * - `special` - Whether to attempt to compare to special values like | |
| * `inf` or `NaN`. Can fail if it doesn't match `num`s Type | |
| * (see safety note). | |
| * | |
| * # Return value | |
| * A tuple containing the byte vector, and a boolean flag indicating | |
| * whether it represents a special value like `inf`, `-inf`, `NaN` or not. | |
| * | |
| * # Failure | |
| * - Fails if `radix` < 2 or `radix` > 36. | |
| * - Fails on wrong value for `special` (see safety note). | |
| * | |
| * # Safety note | |
| * The function detects the special values `inf`, `-inf` and `NaN` by | |
| * dynamically comparing `num` to `1 / 0`, `-1 / 0` and `0 / 0` | |
| * (each of type T) if `special` is `true`. This will fail on integer types | |
| * with a 'divide by zero'. Likewise, it will fail if `num` **is** one of | |
| * those special values, and `special` is `false`, because then the | |
| * algorithm just does normal calculations on them. | |
| * | |
| * # Example | |
| * ~~~ | |
| * // Using the to_ascii_str_common wrapper | |
| * to_ascii_str_common(123456, 10u, DigAll, false); | |
| * to_ascii_str_common(123456.789, 10u, DigAll, true); | |
| * to_ascii_str_common(1.0 / 0.0, 10u, DigAll, true); | |
| * to_ascii_str_common(128u, 2u, DigAll, false); | |
| * to_ascii_str_common(-255, 16u, DigAll, false); | |
| * ~~~ | |
| * results in | |
| * ~~~ | |
| * (~"123456", false) | |
| * (~"123456.789", false) | |
| * (~"inf", true) | |
| * (~"10000000", false) | |
| * (~"-ff", false) | |
| * ~~~ | |
| * | |
| * # Todo | |
| * - Allow rounding of fractional part. | |
| * - Change function to take a pointer and not require Copy trait to make | |
| * it usable with fat number types like bignum. | |
| */ | |
| pub fn to_ascii_bytes_common<T: Num Zero One Eq Ord Round Copy>( | |
| num: T, radix: uint, | |
| digits: SignificantDigits, special: bool) -> (~[u8], bool) { | |
| if radix as int < 2 { | |
| fail fmt!("to_bytes_common: radix %? to low, \ | |
| must lie in the range [2, 36]", radix) | |
| } else if radix as int > 36 { | |
| fail fmt!("to_bytes_common: radix %? to low, \ | |
| must lie in the range [2, 36]", radix) | |
| } | |
| let _0: T = Zero::zero(); | |
| let _1: T = One::one(); | |
| if special { | |
| if is_gen_NaN(&num) { | |
| return (str::to_bytes("NaN"), true); | |
| } else if is_gen_inf(&num){ | |
| return (str::to_bytes("inf"), true); | |
| } else if is_gen_neg_inf(&num) { | |
| return (str::to_bytes("-inf"), true); | |
| } | |
| } | |
| let neg = num < _0; | |
| let num_abs = if (neg) { -num } else { num }; | |
| let mut buf: ~[u8] = ~[]; | |
| let mut deccum = num_abs; | |
| let radix_gen = Num::from_int::<T>(radix as int); | |
| let (limit_digits, max_digits, exact) = match digits { | |
| DigAll => (false, 0u, false), | |
| DigMax(count) => (true, count, false), | |
| DigExact(count) => (true, count, true) | |
| }; | |
| // make sure we have at least a leading '0' by looping at least once | |
| loop { | |
| let current_digit = deccum % radix_gen; | |
| deccum /= radix_gen; | |
| deccum = deccum.floor(); | |
| buf.push(from_digit_ascii(current_digit.to_int() as uint, radix) | |
| .unwrap() as u8); | |
| if !(deccum > _0) { break; } | |
| } | |
| if neg { | |
| buf.push('-' as u8); | |
| } | |
| vec::reverse(buf); | |
| deccum = num_abs.fract(); | |
| if deccum > _0 || (limit_digits && exact && max_digits > 0){ | |
| buf.push('.' as u8); | |
| let mut dig = 0u; | |
| while // calculate new digits while | |
| (!limit_digits && deccum > _0) // - there is no limit and | |
| || (limit_digits && // there are digits left | |
| dig < max_digits && ( // - or there is a limit, | |
| exact || // it's not reached yet and | |
| (!exact && deccum > _0) // - it's exact | |
| ) // - or it's a maximum, | |
| ) { // and there are digits left | |
| deccum *= radix_gen; | |
| let current_digit = deccum.floor(); | |
| buf.push(from_digit_ascii(current_digit.to_int() as uint, radix) | |
| .unwrap() as u8); | |
| deccum = deccum.fract(); | |
| dig += 1u; | |
| } | |
| } | |
| // if number of digits is not exact, remove all trailing '0's up to | |
| // and including the '.' | |
| if !exact { | |
| let mut i = buf.len() - 1; | |
| while buf[i] == '0' as u8 && buf.len() > 1 { | |
| i -= 1; | |
| } | |
| if buf[i] == '.' as u8 { | |
| i -= 1; | |
| } | |
| buf = buf.slice(0, i + 1); | |
| } | |
| (buf, false) | |
| } | |
| /** | |
| * Converts a number to a string. This is a wrapper for | |
| * `to_ascii_bytes_common()`, for details see there. | |
| */ | |
| pub fn to_str_common<T: Num Zero One Eq Ord Round Copy>( | |
| num: T, radix: uint, | |
| digits: SignificantDigits, special: bool) -> (~str, bool) { | |
| let (bytes, special) = to_ascii_bytes_common(num, radix, digits, special); | |
| (str::from_bytes(bytes), special) | |
| } | |
| // Define some constants for from_ascii_bytes_commons input validation | |
| priv const DIGIT_P: uint = ('p' as uint) - ('a' as uint) + 10u; | |
| priv const DIGIT_I: uint = ('i' as uint) - ('a' as uint) + 10u; | |
| priv const DIGIT_E: uint = ('e' as uint) - ('a' as uint) + 10u; | |
| /** | |
| * Converts a byte slice to a number of type `T`. This is meant to | |
| * be a common base implementation for all numeric string conversion | |
| * functions like `from_str()` or `from_str_radix()`. | |
| * | |
| * # Arguments | |
| * - `buf` - The byte slice to parse. | |
| * - `radix` - Which base to parse the number as. Accepts 2-36 | |
| * - `fractional` - Whether to accept numbers with fractional parts. | |
| * - `exponent` - Which exponent format to accept. Options are: | |
| * - `ExpNone`: No Exponent, accepts just plain numbers like `42` or `-8.2`. | |
| * - `ExpDec`: Accepts numbers with a decimal exponent like `42e5` or | |
| * `8.2E-2`. The exponent itself is always base 10. Can | |
| * conflict with `radix`, see Failure. | |
| * - `ExpBin`: Accepts numbers with a binary exponent like `42P-8` or | |
| * `FFp128`. The exponent itself is always base 10. Can | |
| * conflict with `radix`, see Failure. | |
| * - `special` - Whether to accept special values like `inf`, `-inf` | |
| * and `NaN`. Can conflict with `radix`, see Failure. | |
| * - `empty_zero` - Whether to accept a empty `buf` as a 0 or not. | |
| * | |
| * # Return value | |
| * Returns `Some(n)` if `buf` successfully parses to a number n, or `None` if | |
| * `buf`s content is not parse-able, depending on the constraints set by the other | |
| * arguments. | |
| * | |
| * # Failure | |
| * - Fails if `radix` < 2 or `radix` > 36. | |
| * - Fails if `radix` > 13 and `exponent` is `ExpDec` due to conflict | |
| * between digit and exponent sign `'e'`. | |
| * - Fails if `radix` > 24 and `exponent` is `ExpBin` due to conflict | |
| * between digit and exponent sign `'p'`. | |
| * - Fails if `radix` > 17 and `special == true` due to conflict | |
| * between digit and lowest first character in `inf` and `NaN`, the `'i'`. | |
| * | |
| * # Todo | |
| * - Maybe add option to allow for number formats like FF_FF_FF_FF, ak ignore | |
| * underscore. | |
| */ | |
| pub fn from_ascii_bytes_common<T: Num Zero One Copy>(buf: &[u8], radix: uint, | |
| fractional: bool, exponent: ExponentFormat, | |
| special: bool, empty_zero: bool) -> Option<T> { | |
| match exponent { | |
| ExpDec if radix > DIGIT_E | |
| => fail fmt!("from_bytes_common: radix %? incompatible with use \ | |
| of 'e' as decimal exponent", radix), | |
| ExpBin if radix > DIGIT_P | |
| => fail fmt!("from_bytes_common: radix %? incompatible with use \ | |
| of 'p' as binary exponent", radix), | |
| _ if special && radix > DIGIT_I //lowest first digit in inf and NaN | |
| => fail fmt!("from_bytes_common: radix %? incompatible with \ | |
| special values 'inf' and 'NaN'", radix), | |
| _ if radix as int < 2 | |
| => fail fmt!("from_bytes_common: radix %? to low, \ | |
| must lie in the range [2, 36]", radix), | |
| _ if radix as int > 36 | |
| => fail fmt!("from_bytes_common: radix %? to high, \ | |
| must lie in the range [2, 36]", radix), | |
| _ => () | |
| } | |
| let _0: T = Zero::zero(); | |
| let _1: T = One::one(); | |
| let radix_gen: T = ::num::Num::from_int(radix as int); | |
| let len = buf.len(); | |
| if len == 0 { | |
| if empty_zero { | |
| return Some(_0); | |
| } else { | |
| return None; | |
| } | |
| } | |
| if special { | |
| let _inf = gen_inf(); | |
| let _neg_inf = gen_neg_inf(); | |
| let _NaN = gen_NaN(); | |
| if buf == str::to_bytes("inf") { | |
| return Some(_inf); | |
| } else if buf == str::to_bytes("-inf") { | |
| return Some(_neg_inf); | |
| } else if buf == str::to_bytes("NaN") { | |
| return Some(_NaN); | |
| } | |
| } | |
| let (start, sign) = match buf[0] { | |
| '-' as u8 => (1u, -_1), | |
| '+' as u8 => (1u, _1), | |
| _ => (0u, _1) | |
| }; | |
| let mut accum = _0; | |
| let mut i = start; | |
| let mut exp_found = false; | |
| while i < len { | |
| let c = buf[i] as char; | |
| match to_digit_ascii(c, radix) { | |
| Some(digit) => { | |
| accum *= radix_gen; // move accum one left | |
| accum += ::num::Num::from_int(digit as int); // add current position | |
| } | |
| None => match c { | |
| 'e' | 'E' | 'p' | 'P' => { | |
| exp_found = true; | |
| break; // start of exponent | |
| } | |
| '.' if fractional => { | |
| i += 1u; // skip the '.' | |
| break; // start of fractional part | |
| } | |
| _ => return None // invalid number | |
| } | |
| } | |
| i += 1u; | |
| } | |
| if !exp_found { // prevents 2. parse attempt | |
| let mut power = _1; | |
| while i < len { | |
| let c = buf[i] as char; | |
| match to_digit_ascii(c, radix) { | |
| Some(digit) => { | |
| power /= radix_gen; | |
| accum += ::num::Num::from_int::<T>(digit as int) * power; | |
| } | |
| None => match c { | |
| 'e' | 'E' | 'p' | 'P' => { | |
| exp_found = true; | |
| break; // start of exponent | |
| } | |
| _ => return None // invalid number | |
| } | |
| } | |
| i += 1u; | |
| } | |
| } | |
| let mut multiplier = _1; | |
| if exp_found { | |
| let c = buf[i] as char; | |
| let base = match (c, exponent) { | |
| ('e', ExpDec) | ('E', ExpDec) => 10u, | |
| ('p', ExpBin) | ('P', ExpBin) => 2u, | |
| _ => return None // char doesn't fit given exponent format | |
| }; | |
| // parse remaining bytes as decimal integer, | |
| // skipping the exponent char | |
| let exp: Option<int> = from_ascii_bytes_common( | |
| buf.view(i+1, len), 10, false, ExpNone, false, false); | |
| match exp { | |
| Some(exp_pow) => { | |
| multiplier = if exp_pow < 0 { | |
| _1 / generic_pow_with_uint::<T>( | |
| base, (-exp_pow.to_int()) as uint) | |
| } else { | |
| generic_pow_with_uint::<T>( | |
| base, exp_pow.to_int() as uint) | |
| } | |
| } | |
| None => return None // invalid exponent; invalid number | |
| } | |
| } | |
| Some(sign * accum * multiplier) | |
| } | |
| /** | |
| * Converts a string to a number. This is a wrapper for | |
| * `from_ascii_bytes_common()`, for details see there. | |
| */ | |
| pub fn from_str_common<T: Num Zero One Copy>(buf: &str, radix: uint, | |
| fractional: bool, exponent: ExponentFormat, | |
| special: bool, empty_zero: bool) -> Option<T> { | |
| from_ascii_bytes_common(str::to_bytes(buf), radix, fractional, | |
| exponent, special, empty_zero) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment