浏览代码

Calculator: Round small number to prevent crash

Small numbers (smaller than 1e-19) can't be displayed in the calculator.
They provoke a division by zero in Keypad::set_value(), as 10^20
overflows.
Lucas CHOLLET 3 年之前
父节点
当前提交
7532ef78ad

+ 33 - 1
Userland/Applications/Calculator/Calculator.cpp

@@ -68,11 +68,14 @@ KeypadValue Calculator::begin_operation(Operation operation, KeypadValue argumen
         res = argument;
         break;
     case Operation::MemAdd:
-        m_mem = m_mem + argument; //avoids the need for operator+=()
+        m_mem = m_mem + argument; // avoids the need for operator+=()
         res = m_mem;
         break;
     }
 
+    if (should_be_rounded(res))
+        round(res);
+
     return res;
 }
 
@@ -112,6 +115,9 @@ KeypadValue Calculator::finish_operation(KeypadValue argument)
         VERIFY_NOT_REACHED();
     }
 
+    if (should_be_rounded(res))
+        round(res);
+
     clear_operation();
     return res;
 }
@@ -122,3 +128,29 @@ void Calculator::clear_operation()
     m_saved_argument = 0;
     clear_error();
 }
+
+bool Calculator::should_be_rounded(KeypadValue value)
+{
+    // We check if pow(10, value.m_decimal_places) overflow.
+    // If it does, the value can't be displayed (and provoke a division by zero), see Keypad::set_value()
+    // For u64, the threshold is 19
+    return value.m_decimal_places > rounding_threshold;
+}
+
+void Calculator::round(KeypadValue& value)
+{
+    while (value.m_decimal_places > rounding_threshold) {
+        bool const need_increment = value.m_value % 10 > 4;
+
+        value.m_value /= 10;
+        if (need_increment)
+            value.m_value++;
+
+        value.m_decimal_places--;
+
+        if (value.m_value == 0) {
+            value = 0;
+            return;
+        }
+    }
+}

+ 16 - 0
Userland/Applications/Calculator/Calculator.h

@@ -47,6 +47,22 @@ public:
     void clear_error() { m_has_error = false; }
 
 private:
+    static bool should_be_rounded(KeypadValue);
+    static void round(KeypadValue&);
+
+    static constexpr auto rounding_threshold = []() consteval
+    {
+        using used_type = u64;
+
+        auto count = 1;
+        used_type res = 10;
+        while (!__builtin_mul_overflow(res, (used_type)10, &res)) {
+            count++;
+        }
+        return count;
+    }
+    ();
+
     Operation m_operation_in_progress { Operation::None };
     KeypadValue m_saved_argument { (i64)0 };
     KeypadValue m_mem { (i64)0 };

+ 1 - 0
Userland/Applications/Calculator/KeypadValue.h

@@ -12,6 +12,7 @@
 
 class KeypadValue {
     friend class Keypad;
+    friend class Calculator;
 
 public:
     KeypadValue(i64, u8);