odbc_api/conversion.rs
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
use atoi::{FromRadix10, FromRadix10Signed};
/// Convert the text representation of a decimal into an integer representation. The integer
/// representation is not truncating the fraction, but is instead the value of the decimal times 10
/// to the power of scale. E.g. 123.45 of a Decimal with scale 3 is thought of as 123.450 and
/// represented as 123450. This method will regard any non digit character as a radix character with
/// the exception of a `+` or `-` at the beginning of the string.
///
/// This method is robust against representation which do not have trailing zeroes as well as
/// arbitrary radix character. If you do not write a generic application and now the specific way
/// your database formats decimals you may come up with faster methods to parse decimals.
pub fn decimal_text_to_i128(text: &[u8], scale: usize) -> i128 {
// High is now the number before the decimal point
let (mut high, num_digits_high) = i128::from_radix_10_signed(text);
let (low, num_digits_low) = if num_digits_high == text.len() {
(0, 0)
} else {
i128::from_radix_10(&text[(num_digits_high + 1)..])
};
// Left shift high so it is compatible with low
for _ in 0..num_digits_low {
high *= 10;
}
// We want to increase the absolute of high by low without changing highs sign
let mut n = if high < 0 || (high == 0 && text[0] == b'-') {
high - low
} else {
high + low
};
// We would be done now, if every database would include trailing zeroes, but they might choose
// to omit those. Therfore we see if we need to leftshift n further in order to meet scale.
for _ in 0..(scale - num_digits_low) {
n *= 10;
}
n
}
#[cfg(test)]
mod tests {
use super::decimal_text_to_i128;
/// An user of an Oracle database got invalid values from decimal after setting
/// `NLS_NUMERIC_CHARACTERS` to ",." instead of ".".
///
/// See issue:
/// <https://github.com/pacman82/arrow-odbc-py/discussions/74#discussioncomment-8083928>
#[test]
fn decimal_is_represented_with_comma_as_radix() {
let actual = decimal_text_to_i128(b"10,00000", 5);
assert_eq!(1_000_000, actual);
}
/// Since scale is 5 in this test case we would expect five digits after the radix, yet Oracle
/// seems to not emit trailing zeroes. Also see issue:
/// <https://github.com/pacman82/arrow-odbc-py/discussions/74#discussioncomment-8083928>
#[test]
fn decimal_with_less_zeroes() {
let actual = decimal_text_to_i128(b"10.0", 5);
assert_eq!(1_000_000, actual);
}
#[test]
fn negative_decimal() {
let actual = decimal_text_to_i128(b"-10.00000", 5);
assert_eq!(-1_000_000, actual);
}
#[test]
fn negative_decimal_small() {
let actual = decimal_text_to_i128(b"-0.1", 5);
assert_eq!(-10000, actual);
}
}