Skip to content

Instantly share code, notes, and snippets.

@Kimundi
Created January 10, 2013 21:00
Show Gist options
  • Select an option

  • Save Kimundi/4505745 to your computer and use it in GitHub Desktop.

Select an option

Save Kimundi/4505745 to your computer and use it in GitHub Desktop.
#[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