///|
// The original algorithm mixes Python integers/floats and strong NumPy
// scalars. The origin affects promotion, wrapping and range's index protocol.
priv enum LogGridNumber {
PythonInteger(@bigint.BigInt)
NumpyInteger(Int64)
NumpyUnsigned(UInt64)
PythonFloat(Double)
NumpyFloat(Double)
Text
}
///|
fn log_grid_error(message : String) -> @core.DataError {
@core.DataError::InvalidTicks(message~)
}
///|
fn log_grid_integer(value : Int32) -> LogGridNumber {
PythonInteger(@bigint.BigInt::from_int(value))
}
///|
fn LogGridNumber::integer(
self : LogGridNumber,
) -> @bigint.BigInt raise @core.DataError {
match self {
PythonInteger(value) => value
NumpyInteger(value) => @bigint.BigInt::from_int64(value)
NumpyUnsigned(value) => @bigint.BigInt::from_uint64(value)
_ => raise log_grid_error("log grid requires an integer index")
}
}
///|
fn log_grid_float_integer(
value : @bigint.BigInt,
) -> Double raise @core.DataError {
let result = index_round_binary(value, 0)
if result.is_inf() {
raise log_grid_error("log grid integer is too large to convert to float")
}
result
}
///|
fn LogGridNumber::floating(
self : LogGridNumber,
) -> Double raise @core.DataError {
match self {
PythonInteger(value) => log_grid_float_integer(value)
NumpyInteger(value) => value.to_double()
NumpyUnsigned(value) =>
index_round_binary(@bigint.BigInt::from_uint64(value), 0)
PythonFloat(value) | NumpyFloat(value) => value
Text =>
raise log_grid_error(
"log tick count text cannot participate in arithmetic",
)
}
}
///|
fn log_grid_kind(
left : LogGridNumber,
right : LogGridNumber,
) -> Int32 raise @core.DataError {
if left is Text || right is Text {
raise log_grid_error("log tick count text cannot participate in arithmetic")
}
if left is NumpyFloat(_) || right is NumpyFloat(_) {
return 4
}
if left is PythonFloat(_) || right is PythonFloat(_) {
if left is NumpyInteger(_) ||
left is NumpyUnsigned(_) ||
right is NumpyInteger(_) ||
right is NumpyUnsigned(_) {
return 4
}
return 3
}
if (left is NumpyInteger(_) && right is NumpyUnsigned(_)) ||
(left is NumpyUnsigned(_) && right is NumpyInteger(_)) {
return 4
}
if left is NumpyInteger(_) || right is NumpyInteger(_) {
return 1
}
if left is NumpyUnsigned(_) || right is NumpyUnsigned(_) {
return 2
}
0
}
///|
fn log_grid_checked_integer(
value : LogGridNumber,
unsigned : Bool,
) -> @bigint.BigInt raise @core.DataError {
let integer = value.integer()
let lower = if unsigned {
@bigint.BigInt::from_int(0)
} else {
@bigint.BigInt::from_int64(-9223372036854775808L)
}
let upper = if unsigned {
@bigint.BigInt::from_uint64(18446744073709551615UL)
} else {
@bigint.BigInt::from_int64(9223372036854775807L)
}
if integer < lower || integer > upper {
raise log_grid_error(
"log grid Python integer cannot be converted to the NumPy integer dtype",
)
}
integer
}
///|
fn log_grid_wrap(value : @bigint.BigInt) -> UInt64 {
let modulus = @bigint.BigInt::from_int(1) << 64
let remainder = value % modulus
(if remainder < @bigint.BigInt::from_int(0) {
remainder + modulus
} else {
remainder
}).to_uint64()
}
///|
fn log_grid_int_divmod(
left : @bigint.BigInt,
right : @bigint.BigInt,
) -> (@bigint.BigInt, @bigint.BigInt) {
let quotient = left / right
let remainder = left % right
if !remainder.is_zero() &&
(remainder < @bigint.BigInt::from_int(0)) !=
(right < @bigint.BigInt::from_int(0)) {
(quotient - @bigint.BigInt::from_int(1), remainder + right)
} else {
(quotient, remainder)
}
}
///|
fn log_grid_float_divmod(left : Double, right : Double) -> (Double, Double) {
let mut remainder = left % right
let mut quotient = (left - remainder) / right
if remainder != 0.0 {
if (remainder < 0.0) != (right < 0.0) {
remainder += right
quotient -= 1.0
}
} else {
remainder = if right.reinterpret_as_uint64() >> 63 == 0UL {
0.0
} else {
-0.0
}
}
let floor = if quotient != 0.0 {
let floor = quotient.floor()
if quotient - floor > 0.5 {
floor + 1.0
} else {
floor
}
} else {
let divided = left / right
if divided.reinterpret_as_uint64() >> 63 == 0UL {
0.0
} else {
-0.0
}
}
(floor, remainder)
}
///|
// Operations: addition, subtraction, multiplication, floor division, remainder.
fn log_grid_op(
left : LogGridNumber,
right : LogGridNumber,
operation : Int32,
) -> LogGridNumber raise @core.DataError {
let kind = log_grid_kind(left, right)
if kind <= 2 {
let a = if kind == 0 {
left.integer()
} else {
log_grid_checked_integer(left, kind == 2)
}
let b = if kind == 0 {
right.integer()
} else {
log_grid_checked_integer(right, kind == 2)
}
let result = match operation {
0 => a + b
1 => a - b
2 => a * b
_ =>
if b.is_zero() {
if kind == 0 {
raise log_grid_error("log grid integer division by zero")
}
@bigint.BigInt::from_int(0)
} else {
let (quotient, remainder) = log_grid_int_divmod(a, b)
if operation == 3 {
quotient
} else {
remainder
}
}
}
if kind == 1 {
NumpyInteger(log_grid_wrap(result).reinterpret_as_int64())
} else if kind == 2 {
NumpyUnsigned(log_grid_wrap(result))
} else {
PythonInteger(result)
}
} else {
let a = left.floating()
let b = right.floating()
if operation >= 3 && b == 0.0 && kind == 3 {
raise log_grid_error("log grid float division by zero")
}
let value = match operation {
0 => a + b
1 => a - b
2 => a * b
3 => if b == 0.0 { a / b } else { log_grid_float_divmod(a, b).0 }
_ => log_grid_float_divmod(a, b).1
}
if kind == 3 {
PythonFloat(value)
} else {
NumpyFloat(value)
}
}
}
///|
// Correctly rounded Python integer true division need not convert either
// arbitrarily large operand to binary64 first.
fn log_grid_integer_ratio(
numerator : @bigint.BigInt,
denominator : @bigint.BigInt,
) -> Double raise @core.DataError {
if denominator.is_zero() {
raise log_grid_error("log grid true division by zero")
}
let zero = @bigint.BigInt::from_int(0)
let negative = (numerator < zero) != (denominator < zero)
let a = if numerator < zero { numerator.neg() } else { numerator }
let b = if denominator < zero { denominator.neg() } else { denominator }
if a.is_zero() {
return if negative { -0.0 } else { 0.0 }
}
let mut exponent = a.bit_length() - b.bit_length()
if (if exponent >= 0 { a < b << exponent } else { a << -exponent < b }) {
exponent -= 1
}
let unit = Int32::max(exponent - 52, -1074)
let a = if unit < 0 { a << -unit } else { a }
let b = if unit > 0 { b << unit } else { b }
let quotient = a / b
let remainder = a % b
let twice = remainder << 1
let rounded = if twice > b ||
(twice == b && (quotient.to_uint64() & 1UL) != 0UL) {
quotient + @bigint.BigInt::from_int(1)
} else {
quotient
}
let value = index_round_binary(
if negative {
rounded.neg()
} else {
rounded
},
unit,
)
if value.is_inf() {
raise log_grid_error("log grid integer ratio overflows float")
}
if value == 0.0 && negative {
-0.0
} else {
value
}
}
///|
fn log_grid_divide(
left : LogGridNumber,
right : LogGridNumber,
) -> LogGridNumber raise @core.DataError {
let kind = log_grid_kind(left, right)
if kind == 0 {
return PythonFloat(log_grid_integer_ratio(left.integer(), right.integer()))
}
let a = left.floating()
let b = right.floating()
if kind == 3 && b == 0.0 {
raise log_grid_error("log grid float division by zero")
}
let value = a / b
if kind == 3 {
PythonFloat(value)
} else {
NumpyFloat(value)
}
}
///|
fn log_grid_ceil(value : LogGridNumber) -> LogGridNumber raise @core.DataError {
match value {
PythonInteger(_) | NumpyInteger(_) | NumpyUnsigned(_) =>
PythonInteger(value.integer())
_ => PythonInteger(auto_minor_round(value.floating().ceil()))
}
}
///|
fn log_grid_compare_integer_float(
integer : @bigint.BigInt,
floating : Double,
) -> Int32? {
if floating.is_nan() {
return None
}
if floating.is_inf() {
return Some(if floating < 0.0 { 1 } else { -1 })
}
let (mantissa, exponent) = index_binary_parts(floating)
let a = if exponent < 0 { integer << -exponent } else { integer }
let b = if exponent > 0 { mantissa << exponent } else { mantissa }
Some(if a < b { -1 } else if a > b { 1 } else { 0 })
}
///|
fn log_grid_compare(
left : LogGridNumber,
right : LogGridNumber,
) -> Int32? raise @core.DataError {
if left is Text || right is Text {
raise log_grid_error("log grid text is not ordered with numbers")
}
let left_float = left is PythonFloat(_) || left is NumpyFloat(_)
let right_float = right is PythonFloat(_) || right is NumpyFloat(_)
if !left_float && !right_float {
let a = left.integer()
let b = right.integer()
return Some(if a < b { -1 } else if a > b { 1 } else { 0 })
}
if left is PythonInteger(integer) && right is PythonFloat(value) {
return log_grid_compare_integer_float(integer, value)
}
if left is PythonFloat(value) && right is PythonInteger(integer) {
return log_grid_compare_integer_float(integer, value).map(result => -result)
}
let a = left.floating()
let b = right.floating()
if a.is_nan() || b.is_nan() {
return None
}
Some(if a < b { -1 } else if a > b { 1 } else { 0 })
}
///|
fn log_grid_max(
left : LogGridNumber,
right : LogGridNumber,
) -> LogGridNumber raise @core.DataError {
if log_grid_compare(left, right) is Some(-1) {
right
} else {
left
}
}
///|
fn log_grid_min(
left : LogGridNumber,
right : LogGridNumber,
) -> LogGridNumber raise @core.DataError {
if log_grid_compare(left, right) is Some(1) {
right
} else {
left
}
}
///|
fn log_grid_space(value : LogTickSpace) -> LogGridNumber {
match value {
Number(value) =>
NumpyFloat(
if value < 2.0 {
2.0
} else if value > 9.0 {
9.0
} else {
value
},
)
Integer(value) => {
let clipped = if value < @bigint.BigInt::from_int(2) {
2
} else if value > @bigint.BigInt::from_int(9) {
9
} else {
value.to_int()
}
if value >= @bigint.BigInt::from_int64(-9223372036854775808L) &&
value <= @bigint.BigInt::from_int64(9223372036854775807L) {
NumpyInteger(clipped.to_int64())
} else if value >= @bigint.BigInt::from_int(0) &&
value <= @bigint.BigInt::from_uint64(18446744073709551615UL) {
NumpyUnsigned(clipped.to_uint64())
} else {
log_grid_integer(clipped)
}
}
}
}