|
|
from flask import Flask, request, render_template_string |
|
|
from decimal import Decimal, ROUND_HALF_UP, InvalidOperation |
|
|
import re |
|
|
from datetime import datetime |
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
|
|
|
CONVERSION_TABLE = { |
|
|
'length': { |
|
|
'm': 1, |
|
|
'cm': 0.01, |
|
|
'mm': 0.001, |
|
|
'km': 1000, |
|
|
}, |
|
|
'time': { |
|
|
's': 1, |
|
|
'min': 60, |
|
|
'h': 3600, |
|
|
}, |
|
|
'mass': { |
|
|
'g': 1, |
|
|
'kg': 1000, |
|
|
'mg': 0.001, |
|
|
'µg': 1e-6, |
|
|
}, |
|
|
'volume': { |
|
|
'L': 1, |
|
|
'mL': 0.001, |
|
|
} |
|
|
} |
|
|
|
|
|
def log(message): |
|
|
"""タイムスタンプ付きでメッセージを出力""" |
|
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] |
|
|
print(f"[{timestamp}] {message}") |
|
|
|
|
|
def parse_value_unit(value_unit_str): |
|
|
"""数値と単位をパースして、共通単位に変換""" |
|
|
log(f"Parsing: {value_unit_str}") |
|
|
match = re.match(r"([\d.eE+-]+)\s*\[([a-zA-Zµ^2]+)\]", value_unit_str) |
|
|
if match: |
|
|
value, unit = match.groups() |
|
|
log(f"Parsed value: {value}, unit: {unit}") |
|
|
try: |
|
|
decimal_value = Decimal(value) |
|
|
except InvalidOperation: |
|
|
raise ValueError(f"Invalid numeric value: {value}") |
|
|
sig_figs = len(value.split('.')[1]) if '.' in value else 0 |
|
|
return decimal_value, unit, sig_figs |
|
|
else: |
|
|
raise ValueError("Invalid format. The input should be in the form 'value[unit]'.") |
|
|
|
|
|
def find_unit_family(unit): |
|
|
"""指定された単位のファミリー(カテゴリ)を見つける""" |
|
|
for family, units in CONVERSION_TABLE.items(): |
|
|
if unit in units: |
|
|
return family |
|
|
raise ValueError(f"Unit {unit} not found in any known unit families.") |
|
|
|
|
|
def convert_to_common_unit(value, from_unit, to_unit): |
|
|
"""異なる単位を同じ単位に変換する""" |
|
|
from_family = find_unit_family(from_unit) |
|
|
to_family = find_unit_family(to_unit) |
|
|
|
|
|
if from_family != to_family: |
|
|
raise ValueError(f"Cannot convert between different unit families: {from_unit} to {to_unit}") |
|
|
|
|
|
conversion_factor = CONVERSION_TABLE[from_family][from_unit] / CONVERSION_TABLE[to_family][to_unit] |
|
|
converted_value = value * Decimal(conversion_factor) |
|
|
log(f"Converting {value} {from_unit} to {converted_value} {to_unit}") |
|
|
return converted_value |
|
|
|
|
|
def calculate_expression(expression): |
|
|
"""複数の数値を処理する計算(加算、減算、乗算、除算に対応)""" |
|
|
log(f"Calculating expression: {expression}") |
|
|
pattern = r"([\d.eE+-]+\s*\[[^\]]+\])" |
|
|
terms = re.findall(pattern, expression) |
|
|
log(f"Found terms: {terms}") |
|
|
|
|
|
if not terms: |
|
|
raise ValueError("Invalid expression format") |
|
|
|
|
|
|
|
|
value1, unit1, sig_figs1 = parse_value_unit(terms[0]) |
|
|
total_value = value1 |
|
|
total_unit = unit1 |
|
|
sig_figs_list = [sig_figs1] |
|
|
|
|
|
for i in range(1, len(terms)): |
|
|
value2, unit2, sig_figs2 = parse_value_unit(terms[i]) |
|
|
sig_figs_list.append(sig_figs2) |
|
|
|
|
|
if unit1 != unit2 and ('*' not in expression and '/' not in expression): |
|
|
value2 = convert_to_common_unit(value2, unit2, unit1) |
|
|
|
|
|
if '*' in expression: |
|
|
total_value *= value2 |
|
|
total_unit = f"{total_unit}*{unit2}" |
|
|
elif '/' in expression: |
|
|
total_value /= value2 |
|
|
total_unit = f"{total_unit}/{unit2}" |
|
|
else: |
|
|
total_value += value2 |
|
|
|
|
|
log(f"Current total: {total_value}, current significant figures list: {sig_figs_list}") |
|
|
|
|
|
|
|
|
min_sig_figs = min(sig_figs_list) |
|
|
rounded_result = total_value.quantize(Decimal('1e{0}'.format(-(min_sig_figs - 1))), rounding=ROUND_HALF_UP) |
|
|
|
|
|
|
|
|
rounded_result_str = format(rounded_result, 'f') |
|
|
|
|
|
log(f"Final result: {rounded_result_str} [{total_unit}]") |
|
|
|
|
|
return f"{rounded_result_str} [{total_unit}]" |
|
|
|
|
|
|
|
|
template = b""" |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Unit Calculator</title> |
|
|
<style> |
|
|
body { |
|
|
font-family: Arial, sans-serif; |
|
|
background-color: #f4f4f4; |
|
|
margin: 0; |
|
|
padding: 20px; |
|
|
} |
|
|
h1 { |
|
|
color: #333; |
|
|
} |
|
|
form { |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
input[type="text"] { |
|
|
padding: 8px; |
|
|
width: 300px; |
|
|
font-size: 16px; |
|
|
} |
|
|
button { |
|
|
padding: 8px 12px; |
|
|
font-size: 16px; |
|
|
background-color: #007BFF; |
|
|
color: #fff; |
|
|
border: none; |
|
|
cursor: pointer; |
|
|
} |
|
|
button:hover { |
|
|
background-color: #0056b3; |
|
|
} |
|
|
.result { |
|
|
font-size: 20px; |
|
|
font-weight: bold; |
|
|
color: #007BFF; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<h1>Unit Calculator</h1> |
|
|
<form id="calcForm"> |
|
|
<label for="expression">Enter expression:</label> |
|
|
<input type="text" id="expression" name="expression" required> |
|
|
<button type="submit">Calculate</button> |
|
|
</form> |
|
|
<div id="result" class="result"></div> |
|
|
|
|
|
<script> |
|
|
document.getElementById('calcForm').addEventListener('submit', function(event) { |
|
|
event.preventDefault(); |
|
|
var expression = document.getElementById('expression').value; |
|
|
var xhr = new XMLHttpRequest(); |
|
|
xhr.open('POST', '/calculate', true); |
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
|
|
xhr.onreadystatechange = function () { |
|
|
if (xhr.readyState === 4 && xhr.status === 200) { |
|
|
document.getElementById('result').innerText = 'Result: ' + xhr.responseText; |
|
|
} |
|
|
}; |
|
|
xhr.send('expression=' + encodeURIComponent(expression)); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
""" |
|
|
|
|
|
@app.route('/', methods=['GET']) |
|
|
def index(): |
|
|
return render_template_string(template.decode('utf-8')) |
|
|
|
|
|
@app.route('/calculate', methods=['POST']) |
|
|
def calculate(): |
|
|
expression = request.form['expression'] |
|
|
try: |
|
|
result = calculate_expression(expression) |
|
|
return result |
|
|
except Exception as e: |
|
|
log(f"Error: {str(e)}") |
|
|
return f"Error: {str(e)}", 400 |
|
|
|
|
|
if __name__ == '__main__': |
|
|
app.run(debug=True, host="0.0.0.0", port=7860) |
|
|
|