no more float conversion
This commit is contained in:
@@ -447,51 +447,286 @@ decode_magnitude :: proc(data: []byte, positive: bool) -> (DDB_Number, bool) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Arithmetic Operations
|
||||
// Decimal Arithmetic (38-digit precision, no float conversion)
|
||||
// ============================================================================
|
||||
|
||||
// Add two DDB_Numbers
|
||||
MAX_DDB_PRECISION :: 38
|
||||
|
||||
// Add two DDB_Numbers with full decimal precision.
|
||||
// Returns an owned DDB_Number.
|
||||
add_ddb_numbers :: proc(a: DDB_Number, b: DDB_Number) -> (DDB_Number, bool) {
|
||||
// Convert to f64 for arithmetic (loses precision but matches current behavior)
|
||||
// TODO: Implement proper decimal arithmetic
|
||||
a_f64, a_ok := ddb_number_to_f64(a)
|
||||
b_f64, b_ok := ddb_number_to_f64(b)
|
||||
if !a_ok || !b_ok {
|
||||
return {}, false
|
||||
if is_ddb_number_zero(a) { return clone_ddb_number(b), true }
|
||||
if is_ddb_number_zero(b) { return clone_ddb_number(a), true }
|
||||
|
||||
if a.sign == b.sign {
|
||||
// Same sign: add magnitudes, keep sign
|
||||
result, ok := add_magnitudes(a, b)
|
||||
if !ok { return {}, false }
|
||||
result.sign = a.sign
|
||||
return result, true
|
||||
}
|
||||
|
||||
result := a_f64 + b_f64
|
||||
return f64_to_ddb_number(result)
|
||||
// Different signs: subtract smaller magnitude from larger
|
||||
cmp := compare_ddb_number_magnitudes(a, b)
|
||||
if cmp == 0 {
|
||||
return DDB_Number{
|
||||
sign = true,
|
||||
integer_part = strings.clone("0"),
|
||||
fractional_part = strings.clone(""),
|
||||
exponent = 0,
|
||||
}, true
|
||||
}
|
||||
|
||||
if cmp > 0 {
|
||||
result, ok := subtract_magnitudes(a, b)
|
||||
if !ok { return {}, false }
|
||||
result.sign = a.sign
|
||||
return result, true
|
||||
} else {
|
||||
result, ok := subtract_magnitudes(b, a)
|
||||
if !ok { return {}, false }
|
||||
result.sign = b.sign
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
|
||||
// Subtract two DDB_Numbers
|
||||
// Subtract two DDB_Numbers: a - b
|
||||
subtract_ddb_numbers :: proc(a: DDB_Number, b: DDB_Number) -> (DDB_Number, bool) {
|
||||
// Convert to f64 for arithmetic
|
||||
a_f64, a_ok := ddb_number_to_f64(a)
|
||||
b_f64, b_ok := ddb_number_to_f64(b)
|
||||
if !a_ok || !b_ok {
|
||||
neg_b := b
|
||||
neg_b.sign = !b.sign
|
||||
return add_ddb_numbers(a, neg_b)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal arithmetic helpers
|
||||
// ============================================================================
|
||||
|
||||
// Expand a DDB_Number to effective integer and fractional digit bytes
|
||||
// with the exponent fully applied. Returns heap-allocated slices (caller frees).
|
||||
@(private="file")
|
||||
expand_digits :: proc(num: DDB_Number) -> (int_digits: []u8, frac_digits: []u8) {
|
||||
dp := len(num.integer_part) + int(num.exponent)
|
||||
all_len := len(num.integer_part) + len(num.fractional_part)
|
||||
|
||||
if dp <= 0 {
|
||||
// Everything is fractional, need leading zeros
|
||||
frac := make([]u8, -dp + all_len)
|
||||
for i in 0..<(-dp) {
|
||||
frac[i] = '0'
|
||||
}
|
||||
for i in 0..<len(num.integer_part) {
|
||||
frac[-dp + i] = num.integer_part[i]
|
||||
}
|
||||
for i in 0..<len(num.fractional_part) {
|
||||
frac[-dp + len(num.integer_part) + i] = num.fractional_part[i]
|
||||
}
|
||||
|
||||
int_d := make([]u8, 1)
|
||||
int_d[0] = '0'
|
||||
return int_d, frac
|
||||
}
|
||||
|
||||
if dp >= all_len {
|
||||
// Everything is integer, may need trailing zeros
|
||||
int_d := make([]u8, dp)
|
||||
for i in 0..<len(num.integer_part) {
|
||||
int_d[i] = num.integer_part[i]
|
||||
}
|
||||
for i in 0..<len(num.fractional_part) {
|
||||
int_d[len(num.integer_part) + i] = num.fractional_part[i]
|
||||
}
|
||||
for i in all_len..<dp {
|
||||
int_d[i] = '0'
|
||||
}
|
||||
return int_d, nil
|
||||
}
|
||||
|
||||
// Decimal point falls within the original integer_part
|
||||
if dp <= len(num.integer_part) {
|
||||
int_d := make([]u8, dp)
|
||||
for i in 0..<dp {
|
||||
int_d[i] = num.integer_part[i]
|
||||
}
|
||||
|
||||
frac_len := (len(num.integer_part) - dp) + len(num.fractional_part)
|
||||
frac := make([]u8, frac_len)
|
||||
for i in dp..<len(num.integer_part) {
|
||||
frac[i - dp] = num.integer_part[i]
|
||||
}
|
||||
offset := len(num.integer_part) - dp
|
||||
for i in 0..<len(num.fractional_part) {
|
||||
frac[offset + i] = num.fractional_part[i]
|
||||
}
|
||||
return int_d, frac
|
||||
}
|
||||
|
||||
// Decimal point falls within the original fractional_part
|
||||
frac_split := dp - len(num.integer_part)
|
||||
|
||||
int_d := make([]u8, dp)
|
||||
for i in 0..<len(num.integer_part) {
|
||||
int_d[i] = num.integer_part[i]
|
||||
}
|
||||
for i in 0..<frac_split {
|
||||
int_d[len(num.integer_part) + i] = num.fractional_part[i]
|
||||
}
|
||||
|
||||
remaining := len(num.fractional_part) - frac_split
|
||||
frac: []u8 = nil
|
||||
if remaining > 0 {
|
||||
frac = make([]u8, remaining)
|
||||
for i in frac_split..<len(num.fractional_part) {
|
||||
frac[i - frac_split] = num.fractional_part[i]
|
||||
}
|
||||
}
|
||||
return int_d, frac
|
||||
}
|
||||
|
||||
// Normalize a DDB_Number that owns its strings.
|
||||
// Clones the trimmed result, frees the originals.
|
||||
@(private="file")
|
||||
normalize_owned :: proc(num: DDB_Number) -> DDB_Number {
|
||||
norm := normalize_ddb_number(num)
|
||||
|
||||
// Clone the normalized subslices BEFORE freeing originals
|
||||
new_int := strings.clone(norm.integer_part)
|
||||
new_frac := strings.clone(norm.fractional_part)
|
||||
|
||||
// Free the originals
|
||||
delete(num.integer_part)
|
||||
delete(num.fractional_part)
|
||||
|
||||
return DDB_Number{
|
||||
sign = norm.sign,
|
||||
integer_part = new_int,
|
||||
fractional_part = new_frac,
|
||||
exponent = norm.exponent,
|
||||
}
|
||||
}
|
||||
|
||||
// Add absolute values. Returns owned DDB_Number (sign=true).
|
||||
@(private="file")
|
||||
add_magnitudes :: proc(a: DDB_Number, b: DDB_Number) -> (DDB_Number, bool) {
|
||||
a_int, a_frac := expand_digits(a)
|
||||
b_int, b_frac := expand_digits(b)
|
||||
defer { delete(a_int); delete(a_frac); delete(b_int); delete(b_frac) }
|
||||
|
||||
max_int := max(len(a_int), len(b_int))
|
||||
max_frac := max(len(a_frac), len(b_frac))
|
||||
total := max_int + max_frac
|
||||
|
||||
// Build zero-padded aligned arrays
|
||||
a_aligned := make([]u8, total)
|
||||
b_aligned := make([]u8, total)
|
||||
defer { delete(a_aligned); delete(b_aligned) }
|
||||
|
||||
for i in 0..<total { a_aligned[i] = '0'; b_aligned[i] = '0' }
|
||||
|
||||
// Integer digits: right-aligned in [0..max_int)
|
||||
a_off := max_int - len(a_int)
|
||||
b_off := max_int - len(b_int)
|
||||
for i in 0..<len(a_int) { a_aligned[a_off + i] = a_int[i] }
|
||||
for i in 0..<len(b_int) { b_aligned[b_off + i] = b_int[i] }
|
||||
|
||||
// Fractional digits: left-aligned in [max_int..total)
|
||||
for i in 0..<len(a_frac) { a_aligned[max_int + i] = a_frac[i] }
|
||||
for i in 0..<len(b_frac) { b_aligned[max_int + i] = b_frac[i] }
|
||||
|
||||
// Add right-to-left
|
||||
result := make([]u8, total + 1) // +1 for carry
|
||||
carry: u8 = 0
|
||||
for i := total - 1; i >= 0; i -= 1 {
|
||||
sum := (a_aligned[i] - '0') + (b_aligned[i] - '0') + carry
|
||||
result[i + 1] = (sum % 10) + '0'
|
||||
carry = sum / 10
|
||||
}
|
||||
result[0] = carry + '0'
|
||||
|
||||
// Split: decimal point is at max_int + 1 (carry slot shifts everything)
|
||||
int_end := max_int + 1
|
||||
int_str := strings.clone(string(result[:int_end]))
|
||||
frac_str := strings.clone(string(result[int_end:]))
|
||||
delete(result)
|
||||
|
||||
num := normalize_owned(DDB_Number{
|
||||
sign = true,
|
||||
integer_part = int_str,
|
||||
fractional_part = frac_str,
|
||||
exponent = 0,
|
||||
})
|
||||
|
||||
if len(num.integer_part) + len(num.fractional_part) > MAX_DDB_PRECISION {
|
||||
delete(num.integer_part)
|
||||
delete(num.fractional_part)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
result := a_f64 - b_f64
|
||||
return f64_to_ddb_number(result)
|
||||
return num, true
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Conversion Helpers
|
||||
// ============================================================================
|
||||
// Subtract absolute values: |a| - |b|, where |a| >= |b|.
|
||||
// Returns owned DDB_Number (sign=true).
|
||||
@(private="file")
|
||||
subtract_magnitudes :: proc(a: DDB_Number, b: DDB_Number) -> (DDB_Number, bool) {
|
||||
a_int, a_frac := expand_digits(a)
|
||||
b_int, b_frac := expand_digits(b)
|
||||
defer { delete(a_int); delete(a_frac); delete(b_int); delete(b_frac) }
|
||||
|
||||
// Convert DDB_Number to f64 (may lose precision)
|
||||
ddb_number_to_f64 :: proc(num: DDB_Number) -> (f64, bool) {
|
||||
str := ddb_number_to_string(num)
|
||||
return strconv.parse_f64(str)
|
||||
}
|
||||
max_int := max(len(a_int), len(b_int))
|
||||
max_frac := max(len(a_frac), len(b_frac))
|
||||
total := max_int + max_frac
|
||||
|
||||
// Convert f64 to DDB_Number
|
||||
f64_to_ddb_number :: proc(val: f64) -> (DDB_Number, bool) {
|
||||
// Format with enough precision to preserve the value
|
||||
str := fmt.aprintf("%.17g", val)
|
||||
return parse_ddb_number(str)
|
||||
a_aligned := make([]u8, total)
|
||||
b_aligned := make([]u8, total)
|
||||
defer { delete(a_aligned); delete(b_aligned) }
|
||||
|
||||
for i in 0..<total { a_aligned[i] = '0'; b_aligned[i] = '0' }
|
||||
|
||||
a_off := max_int - len(a_int)
|
||||
b_off := max_int - len(b_int)
|
||||
for i in 0..<len(a_int) { a_aligned[a_off + i] = a_int[i] }
|
||||
for i in 0..<len(b_int) { b_aligned[b_off + i] = b_int[i] }
|
||||
for i in 0..<len(a_frac) { a_aligned[max_int + i] = a_frac[i] }
|
||||
for i in 0..<len(b_frac) { b_aligned[max_int + i] = b_frac[i] }
|
||||
|
||||
// Subtract right-to-left
|
||||
result := make([]u8, total)
|
||||
borrow: u8 = 0
|
||||
for i := total - 1; i >= 0; i -= 1 {
|
||||
ad := a_aligned[i] - '0'
|
||||
bd := (b_aligned[i] - '0') + borrow
|
||||
if ad < bd {
|
||||
ad += 10
|
||||
borrow = 1
|
||||
} else {
|
||||
borrow = 0
|
||||
}
|
||||
result[i] = (ad - bd) + '0'
|
||||
}
|
||||
|
||||
int_str := strings.clone(string(result[:max_int]))
|
||||
frac_str := strings.clone(string(result[max_int:]))
|
||||
delete(result)
|
||||
|
||||
if len(int_str) == 0 {
|
||||
delete(int_str)
|
||||
int_str = strings.clone("0")
|
||||
}
|
||||
|
||||
num := normalize_owned(DDB_Number{
|
||||
sign = true,
|
||||
integer_part = int_str,
|
||||
fractional_part = frac_str,
|
||||
exponent = 0,
|
||||
})
|
||||
|
||||
if len(num.integer_part) + len(num.fractional_part) > MAX_DDB_PRECISION {
|
||||
delete(num.integer_part)
|
||||
delete(num.fractional_part)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
return num, true
|
||||
}
|
||||
|
||||
// Format a DDB_Number for display
|
||||
|
||||
@@ -1262,10 +1262,9 @@ evaluate_sort_key_condition :: proc(item: Item, skc: ^Sort_Key_Condition) -> boo
|
||||
}
|
||||
return false
|
||||
case .BEGINS_WITH:
|
||||
// BEGINS_WITH on numbers: fall through to string comparison
|
||||
item_str := format_ddb_number(item_num)
|
||||
cond_str := format_ddb_number(cond_num)
|
||||
return strings.has_prefix(item_str, cond_str)
|
||||
// begins_with is not a valid operator for Number sort keys.
|
||||
// DynamoDB rejects this at validation time. Return false.
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user