|
543 | 543 | The Boolean variable \pythonil{negative} is set to \pythonil{True} if \pythonil{a < 0} and to \pythonil{negative} otherwise.
|
544 | 544 | We then make sure that \pythonil{a} is positive by computing~\pythonil{abs(a)}.
|
545 | 545 | Then we also copy the \pgls{denominator} into a variable~\pythonil{b}.
|
546 |
| - |
| 546 | +We will not change the local variable~\pythonil{b} in our function, so we mark it with the \pgls{typeHint}~\pythonilIdx{Final}.% |
| 547 | +% |
| 548 | +\bestPractice{varFinal}{% |
| 549 | +Every time you declare a variable that you do not intend to change, mark it with the \pgls{typeHint}~\pythonilIdx{Final}. % |
| 550 | +On one hand, this conveys the intention \inQuotes{this does not change} to anybody who reads the code. % |
| 551 | +On the other hand, if you do accidentally change it later, tools like \mypy\ can notify you about this error in your code.% |
| 552 | +}% |
| 553 | +% |
547 | 554 | We now want to fill a list~\pythonil{digits} with the digits representing the fraction in a \pythonil{while}~loop.
|
548 | 555 | Let us assume that our fraction is~$-\frac{179}{16}$.
|
549 | 556 | Then \pythonil{negative == True}, \pythonil{a = 179}, and \pythonil{b = 16}.
|
|
638 | 645 | Interestingly, they fail!
|
639 | 646 | The output tells us that \pythonil{Fraction(91995, 100000).decimal_str(3)} does not yield the expected~\pythonil{"0.92"}.
|
640 | 647 | Instead, we get \pythonil{"0.9110"}.
|
| 648 | +Where does the trailing zero come from? |
| 649 | +And why do we have two 1s? |
| 650 | +Even if we did not round correctly, at least we should get something like~0.919, but certainly not~0.911? |
641 | 651 |
|
642 | 652 | \begin{figure}%
|
643 | 653 | \centering%
|
|
687 | 697 | What we did not see in that output is that actually \emph{two} \pglspl{doctest} failed.
|
688 | 698 | A left-click on the second failed test in~\cref{fig:dunder:doctests3} tells us that \pythonil{Fraction(99995, 100000).decimal_str(4)} did not yield the expected~\pythonil{"1"}.
|
689 | 699 | Instead, it produced~\pythonil{"0.99910"}.
|
| 700 | +Why is there a \pythonil{"0"} at the end of our number? |
| 701 | +Where did it come from? |
| 702 | +Zeros at the end should not be possible with our code. |
| 703 | +Also, there are four~9s in our number, not three. |
| 704 | +What went wrong here? |
690 | 705 |
|
691 |
| -While you might have guessed the problem when reading our code for \pythonil{decimal_str}, let us here assume that we are clueless why these tests fail. |
| 706 | +We are clueless why these tests fail. |
692 | 707 | The question arises:
|
693 | 708 | What can we do?
|
694 | 709 |
|
|
754 | 769 | \caption{Using the \debugger\ in \pycharm.}%
|
755 | 770 | \label{fig:dunder:debugA}%
|
756 | 771 | \end{figure}%
|
| 772 | +\afterpage{\clearpage}% |
757 | 773 | %
|
758 | 774 | In \pycharm, we can apply the \debugger\ to a complete program, but also to \pglspl{doctest}.
|
759 | 775 | This is what we will do in \cref{fig:dunder:debugA}.
|
|
832 | 848 | \caption{Using the \debugger\ in \pycharm.}%
|
833 | 849 | \label{fig:dunder:debugB}%
|
834 | 850 | \end{figure}%
|
| 851 | +\afterpage{\clearpage}% |
835 | 852 |
|
836 | 853 | This test case is already successful, so we are not interested in it.
|
837 | 854 | Among the symbols in \menu{Debug} register, we click \pycharmDebuggerResume, which will let the program continue its execution~(\cref{fig:dunder:debug07}).
|
|
845 | 862 | When the debugger arrives at the fifth test case, \pythonil{Fraction(1235, 1000)}, we find that this fraction has been normalized correctly to~$\frac{247}{200}$.
|
846 | 863 | Nonetheless, we can skip this test case via \keys{F9}, too, because we know that it will succeed~(\cref{fig:dunder:debug11}).
|
847 | 864 | This takes us to the last successful \pgls{doctest} case, \pythonil{Fraction(99995, 100000)}, which corresponds to~$\frac{19999}{20000}$ in \cref{fig:dunder:debug12}.
|
848 |
| -After skipping it by pressing~\pycharmDebuggerResume, we will finally arrive at the cases that did fail and which we hence want to investigate step-by-step.% |
849 |
| -\clearpage% |
850 |
| -% |
| 865 | +After skipping it by pressing~\pycharmDebuggerResume, we will finally arrive at the cases that did fail and which we hence want to investigate step-by-step. |
| 866 | + |
851 | 867 | \begin{figure}%
|
852 | 868 | \ContinuedFloat%
|
853 | 869 | \centering%
|
|
902 | 918 | \caption{Using the \debugger\ in \pycharm.}%
|
903 | 919 | \label{fig:dunder:debugC}%
|
904 | 920 | \end{figure}%
|
| 921 | +\afterpage{\clearpage}% |
905 | 922 | %
|
906 | 923 | \begin{sloppypar}%
|
907 | 924 | \Cref{fig:dunder:debug13} shows that we now arrived at the beginning of the failing \pgls{doctest} case \pythonil{Fraction(91995, 100000).decimal_str(3)}.
|
|
960 | 977 | \strut\hfill\strut%
|
961 | 978 | %
|
962 | 979 | \subfloat[][%
|
963 |
| -Using the \debugger\ in \pycharm.% |
| 980 | +\pythonil{digits} now contains the result of \pythonil{a // b}, i.e., is~\pythonil{[0]}. % |
| 981 | +We press~\keys{F8}.% |
964 | 982 | \label{fig:dunder:debug22}%
|
965 | 983 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug22}}}%
|
966 | 984 | %
|
967 | 985 | \\[10pt]%
|
968 | 986 | %
|
969 | 987 | \subfloat[][%
|
970 |
| -Using the \debugger\ in \pycharm.% |
| 988 | +\pythonil{a} now is \pythonil{183990}. % |
| 989 | +We press~\pycharmDebuggerStepOver\ to continue.% |
971 | 990 | \label{fig:dunder:debug23}%
|
972 | 991 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug23}}}%
|
973 | 992 | %
|
974 | 993 | \strut\hfill\strut%
|
975 | 994 | %
|
976 | 995 | \subfloat[][%
|
977 |
| -Using the \debugger\ in \pycharm.% |
| 996 | +We hit the \keys{F8} key to continue. % |
| 997 | +The loop condition is still met, so the first line of the loop body is marked again.% |
978 | 998 | \label{fig:dunder:debug24}%
|
979 | 999 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug24}}}%
|
980 | 1000 | %
|
981 | 1001 | \caption{Using the \debugger\ in \pycharm.}%
|
982 | 1002 | \label{fig:dunder:debugD}%
|
983 |
| -\end{figure} |
| 1003 | +\end{figure}% |
| 1004 | +\afterpage{\clearpage}% |
984 | 1005 |
|
985 | 1006 | This executes \pythonil{b = self.b}.
|
986 | 1007 | Thus, the new local variable \pythonil{b} with value \pythonil{20000} is created in~\cref{fig:dunder:debug19}.
|
|
995 | 1016 | In \cref{fig:dunder:debug21}, we find that now the first line of the loop's body is marked.
|
996 | 1017 | This means that \pythonil{a != 0} and \pythonil{len(digits) <= max_frac} are both~\pythonil{True}.
|
997 | 1018 | And they should be, since \pythonil{a} is \pythonil{18399}, \pythonil{len(digits)} if~0, and \pythonil{max_frac} is~3.
|
998 |
| -We press~\pycharmDebuggerStepOver. |
| 1019 | +We press the \pycharmDebuggerStepOver~button to execute the first line of the loop body. |
| 1020 | + |
| 1021 | +\pythonil{digits.append(a // b)} will append the value \pythonil{18399 // 20000} to the list~\pythonil{digits}. |
| 1022 | +As this is the result of an integer division where the \pgls{denominator} is larger than the \pgls{numerator}, \pythonil{digits} is now~\pythonil{[0]}~(\cref{fig:dunder:debug22}). |
| 1023 | +We press \keys{F8} to continue. |
| 1024 | + |
| 1025 | +Now, \pythonil{a = 10 * (a \% b)} is executed. |
| 1026 | +Since \pythonil{18399 \% 20000} is still \pythonil{18399}, \pythonil{a} becomes \pythonil{183990} in \cref{fig:dunder:debug23}. |
| 1027 | +The head of the loop is now marked again. |
| 1028 | +We press the \pycharmDebuggerStepOver~button to let execute it. |
| 1029 | + |
| 1030 | +In \cref{fig:dunder:debug24}, we again hit \keys{F8}. |
| 1031 | +The loop condition is still met, so the first line in the loop body is marked again. |
999 | 1032 |
|
1000 | 1033 | \begin{figure}%
|
1001 | 1034 | \ContinuedFloat%
|
1002 | 1035 | \centering%
|
1003 | 1036 | %
|
1004 | 1037 | \subfloat[][%
|
1005 |
| -Using the \debugger\ in \pycharm.% |
| 1038 | +\pythonil{9} gets appended to \pythonil{digits}.% |
1006 | 1039 | \label{fig:dunder:debug25}%
|
1007 | 1040 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug25}}}%
|
1008 | 1041 | %
|
1009 | 1042 | \strut\hfill\strut%
|
1010 | 1043 | %
|
1011 | 1044 | \subfloat[][%
|
1012 |
| -Using the \debugger\ in \pycharm.% |
| 1045 | +\pythonil{a} gets updated to \pythonil{39900}.%% |
1013 | 1046 | \label{fig:dunder:debug26}%
|
1014 | 1047 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug26}}}%
|
1015 | 1048 | %
|
1016 | 1049 | \\[10pt]%
|
1017 | 1050 | %
|
1018 | 1051 | \subfloat[][%
|
1019 |
| -Using the \debugger\ in \pycharm.% |
| 1052 | +The loop condition is still met.% |
1020 | 1053 | \label{fig:dunder:debug27}%
|
1021 | 1054 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug27}}}%
|
1022 | 1055 | %
|
1023 | 1056 | \strut\hfill\strut%
|
1024 | 1057 | %
|
1025 | 1058 | \subfloat[][%
|
1026 |
| -Using the \debugger\ in \pycharm.% |
| 1059 | +\pythonil{1} gets appended to \pythonil{digits}.% |
1027 | 1060 | \label{fig:dunder:debug28}%
|
1028 | 1061 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug28}}}%
|
1029 | 1062 | %
|
1030 | 1063 | \\[10pt]%
|
1031 | 1064 | %
|
1032 | 1065 | \subfloat[][%
|
1033 |
| -Using the \debugger\ in \pycharm.% |
| 1066 | +\pythonil{a} gets updated to \pythonil{199000}.% |
1034 | 1067 | \label{fig:dunder:debug29}%
|
1035 | 1068 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug29}}}%
|
1036 | 1069 | %
|
1037 | 1070 | \strut\hfill\strut%
|
1038 | 1071 | %
|
1039 | 1072 | \subfloat[][%
|
1040 |
| -Using the \debugger\ in \pycharm.% |
| 1073 | +The loop condition is still met.% |
1041 | 1074 | \label{fig:dunder:debug30}%
|
1042 | 1075 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug30}}}%
|
1043 | 1076 | %
|
|
1046 | 1079 | \label{fig:dunder:debugE}%
|
1047 | 1080 | \end{figure}%
|
1048 | 1081 | %
|
1049 |
| -% |
1050 | 1082 | \begin{figure}%
|
1051 | 1083 | \ContinuedFloat%
|
1052 | 1084 | \centering%
|
1053 | 1085 | %
|
1054 | 1086 | \subfloat[][%
|
1055 |
| -Using the \debugger\ in \pycharm.% |
| 1087 | +\pythonil{9} gets appended to \pythonil{digits}.% |
1056 | 1088 | \label{fig:dunder:debug31}%
|
1057 | 1089 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug31}}}%
|
1058 | 1090 | %
|
1059 | 1091 | \strut\hfill\strut%
|
1060 | 1092 | %
|
1061 | 1093 | \subfloat[][%
|
1062 |
| -Using the \debugger\ in \pycharm.% |
| 1094 | +\pythonil{a} gets updated to \pythonil{190000}.% |
1063 | 1095 | \label{fig:dunder:debug32}%
|
1064 | 1096 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug32}}}%
|
1065 | 1097 | %
|
1066 | 1098 | \\[10pt]%
|
1067 | 1099 | %
|
1068 | 1100 | \subfloat[][%
|
1069 |
| -Using the \debugger\ in \pycharm.% |
| 1101 | +Since the loop condition evaluates to \pythonil{False}, the cursor is now at the next line after the loop body. % |
| 1102 | +This line checks whether we should round up the next digit. % |
| 1103 | +We hit \keys{F8}.% |
1070 | 1104 | \label{fig:dunder:debug33}%
|
1071 | 1105 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug33}}}%
|
1072 | 1106 | %
|
1073 | 1107 | \strut\hfill\strut%
|
1074 | 1108 | %
|
1075 | 1109 | \subfloat[][%
|
1076 |
| -Using the \debugger\ in \pycharm.% |
| 1110 | +The condition of the \pythonil{if} is met, we can now execute its body. % |
| 1111 | +We press the \pycharmDebuggerStepOver~button.% |
1077 | 1112 | \label{fig:dunder:debug34}%
|
1078 | 1113 | ]{\tightbox{\includegraphics[width=0.48\linewidth]{\currentDir/debug34}}}%
|
1079 | 1114 | %
|
1080 | 1115 | \\[10pt]%
|
1081 | 1116 | %
|
1082 | 1117 | \subfloat[][%
|
1083 |
| -Using the \debugger\ in \pycharm.% |
| 1118 | +Our code for rounding up actually turned the last digit into a~\pythonil{10}! % |
| 1119 | +Because we did not consider that rounding up could cause the next~9 to become a 10, which should be represented as a~0 and lead to the next digit to be rounded up as well.% |
1084 | 1120 | \label{fig:dunder:debug35}%
|
1085 | 1121 | ]{\tightbox{\includegraphics[width=0.8\linewidth]{\currentDir/debug35}}}%
|
1086 | 1122 | %
|
1087 | 1123 | \caption{Using the \debugger\ in \pycharm.}%
|
1088 | 1124 | \label{fig:dunder:debugF}%
|
1089 | 1125 | \end{figure}%
|
1090 |
| -% |
| 1126 | +\afterpage{\clearpage}% |
| 1127 | + |
| 1128 | +In \cref{fig:dunder:debug25,fig:dunder:debug26,fig:dunder:debug27,fig:dunder:debug29,fig:dunder:debug30,fig:dunder:debug31,fig:dunder:debug32} we work our way through the loop in the same way, by pressing \keys{F8} repeatedly. |
| 1129 | +First, \pythonil{9} gets appended to \pythonil{digits}, then \pythonil{a} gets updated to \pythonil{39900}. |
| 1130 | +In the following iteration, \pythonil{1} gets appended to \pythonil{digits}, then \pythonil{a} gets updated to \pythonil{199000}. |
| 1131 | +Then, \pythonil{9} gets appended to \pythonil{digits} and \pythonil{a} gets updated to \pythonil{190000}. |
| 1132 | + |
| 1133 | +At this stage, \pythonil{digits} has become~\pythonil{[0, 9, 1, 9]}. |
| 1134 | +Since \pythonil{max_frac} is \pythonil{3}, \pythonil{len(digits) <= max_frac} is no longer~\pythonil{True}. |
| 1135 | +In \cref{fig:dunder:debug32}, we have arrived back at the head of the loop. |
| 1136 | +When we hit \keys{F8}, the loop condition is evaluated again, but this time it evaluates to~\pythonil{False}. |
| 1137 | +The loop terminates and the cursor is placed on the next line of code after the loop. |
| 1138 | + |
| 1139 | +If we look at what was computed so far, we find that everything is exactly as it should be. |
| 1140 | +We want to translate the fraction~$\frac{91995}{100000}$ to a decimal string with three fractional digits. |
| 1141 | +So far, we got the digits~0, 9, 1, and~9. |
| 1142 | + |
| 1143 | +The next line of code, \pythonil{if (a // b) >= 5}, is supposed to check whether we should round up the last digit. |
| 1144 | +Since \pythonil{a} is \pythonil{190000} and \pythonil{b} is still \pythonil{20000}, \pythonil{a // b} is~\pythonil{9}. |
| 1145 | +So the condition should be met. |
| 1146 | +In \cref{fig:dunder:debug32}, we press the \pycharmDebuggerStepOver~button to find it out. |
| 1147 | + |
| 1148 | +A look at \cref{fig:dunder:debug35} reveals the bug in our code: |
| 1149 | +In order to round up, we incremented the last number in our list~\pythonil{digits}. |
| 1150 | +\pythonil{digits} was~\pythonil{[0, 9, 1, 9]}. |
| 1151 | +So it is~\pythonil{[0, 9, 1, 10]}. |
| 1152 | + |
| 1153 | +The strange trailing zeros in our output were not separate digits. |
| 1154 | +They were the zeros of a ten. |
| 1155 | +We did not consider that, when rounding up, we do not just have simple cases like~1.25 which we can round to~1.3 by only incrementing one digit. |
| 1156 | +We can have cases like~0.9999, which rounds up to~1, even if we want three fractional digits of precision. |
| 1157 | +We can stop the debugging here and go back to our code. |
| 1158 | + |
1091 | 1159 | \gitPython{\programmingWithPythonCodeRepo}{09_dunder/fraction.py}{--args format --labels part_6}{dunder:fraction:part_6}{%
|
1092 | 1160 | The repaired Part~6 of the \pythonil{Fraction} class: A correct \pythonil{decimal_str} method.}%
|
1093 |
| -% |
| 1161 | + |
| 1162 | +In \cref{dunder:fraction:part_6}, we revisit the \pythonil{decimal_str} method of our class~\pythonil{Fraction}. |
| 1163 | +Our rounding-up code becomes more complex: |
| 1164 | +First, we need to loop over all fractional digits, from the end of the list \pythonil{digits} forward: |
| 1165 | +\pythonil{for i in range(len(digits) - 1, 0, -1)} does this. |
| 1166 | +If \pythonil{len(digits) == 5}, then \pythonil{range(len(digits) - 1, 0, -1)} iterates over the numbers 4, 3, 2, and~1. |
| 1167 | +We increment the digit at index~\pythonil{i} by~1. |
| 1168 | +If it does not become~10, then we can stop the loop via~\pythonilIdx{break}. |
| 1169 | +If it did become~10, then we set it to zero and continue the loop. |
| 1170 | +This will increment the next digit, and so on. |
| 1171 | +If we arrive at index~1 and still need to continue, the regular loop execution ends anyway. |
| 1172 | + |
| 1173 | +Then, the \pythonil{else} statement is hit. |
| 1174 | +Recall from \cref{sec:loopElse} that the \pythonil{else}\pythonIdx{for!else} statement at the bottom of a loop is \emph{only} executed if the loop finished regularly, i.e., if no \pythonil{break} statement was executed. |
| 1175 | +Therefore, if and only if the digit at index~1 also became~10 and was then set to~0, we increment the digit at index~0. |
| 1176 | +This digit represents the integer part of our fraction. |
| 1177 | +Here, it is totally OK to round a~9 to a~10. |
| 1178 | +For example, 9.999 can be rounded to 10, and 1239.9 can be rounded to 1240. |
| 1179 | + |
| 1180 | +This new code for rounding numbers may introduce zeros at the end of our string. |
| 1181 | +We just gobble them up with an additional \pythonil{while} loop directly after the rounding. |
| 1182 | +And with this, we are done. |
| 1183 | +We have working code converting fractional numbers to decimal strings. |
| 1184 | +All the \pglspl{doctest} now pass. |
| 1185 | + |
1094 | 1186 | \gitPython{\programmingWithPythonCodeRepo}{09_dunder/fraction_sqrt.py}{--args format}{dunder:fraction_sqrt}{%
|
1095 | 1187 | Using the \pythonil{Fraction} class to compute square roots.}%
|
1096 | 1188 | %
|
|
0 commit comments