fix: fix floating point precision error
This commit is contained in:
@@ -89,6 +89,42 @@ Services from BPN API may lack complete data (especially prices). Always enrich
|
|||||||
### Notification System
|
### Notification System
|
||||||
Field handlers generate notifications (auto-changes) → collected by controller → sent via HX-Trigger → displayed as toasts.
|
Field handlers generate notifications (auto-changes) → collected by controller → sent via HX-Trigger → displayed as toasts.
|
||||||
|
|
||||||
|
### Floating-Point Precision in Price Calculations
|
||||||
|
**Critical**: All monetary comparisons must account for floating-point arithmetic accumulation errors.
|
||||||
|
|
||||||
|
**The Problem:**
|
||||||
|
- Prices are parsed from XML as German-formatted strings (e.g., `"1.812,70"`)
|
||||||
|
- Multiple price additions (rooms + services + insurances) accumulate tiny precision errors (`~1e-15` per operation)
|
||||||
|
- With bulk insurance and deep calculation chains, errors compound to `~1e-13` or larger
|
||||||
|
- Example: API returns `1812.7`, calculation produces `1812.6999999999998`
|
||||||
|
|
||||||
|
**The Solution:**
|
||||||
|
- **Always round monetary values to 2 decimal places before comparison**
|
||||||
|
- Use `round($price, 2)` for cent precision (standard for EUR currency)
|
||||||
|
- Never use strict equality (`===` or `!==`) on unrounded float prices
|
||||||
|
|
||||||
|
**Implementation Example (Step3Controller:93-94):**
|
||||||
|
```php
|
||||||
|
// CORRECT: Round to cent precision before comparison
|
||||||
|
$apiTotal = round($inquiryResponse->totalPrice ?? 0.0, 2);
|
||||||
|
$calculatedTotal = round($this->priceCalculator->calculateGrandTotal($bookingCreateDto), 2);
|
||||||
|
|
||||||
|
if ($apiTotal !== $calculatedTotal) {
|
||||||
|
// Handle mismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// WRONG: Direct float comparison (will fail due to precision errors)
|
||||||
|
if ($inquiryResponse->totalPrice !== $this->priceCalculator->calculateGrandTotal($bookingCreateDto)) {
|
||||||
|
// This comparison is unreliable!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Notes:**
|
||||||
|
- Rounding eliminates precision errors smaller than 1 cent (€0.01)
|
||||||
|
- The BPN API returns prices already rounded to cent precision
|
||||||
|
- This is the industry-standard approach for financial calculations
|
||||||
|
- Only affects comparison logic - does not alter actual price calculation flow
|
||||||
|
|
||||||
### Dirty State Detection (Edit Mode)
|
### Dirty State Detection (Edit Mode)
|
||||||
Fingerprint-based change detection to warn users about unsaved modifications:
|
Fingerprint-based change detection to warn users about unsaved modifications:
|
||||||
- **BookingFingerprintService** generates SHA-256 hash of all mutable booking data
|
- **BookingFingerprintService** generates SHA-256 hash of all mutable booking data
|
||||||
@@ -345,6 +381,7 @@ ddev exec "php -r 'opcache_reset()';" # Clear opcache after code cha
|
|||||||
- **Edit mode service availability**: Services with `available <= 0` remain visible and editable for participants who already have them (prevents fingerprint false positives)
|
- **Edit mode service availability**: Services with `available <= 0` remain visible and editable for participants who already have them (prevents fingerprint false positives)
|
||||||
- **Session cleanup on exit**: All exit paths from edit mode (save, discard, cancel) properly clear session to reset dirty state
|
- **Session cleanup on exit**: All exit paths from edit mode (save, discard, cancel) properly clear session to reset dirty state
|
||||||
- **Participant naming convention**: Index 0 is "Anmelder:in", others are "Teilnehmer:in N" (1-based, not 0-based)
|
- **Participant naming convention**: Index 0 is "Anmelder:in", others are "Teilnehmer:in N" (1-based, not 0-based)
|
||||||
|
- **Floating-point price comparisons**: ALWAYS use `round($price, 2)` before comparing monetary values to avoid precision errors (see "Floating-Point Precision in Price Calculations" section)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ class Step3Controller extends AbstractController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate price match (exact comparison)
|
// Validate price match (rounded to cent precision to avoid floating-point errors)
|
||||||
$apiTotal = $inquiryResponse->totalPrice ?? 0.0;
|
$apiTotal = round($inquiryResponse->totalPrice ?? 0.0, 2);
|
||||||
$calculatedTotal = $this->priceCalculator->calculateGrandTotal($bookingCreateDto);
|
$calculatedTotal = round($this->priceCalculator->calculateGrandTotal($bookingCreateDto), 2);
|
||||||
|
|
||||||
if ($apiTotal !== $calculatedTotal) {
|
if ($apiTotal !== $calculatedTotal) {
|
||||||
return $this->handleApiError(
|
return $this->handleApiError(
|
||||||
|
|||||||
Reference in New Issue
Block a user